NIO의 구성 요소

Channel

네트워크 소켓에 대한 채널을 의미하며 NetworkChannel 인터페이스로 정의된다. NetworkChannel 인터페이스의 구현 클래스로는 ServerSocketChannel, SocketChannel, DatagramChannel 등이 있다.

※ 소켓이란?

서버 프로그램과 클라이언트 프로그램의 양방향 통신을 위한 양방향 소프트웨어 엔드포인트이다. 엔드포인트는 IP 주소와 포트 번호로 구성된다.

Java의 스트림은 read/write 둘 중 하나만 수행할 수 있는 단방향이지만 Channel은 read/write 모두 수행할 수 있는 양방향 입출력 클래스이다.

  • ServerSocketChannel
    • ServerSocketChannel은 클라이언트의 연결 요청이 들어오면 [open] - [bind] - [accept] 단계를 거친 후, SocketChannel을 생성한다.
  • SocketChannel
    • 클라이언트가 전달한 데이터를 read하고, 클라이언트에게 데이터를 write하기 위한 채널이다.
    • ServerSocketChannel이 클라이언트의 연결을 accept하면 SocketChannel이 생성된다.

Selector

  • Selector는 채널에 I/O 이벤트가 있는지 없는지를 알려주는 일종의 이벤트 리스너(Event Listener)의 역할을 한다.
  • ServerSocketChannelSocketChannelregister() 메서드를 통해 Selector로 등록된다.
  • Selector에 등록된 채널은 I/O 이벤트 발생 시, 이벤트 리스닝의 대상이 된다.
  • 즉, Selector.select()를 호출하면 I/O 이벤트가 있는 채널 set을 리턴한다.

SelectionKey

  • SelectionKey는 Selector에 등록된 채널이 어떤 I/O 이벤트를 처리할지를 지정하는 객체이다.
  • SelectionKey 유형
    • SelectionKey.OP_ACCEPT
    • SelectionKey.OP_CONNECT
    • SelectionKey.OP_READ
    • SelectionKey.OP_WRITE
  • SelectionKey는 채널의 register()를 호출하면 리턴된다.
  • SelectionKey.attach()를 통해 SelectionKey 유형의 이벤트를 처리할 이벤트 핸들러를 등록할 수 있다.
  • SelectionKey의 유형은 SelectionKey.interestOps()로 지정할 수 있다.

 

ByteBuffer

  • 데이터를 read/write 할 수 있는 버퍼로 Java의 NIO에서 사용된다.
  • byte buffer가 사용하는 메모리의 위치에 따라서 non-direct buffer와 direct buffer로 나눌 수 있다.
  • non-direct buffer는 우리가 흔히 알고 있는 JVM의 heap memory를 사용하지만 direct buffer는 OS(Operatoion System)의 memory를 사용한다.

Charset

  • character(문자)를 byte로 인코딩하거나 byte를 charter(문자)로 디코딩할 때 사용하는 클래스이다.

NIO의 동작 방식

ServerSocketChannel의 바인딩 흐름

ServerSocketChannel의 바인딩 흐름

  • ServerSocketChannel과 Selector의 생성
    • 애플리케이션이 실행되면 ServerSocketChannel.open()을 통해 ServerSocketChannel을 생성한다.
    • 애플리케이션이 실행되면 Selector.open()을 통해 Selector를 생성한다. 생성된 Selector는 ServerSocketChannel 또는 SocketChannel 등을 Selector에 등록하는데 사용된다.
  • ServerSocketChannel.bind()를 통해 ServerSocketChannel을 바인딩한다.
    • 여기서의 바인딩은  IP 주소와 로컬 포트 번호를 서버 소켓과 연결함을 의미한다.
  • ServerSocketChannel.register()를 통해서 Selector에 ServerSocketChannel을 등록한다.
    • SelectionKey.OP_ACCEPT를 지정함으로써 클라이언트의 connect 요청 이벤트가 발생하면 클라이언트의 connect 요청을 수락하기 위한 로직을 처리할 수 있다.

 

ServerSocketChannel과 Selector를 통한 SocketChannel 연결 흐름

Client와 SocketChannel 연결 흐름

  • 애플리케이션이 실행되고, ServerSocketChannel이 바인딩 된 이후에 Selector는 무한 루프 상태에서 SelectionKey 유형에 해당하는 I/O 이벤트를  처리하기 위해 대기 모드를 유지한다.
  • Selector.select()를 통해 발생하는 이벤트를 기다린다.
    • 이벤트가 발생하는지에 대한 일종의 이벤트 리스닝이라고 볼 수 있다.
  • Selector.selectedKeys()를 통해 이벤트가 발생된 key를 의미하는 SelectionKey set을 얻는다.
    • 여기서는 클라이언트의 connect 요청 흐름에 대해서만 설명하므로 SelectionKey.OP_ACCEPT에 해당하는 이벤트만 처리한다.
  • SelectionKey.channel()을 통해 ServerSocketChannel을 얻는다.
  • ServerSocketChannel.accept()을 통해 클라이언트의 connect 요청을 기다리고, 클라이언트의 connect 요청이 들어오면 SocketChannel을 생성한다.
    • ServerSocketChannel에서 사용하는 포트 번호는 클라이언트의 연결 요청을 수락하기 위해서 계속해서 사용되어야 하므로 SocketChannel은 별도의 새 로컬 포트 번호를 할당 받아서 바인딩된다.

Client와 SocketChannel의 데이터 read/write 흐름

Client와 SocketChannel의 데이터 read/write 흐름

 

  • Client와 SocketChannel이 바인딩 되면 Selector.select()를 통해 I/O 이벤트가 발생할 때까지 대기한다.
  • I/O 이벤트가 발생하면 Selector.selectedKeys()를 이벤트가 발생한 SelectioinKey set을 얻는다.
  • SelectionKey.channel()을 통해 이벤트가 발생한 SocketChannel을 얻을 수 있으며, 이 SocketChannel은 해당 이벤트를 처리할 이벤트 핸들러에게 전달된다.
  • SelectionKey.attachment()를 통해 이벤트를 처리할 이벤트 핸들러를 얻을 수 있으며, 이벤트 핸들러가 SocketChannel을 이용해 이벤트를 처리한다.

+ Recent posts

출처: http://large.tistory.com/23 [Large]