본문 바로가기

공부/컴퓨터

[NIO-03] NIO Selector - 1

반응형
==========================================================================
이 글은 이론적으로 아는 것을 직접 설명 및 구현을 해 봄으로써 제 자신의
실력을 다지기 위한 글 입니다. 물론 정확한 이론. 용어도 아님을 밝힙니다.
이 글을 직.간접적으로 사용함으로써 발생되는 모든 불이익을 책임지지 않습니다.

문의점, 오류, 잘못된 용어들은 저의 홈페이지 Work 게시판을 이용하여 주시고
이상의 사항에 대하여는 최대한 덧글 ( 코멘트 ) 를 이용해 주십시오.

본 글은 저의 홈페이지인 http://ggaman.com 과
싸이월드의 (JPSC) JAVA program study club 에서 보실수 있습니다.

homepage : http://ggaman.com e-mail n MSN : chan at ggaman.com

20031211 - Chan
==========================================================================


안녕하세요.
찬 입니다.

정말 오랜만이죠 ^^

컴터가 좋아 지니 더 공부를 안하는것 같아서 참 걱정입니다. -_-;;;

오늘 다루어 볼 내용은 정말로 제가 다루고 싶었던 Non-Blocking I/O 입니다.

아마도 이 내용을 다 다루고 나면 더 이상의 NIO 정리를 없을것 같습니다.
( 필요할때 나머지를 정리하던지.. 할것 같습니다. )

제 목표가 우선 JAVA로 채팅 서버를 만드는것인데.
이 Non-Blocking I/O로 만들려고 한것 입니다.
( 그래서 1.4 튜토리얼도 구입했구요 ^^ )

인터넷에 있는 Non-Blocking I/O에 대한 강좌나 글들은
초보가 알아 먹기 힘들게 되어 있더군요.
그래서 전 제가 알아 먹기 쉽게. 글을 적도록 하겠습니다.


Java 1.4 이전에는 이 Non-Blocking I/O (이하 NBIO : 정식명칭이 아님)가 지원되지 않았습니다.
1.4에서 지원이 되는것이지요.
C나 C++에서는 이미 지원이 되고 있었죠.


첫번째 글에도 적었지만 NBIO에 대하여 대충 아는대로 설명 드리겠습니다.
자바 1.3이하에서는 입출력을 위해서 항상 block 되어 있어야 했습니다.
( 비록 다른 방법을 이용하여 block를 피할수는 있었지만.. )
여기서 block가 되어 버리면 다른 작업을 하지 못하고 계속 대기하고 있어야 할것입니다.
정말로.. 낭비가 아닐수 없죠.


하지만 1.4에서는 I/O를 할때 block 이 되지 않게 할수 있습니다.
즉 i/o를 할때 그 결과를 기다리지 않습니다. 바로 다음 작업으로 넘어가는 거죠.
그럼 잘되었는지 안되었는지는 어떻게 알수 있냐구요?
그건 천천히 말씀 드리겠습니다.


보통 자바( 또는 다른언어로 ) 서버를 만들때에 나오는 예제나 글들은
클라이언트 하나 하나 마다 모두 쓰레드를 만들어서 돌립니다.
즉, 한 채팅 사이트에 20명이 붙어서 대화를 하면 쓰레드가 총 20개가 생기는것입니다.
이것은 엄청난 낭비라고 볼수 있습니다.
쓰레드가 20개라는 말은 쓰레드에 사용되는 각종 private 변수들이 모두 따로 존재하는것이라서
메모리 관리 측면에서도 문제가 됩니다. 그리고 자세히는 모르지만 스레드를 많이 쓰면
부하를 많이 받는다는 말도 들어 본것 같습니다. ;;
( 설명하는 제가 정확하게 모르니;; 이해를 부탁드립니다. )


자~ 설명은 이정도로 대충 끝내고 다름으로 넘어 가겠습니다.


이번에 설명 드릴것은 Polling (폴링) 이라고 불리는 기법입니다.
서버를 만들때 다음과 같은 기법을 사용하여 작성하는 것입니다.

채팅서버를 만든다고 하면 여러개의 클라이언트 커넥션이 존재할것입니다.
처음에 1번의 커넥션을 조사해서 IO가 있으면 처리하고. 없다면
2번의 커넥션을 조사하고... 다시 3번을 조사하고.
맨 마지막 커넥션까지 가면 다시 1번커넥션을 검사..
이런 방식으로 처리하는 방식입니다.
( 뭐.. 돌아가는 방식은 그렇게 어렵지 않군요 .. )
( 책에서 확인하니 커넥션이 한번 루프를 돌때마다 0.1초 정도 쉬어주어야 한다는군요 ;; )


하지만 이 방식은 약간의 문제가 있습니다.
만약 1번의 커넥션이 처리가 된지... 나머지것들은 I/O가 없고 다시 1번에서 IO가 발생하면
끝까지 루프를 돈 뒤에야 1번 커넥션을 다시 처리할수 있다는 것입니다.
그렇다면 전혀 입출력이 없는 2~n 번까지의 커넥션을 거치기 때문에
1번의 처리는 느려지는것은 당연지사입니다.



이것을 보완하는 방법이 이제 설명드릴 select라는 방식입니다.
( 저도 말만 들어 봤지.. 이번에 처음 공부하는 부분입니다 ^^ )

그럼 select를 공부해야 하는데. 우선 이부분은 잠시 후에 다시 하겠습니다.



우리가 오늘 배울것은 Non-Blocking I/O에 대해서 배울것입니다.
이것은 java 1.4 부터 나오는 nio에 속해 있죠.
두번째 정리에서 공부했던게 생각나시나요?
nio에서는 입출력을 위해 Stream을 바로 쓰지 않고 Channel을 만들어서 쓴다는것.
맞습니다. 우리는 Non-Blocking I/O에서도 Channel을 만들어서 사용할것입니다.

그럼. 소스를 보면서 이야기 해 보겠습니다.

=======================================================================
1: ServerSocketChannel ssc = ServerSocketChannel.open();
2: ssc.configureBlocking( false );
3: ServerSocket ss = ssc.socket();
4: InetSocktAddress isa = new InetSocketAddress( PORT );
5: ss.bind( isa );
=======================================================================

소스를 보시면 우선 1 서버소켓채널을 생성합니다.
( 이 역시 ByteBuffer과 마찬가지로 생성자가 없이 생성을 시키는것입니다. )
그리고 2 서버소켓채널을 NBIO 모드로 설정합니다.
그런뒤에 서버소켓채널에서 서버소켓을 하나 얻습니다.
그리고 port를 정해서 서버소켓에 바인드 시킵니다.
( 여기서 바인드 시킨다는 말은 match 시킨다는 뜻으로 해석하면 될것 같습니다. )


뭐 순서만 본다면 그렇게 어렵지는 않군요..





그럼 이제 다시 select에 대하여 공부해 보죠..
select는 이것 하나로 입출력을 감지 할수 있습니다. 감지 할수 있다는 말이 참 좋은 말이죠.
예전의 폴링 같은 경우는 하나씩 검사를 해서 처리를 하곤했는데.
데이터가 입출력되는지 감지를 하기 때문에 일일이 검사할 필요가 없어지게 된것입니다.

자 그림 select를 생성해 보겠습니다.
========================================================================
import java.nio.channels.*;
Selector selector = Selector.open();
========================================================================
생성하는것 역시 간단하군요. 마찬가지로 여기도 Selector 생성자를 직접 사용하지 않는군요.




그럼 지금 만든 selector를 서버소켓채널에 등록을 시켜야 할것입니다.
========================================================================
ssc.register( selector, SelectionKey.OP_ACCEPT );
========================================================================
이 줄은 ssc에 selector을 등록시켜서 ssc를 감지하겠다는 말입니다.
맨 마지막에 있는 SelectionKey.OP_ACCEPT는 이것을 감지하겠다는 말입니다.
즉 ACCEPT를 감지하겠다는 말이 됩니다.

그럼 어떤 종류를 감지 할수 있는지 대충 적어 보겠습니다.

OP_READ    : 채널에 데이터가 들어와서 읽을수 있는 상태가 되면 발생.
OP_WRITE   : 채널이 쓰기 가능한 상태가 되면 발생.
OP_CONNECT : 리모트 서버에 완벽하게 연결되었을때 발생.
OP_ACCEPT  : 서버소켓에 클라이언트가 접속시 발생.

흠. 어렵지는 않군요.
채널은 입출력이 다 되니 당연히 READ와 WRITE 둘다 있어야 할것입니다.
그리고 클라이언트입장에서 서버에 접속했을때(CONNECT)와
서버입장에서 클라이언트가 접속했을때(ACCEPT) 이렇게 이벤트(?) 가 발생되는군요.


여기서 하나 짚고 넘어가야 할 부분이 있습니다.
저기 네가지의 경우는 모든 소켓관련 부분에서 쓸수 있는가 입니다.
당연히 정답은 "아니오"가 됩니다.

OP_READ와 OP_WRITE는 당연히 Socket쪽에서만 쓸수 있고
OP_CONNECT와 OP_ACCEPT는 당연히 ServerSocket쪽에서만 쓸수 있습니다.
( 데이터를 입출력할때는 Socket을 사용하여 입출력합니다. )
( ServerSokcet는 접속 부분만 관련되어 있죠 )



만약 하나의 서버소켓채널에서 여러가지의 이벤트를 받고 싶다면 다음과 같이 사용하면 됩니다.
========================================================================
ssc.register( selector, SelectionKey.OP_ACCEPT | SelectionKey.OP_READ );
========================================================================
저렇게 | ( bit연산 OR )을 사용해서 여러개의 이벤트를 받아 들일수도 있습니다.


하지만 여기서 문제가 또 하나 발생합니다.
이벤트가 일어 났다면 둘중(ACCEPT와 READ)중 어느 것이 발생했는지 알아야 할것입니다.
register() 메소드는 리턴값이 SelectionKey 객체를 반환하게 됩니다.

그것을 이용해서 실제도 일어난 이벤트는 다음과 같은 코드로 검출이 가능합니다.
===========================================================================================
SelectionKey sk = ssc.register( selector, SelectionKey.OP_ACCEPT | SelectionKey.OP_READ );
int op = sk.readOps();
if ( ( op & SelectionKey.OP_ACCEPT ) == SelectionKey.OP_ACCEPT ) { // SomeCode };
===========================================================================================


오늘은 이만 끝내도록 하죠.
시간도 늦었고.. ( 새벽 4:30분 -_-;; 백수는 할일이 없음 ;; )

다음에는 이제 처리하는 방법에 대하여 대략적인 설명과 코드를 적도록 하겠습니다.
그 다음시간에는 아마도 채팅 서버를 만들수 있을것 같네요.
물론 프로토콜 같은게 정확하게 정해진게 없으니.. 대충이라도 하나 만들어 보겠습니다.
반응형