ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java NIO] Selector register 이해하기
    Java/NIO 2024. 6. 11. 06:35
    728x90
    반응형

    - 목차

     

    들어가며.

    Java NIOSelector 는 비동기적으로 여러 채널(Channel)을 모니터링할 수 있는 강력한 도구입니다.

    이 중에서도 register 메서드는 특정 채널을 Selector에 등록하고 관심 이벤트를 설정하는 중요한 역할을 합니다.

    이 글에서는 register 메서드의 사용법과 작동 원리를 살펴보겠습니다.

     

    Selector와 Channel의 관계.

    Selector 는 여러 개의 Channel 을 등록하여, 읽기, 쓰기, 연결 수락 등의 이벤트를 비동기적으로 감지할 수 있습니다.

    register 메서드는 SelectorChannel 사이의 연결 고리 역할을 합니다.

    • Selector: 이벤트를 감지하고, 필요한 처리를 트리거합니다.
    • Channel: Selector 에 등록되어 이벤트를 발생시킵니다.

     

    register 메서드 시그니처.

    register 메소드의 시그니처는 아래와 같습니다.

     

    SelectionKey register(Selector sel, int ops, Object att);

     

    • sel: Selector 객체로, 이벤트를 감지할 주체입니다.
    • ops: 관심 있는 이벤트를 나타내는 정수 값입니다. OP_READ, OP_WRITE, OP_ACCEPT, OP_CONNECT 등이 있습니다.
    • att: 채널과 연관된 부가 정보(attachment)입니다. 이 정보는 이후에 이벤트 처리 시 유용하게 활용됩니다.
    • Return: SelectionKey 객체를 반환합니다. 이 키는 Selector와 Channel의 연결을 나타내며, 추가 작업 시 사용할 수 있습니다.

     

    Socket System Call 알아보기.

    Java NIO Selector 에 대해서 자세히 알아보기 이전에 네트워크 서버를 구축할 때 사용하는 대표적인 시스템 호출(System Call) 에 대해 먼저 알아보겠습니다.

    Socket, Bind, Listen, Accept 등에 대한 System Call 에 대한 배경지식이 있다면 Java NIO 의 Selector 에 대해서 깊이 있는 이해가 가능합니다.

     

    socket.

     

    socket System Call 은 네트워크 통신에 필요한 소켓을 생성합니다.

    그리고 소켓은 네트워크 통신의 출발점이 되는 파일 디스크립터 역할을 합니다.

    대부분의 서버 프로그래밍은 socket 이라는 System Call 의 호출을 기반으로 시작됩니다.

    이는 NIC (Network Interface Card) 와 서버 프로그램 사이에 통신을 수행할 Socket 이라는 가상 파일을 생성하는 과정입니다.

    Socket System Call 을 통해서 소켓이라는 가상의 파일이 생성되구요.

    이를 통해서 네트워크 트래픽이 NIC 를 통해서 서버 프로그램으로 전달될 수 있습니다.

     

     

     

    bind.

    socket System Call 이후에 호출되는 System Callbind 호출입니다.

    bind System Call 은 특정 Port 와 서버 프로그램을 연결하는 역할을 합니다.

    그래서 bind(8080) 와 같은 호출을 통해서 localhost:8080 로의 요청이 binding 된 서버 프로그램으로 전달될 수 있습니다.

    binding 이 된 시점부터 본격적으로 Socket File 에 네트워크 트래픽의 정보들이 기록될 수 있게 됩니다.

     

     

     

    listen.

    listen System Call 은 서버 프로그램이 연결된 Socket File 의 네트워크 요청 정보를 읽어들일 수 있게 됩니다.

    비로소 서버 프로그램은 클라이언트의 연결 요청을 받을 준비를 합니다.

     

     

     

    accept.

     

    accept System Call 은 Socket 으로부터 클라이언트 연결 요청을 가져와 수락합니다.

    accept System Call 과정을 통해서 클라이언트와 통신할 새로운 소켓을 반환합니다.

    여기서 중요한 점은 최초의 Socket 과 Accept System Call 에 의해서 생성되는 Socket 은 별도의 소켓이라는 점이 중요합니다.

     

    register 의 여러 활용 예시 알아보기.

     

    SelectionKey.OP_ACCEPT

    Java NIO 에서 Selector 와 함께 사용되는 상수로, 새로운 연결 요청(Accept 이벤트) 을 감지하는 데 사용됩니다.

    이는 Network System Call 중에서 Accept 와 관련됩니다.

    SelectionKey.OP_ACCEPT 는 서버와 통신을 위한 새로운 Socket 요청과 이를 수락할 준비를 위한 Event 입니다.

     

    SelectorOP_ACCEPT 이벤트를 등록하는 방법은 아래와 같습니다.

     

    package com.westlife.test;
    
    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.*;
    import java.util.Iterator;
    
    public class RegisterTest {
        public static void main(String[] args) throws IOException {
            Selector selector = Selector.open();
            ServerSocketChannel serverChannel = ServerSocketChannel.open();
            serverChannel.bind(new InetSocketAddress(8080));
            serverChannel.configureBlocking(false);
            serverChannel.register(selector, SelectionKey.OP_ACCEPT);
    
            System.out.println("Server is listening on port 8080...");
    
            while (true) {
                selector.select();
    
                Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
                while (keys.hasNext()) {
                    SelectionKey key = keys.next();
                    keys.remove();
    
                    if (key.isAcceptable()) {
                        ServerSocketChannel server = (ServerSocketChannel) key.channel();
                        SocketChannel clientChannel = server.accept();
                        clientChannel.configureBlocking(false);
    
                        clientChannel.register(selector, SelectionKey.OP_READ);
    
                        System.out.println("Accepted connection from: " + clientChannel.getRemoteAddress());
                    } else if (key.isReadable()) {
                        SocketChannel clientChannel = (SocketChannel) key.channel();
                        ByteBuffer buffer = ByteBuffer.allocate(256);
    
                        int bytesRead = clientChannel.read(buffer);
                        if (bytesRead == -1) {
                            clientChannel.close();
                            System.out.println("Client disconnected.");
                        } else {
                            buffer.flip();
                            String message = new String(buffer.array(), 0, buffer.limit());
                            System.out.println("Received: " + message);
    
                            buffer.rewind();
                            clientChannel.write(buffer);
                        }
                    }
                }
            }
        }
    }

     

    nc -vz localhost 8080
    # Connection to localhost port 8080 [tcp/http-alt] succeeded!

     

     

     

    SelectionKey.OP_READ

    Java NIO SelectorOP_READ 는 Selector 에 등록된 Channel 에 데이터가 존재하여 읽을 준비가 된 상태를 모니터링하기 위한 이벤트입니다.

    OP_ACCEPT 단계에서 Client - Server 의 통신을 위한 Socket 이 생성 및 등록되게 되는데요.

    OP_READ 는 클라이언트의 요청이 Channel 에 추가되고 이를 처리할 준비가 된 상태입니다.

     

    아래의 예시처럼 key.isAcceptable 과정에서 clientChannel 을 생성하고 이를 Selector 와 연결합니다.

    그리고 동시에 SelectionKey.OP_READ 이벤트를 등록하였습니다.

     

    if (key.isAcceptable()) {
        ServerSocketChannel server = (ServerSocketChannel) key.channel();
        SocketChannel clientChannel = server.accept();
        clientChannel.configureBlocking(false);
    
        clientChannel.register(selector, SelectionKey.OP_READ);
    
        System.out.println("Accepted connection from: " + clientChannel.getRemoteAddress());
    }

     

    이렇게 selector 가 clientChannel 의 OP_READ 이벤트 모니터링을 할 수 있도록 등록해두었기 때문에

    아래의 else if (key.isReadable()) 코드 블록이 실행될 수 있습니다.

     

     else if (key.isReadable()) {
        SocketChannel clientChannel = (SocketChannel) key.channel();
        ByteBuffer buffer = ByteBuffer.allocate(256);
    
        int bytesRead = clientChannel.read(buffer);
        if (bytesRead == -1) {
            clientChannel.close();
            System.out.println("Client disconnected.");
        } else {
            buffer.flip();
            String message = new String(buffer.array(), 0, buffer.limit());
            System.out.println("Received: " + message);
    
            buffer.rewind();
            clientChannel.write(buffer);
        }
    }

     

     

    반응형
Designed by Tistory.