자바

자바 NIO TCP 넌블로킹 채널 - 넌블로킹 방식의 특징, 셀렉터의 생성과 등록

알통몬_ 2017. 4. 21. 18:18
반응형


안녕하세요 알통몬입니다.

공감 및 댓글은 포스팅 하는데 아주아주 큰 힘이 됩니다!!

포스팅 내용이 찾아주신 분들께 도움이 되길 바라며

더 깔끔하고 좋은 포스팅을 만들어 나가겠습니다^^

 

ServerSocketChannel과 SocketChannel은 블로킹방식도 지원하지만 넌블로킹 방식도 지원합니다.


블로킹 방식의 경우 언제 클라이언트가 연결을 요청할지 알 수가 없어서 accept()에서 블로킹이 됩니다.

그리고 언제 클라이언트가 데이터를 보낼지 알 수가 없어서 read()에서도 항상 받을 준비를 위해 블로킹이 됩니다. 이러한 방식의 단점은 ServerSocketChannel 과 SocketChannel 당 하나의 스레드가 할당되어야 한다는 것입니다. 연결된 클라이언트의 수가 많을수록 스레드의 수가 증가하고 서버의 심각한 문제를 유발할 가능성이 높아집니다. 그래서 이러한 문제를 해결하기 위해 스레드풀( ExecutorService) 을 사용했었습니다.


자바에서는 또 다른 해결책으로 넌블로킹 방식을 지원하는데요.

넌블로킹 방식에서는 connect(), accept(), read(), write() 에서 블로킹이 없습니다.

만약 클라이언트의 요청이 없다면 accept()는 즉시 null을 리턴합니다.

read()도 마찬가지로 클라이언트가 데이터를 보내지 않았다면 바로 0을 리턴합니다.

때문에 매개 값으로 전달한 ByteBuffer에는 아무런 데이터가 저장되지 않습니다.


넌블로킹 방식에서 아래 코드는 클라이언트가 연결 요청을 하지 않으면 무한 루프를 계속 돕니다.

while(true) {

     SocketChannel socketChannel = serverSocketChannel.accept();

    ...

}


accept()에서 블로킹되지 않고 바로 리턴되기 때문입니다. 클라이언트가 요청을 보내기 전까지

while 블록 안에 코드가 쉬지 않고 실행되기 때문에 CPU가 과도하게 소비되는 문제점이 발생하는데요.

그래서 넌블로킹은 이벤트 리스너 역할을 하는 셀렉터를 사용합니다.

넌블로킹에서 selector를 등록해 놓으면 클라이언트의 연결 요청이 들어오는 경우나 데이터가 도착하는 경우,

채널은 selector 에 통보를 하고 selector는 통보한 채널들을 선택해 작업 스레드가 accept()나 read()를

실행해서 즉시 작업을 처리하도록 합니다.

selector 는 멀티 채널의 작업을 싱글 스레드에서 처리할 수 있도록 해주는 멀티플렉서의 역할입니다.

채널은 selector에게 자신을 등록할 때 작업 유형을 SelectionKey로 생성하고 selector의 interest-set(관심키셋)에 저장합니다.


넌블로킹에서 꼭 하나의 작업스레드만을 사용할 필요는 없습니다. 채널 작업 처리 시에 스레드풀을 

사용할 수 있습니다. 작업스레드가 블로킹되지 않으므로 적은 수의 스레드로 많은 양의 작업을

고속으로 할 수 있기 때문에 블로킹 방식보다 서버의 성능이 향상될 수 있습니다.



Selector 는 open() 정적 메소드를 호출하여 생성하고, open()은 IOException을 발생시킬 수 있기 때문에

예외처리를 해주어야 합니다.

try {

    Selector selector = Selector.open();

} catch (IOException ioe) {

 ...

}

셀렉터 에서는 SelectableChannel의 하위 채널만 등록이 가능한데

TCP 통신에서 사용되는 ServerSocketChannel, SocketChannel,

UDP 통신에서 사용되는 DatagramChannel 은 전부 SelectableChannel의 하위 클래스이므로

모두 셀렉터에 등록이 가능합니다. 단 넌블로킹으로 설정된 것만 가능합니다.


각 채널은 register()를 이용해서 셀렉터에 등록합니다.

첫 번째 매개 값은 셀렉터이고 두 번째 매개 값은 채널의 작업 유형입니다.

SelectionKey selectionKey = serverSocketChannel.register(Selector selector, int options);

SelectionKey selectionKey = socketChannel.register(Selector selector, int options);


register() 는 채널과 작업 유형 정보를 담고 있는  SelectionKey 를 생성하고 셀렉터에 관심 키켓에

저장을 한 후에 해당 SelectonKey를 리턴합니다.

SelectionKey selectionKey = ServerSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

ServerSocketChannel 은 클라이언트의 연결 수락작업을 하기 때믄에 작업 유형을 OP_ACCEPT로 했씁니다.

SelectionKey selectionKey = SocketChannel.register(selector, SelectionKey.OP_CONNECT);

SelectionKey selectionKey = SocketChannel.register(SelectionKey.OP_READ);

SelectionKey selectionKey = SocketChannel.register(SelectionKey.OP_WRITE);

* 동일한 SocketChannel 로 두가지 이상의 작업 유형을 등록할 수 없습니다.

register() 를 두 번 이상 호출할 수 없고, 등록은 한 번만 가능하며 작업 유형이 변경될 경우 이미 생성된

SelectionKey를 수정해야 합니다.

register() 가 리턴한 SelectionKey 는 작업 유형 변경, 첨부 객체 저장, 채널 등록 취소 등을 할 때 사용하고

SelectionKEy를 별도로 관리할 필요가 없습니다.

채널이 셀렉터를 등록하면 채널의 KeyFor()로 SelectionKey를 언제든지 얻을 수 있습니다.

SelectionKey key = socketChannel.ketFor(selector);


다음 포스팅에서 이어집니다.

이상입니다.


반응형