삽질의 현장/- 네트워크 프로그래밍

#019_Window_Network_소켓 입출력 모델_Select 모델

shovelman 2015. 10. 10. 13:30


안녕하세요 삽잡이입니다.


이번 시간에는 네트워크 I/O 모델 방식 중 Select 모델 방식에 대해서 알아보겠습니다.


linux 같은 OS들에서도 모든 시그널이 다 select 모델 방식으로 되어있다고하네요

하지만 우리는 Windows의 select 모델을 알아본다는 점~!

자 그러면 지금부터 개념을 알차게 알아가보도록 하겠습니다.


Select 모델 방식의 핵심은 말 그대로 Select() 함수입니다.



이런 모델을 쓰는 이유는,

'넌 블로킹 소켓을 사용해서 얻는 이점' + 

'이런 모델 방식을 사용해서 넌 블로킹 소켓이 아주 유연하게 동작하게 하는 것' 

이와 같이 시너지를 내기 위함이 핵심이기 때문입니다...


select 모델에서는 쓰레드를 단 하나 가지고

즉, Main 함수를 움직이는 Primary 쓰레드 하나만을 가지고 

다중 클라이언트 통신이 가능하게 됩니다.

쓰레드가 하나인데도 다중처리가 가능하고 성능도 많이 안잡아 먹고 말입니다...

대단하지요...


대기 소켓은 이전 시간에서 언급했던 iotlcontrol()함수를 사용해 넌 블로킹 소켓으로 바꿉니다.



아까도 언급했지만,

Select 모델링 방식의 핵심은 select() 함수인데, 이 함수도 입출력을 판단하는 함수입니다.

이때 함수들은 모두 다 넌 블록킹 함수로 동작하지만, 오직 Select()만 블로킹 상태로 동작하게 됩니다.

즉, 오직 블로킹 가능 함수는 Select()라고 말하는 것입니다.


select 모델 방식은 flag(깃발, 신호) 방식을 사용합니다.

깃발을 올리면 깃발에 맞도록 임무를 수행하는 것이지요...

이 신호를 반단하는 놈이 select() 함수이구요...


아하... 쓰레드는 그럼 항상 Select() 함수에 블록킹 되어있겠군요...

select()는 클라이언트가 접속했다는 사실을 알려줘야한다는 것입니다.

또한 메시지가 왔다면, 혹은 상대방의 recv 버퍼가 비워졌다면 역시 사실을 알려줘야겠지요..

따라서 이에 맞는 I/O 작업을 수행해야하는 것이구요...


우리의 핵심적인 I/O 작업은 accept, send, recv 작업입니다.

우리가 네트워크 프로그램을 하면서 핵심적인 I/O 작업이란 말입니다...

그렇다면 select 함수의 핵심을 알았으니 해당 함수의 기능을 알아야겠지요...



우선 select() 함수의 인수는 다섯개입니다.

첫번째 인수는 리눅스에서만 사용합니다. 

윈도우즈는 핸들이 순차적으로 만들어지지 않기 때문에 사용하지 않지요...

그래서 못쓰고 다른 방식을 쓰는데... 리눅스 계열에서는 순차적으로 만들어지죠...

아무튼... 어찌됬건 이건 Windows에서는 안씁니다...


그리고 2,3,4 번째 인수는 깃발과 같은 역할을 하는 것입니다.

read set, write set, exception set..

이들은 모두 읽기 집합, 쓰기 집합, 예회 집합을 말합니다.


우선 두번째 인자는,

읽는 것과 관련되는 네트워크 상황이 발생시 두 번째 인자로 상황을 알려주는 것입니다.

세번째 인자는 쓰는 것과 관련되는 상황이 발생시 알려주는것이구,

네 번째 인짜는 예외 set이라고 OOB (Out Of Bend) 자리를 뜻합니다.

예전에는 직접 긴급 메시지인지 판단하고 그 긴급 메시지에 

해당하는 자리(비트셋)를 읽었어야했는데, 결과적으로 코드가 달라지고 복잡해지는 결과글 초래했습니다.

그래서 네번째 인자를 새로 만들어놓은 것이죠...

하지만 쓰지 않으니까 NULL로 만들어 초기화합니다...

아무튼... 네번째 인자는 긴급 메시지를 보내거나 받을 때 알려주는 인자입니다.


마지막 인수는 TimeOut을 가리킵니다.

블록 함수는 모두 유효 시간을 쓰는 인자가 있습니다.

Select() 함수 또한 블록 함수이니까 이 인자가 있습니다.



위의 코드에서는 rset이나 wset은 FD_SET이라는 구조체로 만들어져 있습니다.

역사적인 문제로 File Discript의 약자를 가지고 있는데... 좀 안맞아 보이지요...



아무튼... rset은 읽기에 관련된 정보를 알고 싶은 소켓 집합이 담기도록,

wset에는 쓰기와 관련된 정보를 알고 싶은 소켓 집합이 담기도록 하겠습니다.


그렇다면, Select() 함수를 호출 하기 전에 뭔짓을 해야하냐면,

읽기와 관련된 네트워크 정보를 알고 싶다면, rset에 소켓 정보가 담기도록,

쓰기와 관련된 네트워크 정황을 알고 싶다면, wset에 소켓 정보가 담기도록 해야겠지요...


대기 소켓은 무조건 rset에 담겨야합니다...

클라이언트가 들어오면 정보를 읽어줘야하기 때문입니다.

어찌됬건 대기 소켓은 rset에 무조건 담긴다는 사실을 기억하시길 바랍니다.


우리가 프로그램을 실행시 대기 소켓의 핸들이 rset에 담기도록 했습니다.


 

리눅스와 호환가능하도록 해야하니 메크로 함수를 써서 사용하자고 약속이 되어있다고 합니다.

rset과 wset은 소켓을 담든 구조체인데 

위 코드는 우선, 이 소켓을 담는 구조체를 싹 비우고,

FD_SET을 통해 rset에 listen_sock(대기 소켓)을 담는 것입니다.


 


네트워크에서 어떠한 상황들도 발생하지 않는다면,

이 쓰레드는 Select에서 blocking 상태로 대기하고 있을 것입니다.

지금은 클라이언트가 접속하는 상황밖에 발생할 이벤트가 없지요.


따라서 미리 알려줍니다.

선 flag 후 행동 방식이라고 해서, 네트워크 상황을 미리 알려주는 것입니다.

그렇게 되면 accept() 행동을 하는 것이지요...

데이터가 들어오면 recv() 행동을 하도록, 보내면 send() 행동을 하도록 말입니다...

즉, 이렇게 행동을 하도록 유도(?)를 하기 때문에 '선'신호 '후'행동을 한다..이겁니다...


그렇다면... 이벤트가 없는 상태에서는 고요하겠지만, 

클라이언트의 접속요청이나, 그 외 이벤트가 발생하면 Select() 함수가 감지하고 return 하겠죠...


자... 지금부터는 대기 소켓만이 소켓 셋에 저장되어있는 상황이 아닌,

여러 소켓들이 각각 rset, wset에 들어가 있다고 가정을 해보고 설명을 시작해보도록 하겠습니다...

음... 설명이 어려울 수 있으나... 고고...




select() 함수에서 block 상태에 있다가 요청이 들어오면, 

select()함수는 몇대에서 요청이 들어왔는지 감지를 하고 그 개수를 return합니다.

그렇다면 rset과 wset의 상황이 달라집니다...

네트워크 상황이 발생한 소켓들만 rset과 wset에 남겨지게 되는 것입니다...

즉, Select가 반환을 하면 flag 신호가 온 대기 소켓이나 통신 소켓의 일을 처리한다 이겁니다.


OS 레벨에서 개념으로 표현하자면,

대기 소켓은 클라이언트 접속 요청이 들어오면 flag를 들어올리는 것이구,

통신 소켓은 자신의 recv 버퍼에 클라이언트로부터의 데이터가 담겨지면 flag를 들어올리는 것입니다.


그렇다면 Select 함수가 return 되는 순간 flag를 든 소켓만이 각 소켓 셋에 남아있게 됩니다.

그리고 각각 소켓 셋으로 부터 flag 표시를 한 소켓들을 checking하는 것입니다.

그 다음에 각 소켓마다 일 처리를 시켜주면 됩니다... 

물론, 쓰레드를 사용하지 않으니 순차적으로 일 처리를 해주겠지요...


지금까지의 말을 통해 Select 모델 방식이 어떻게 동작되는지 정리해보겠습니다.

rset, wset과 같이 소켓 셋을 만들어 놓고 모두 초기화를 진행합니다.

그 다음 호출할 함수의 종류에 따라 소켓들을 넣어둡니다.

그리고 Select() 에서는 소켓셋에 집어 넣은 소켓들 중에 적어도 하나 이상의 소켓에서

이벤트 신호가 발생한다면, 입출력이 가능한 상태의 소켓들만 소켓 셋에 남고 나머지는 제거가 되는 것이지요...


Select 모델에서는 소켓 셋을 편리하게 다루고자 소켓 셋을 조작할 수 있는 메크로 함수를 제공해줍니다.

출처는 MSDN입니다...


 


이상 삽잡이였습니다!