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)의 역할을 한다.ServerSocketChannel
과SocketChannel
은register()
메서드를 통해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과 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 연결 흐름
- 애플리케이션이 실행되고,
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이 바인딩 되면
Selector.select()
를 통해 I/O 이벤트가 발생할 때까지 대기한다. - I/O 이벤트가 발생하면
Selector.selectedKeys()
를 이벤트가 발생한 SelectioinKey set을 얻는다. SelectionKey.channel()
을 통해 이벤트가 발생한 SocketChannel을 얻을 수 있으며, 이 SocketChannel은 해당 이벤트를 처리할 이벤트 핸들러에게 전달된다.SelectionKey.attachment()
를 통해 이벤트를 처리할 이벤트 핸들러를 얻을 수 있으며, 이벤트 핸들러가 SocketChannel을 이용해 이벤트를 처리한다.