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

#025_Window_Network_비동기 소켓 입출력 모델_IOCP모델

shovelman 2015. 10. 14. 21:54



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


이번시간에는 지금까지 배워왔던 모델들의 장단점을 총 종합해서 만들었다고 해도 과언이 아닌, 

마지막 모델 IOCP 모델에 대해서 알아보려고 합니다.

I/O Completion Port 모델이라고도 부를 수 있습니다.


Window 서버로 만들어지는 경우가 많이 없지만,

만약 Window 계열로 서버가 만들어 지는 경우의 거의 90% 이상은 

IOCP 모델로 만들어진다고 생각하셔도 된다고 합니다.


기존에 Overlapped 모델과는 차이점이 뭘까요?

Overlapped 모델 중 Completion Routine를 사용할 경우에는 

Thread 하나를 가지고 작업을 진행했었습니다.

하지만, 이 작업을 진행하는 쓰레드를 여러대 생성할 수 있을 경우에 

그 만큼 동시 작업 처리에 효율성이 생기지요...

IOCP 모델은 여러 대의 쓰레드를 사용하기 때문에, 동시 작업 처리에 효율적인 모델입니다.

이 여러대의 쓰레드는 적정한 개수만큼 띄울 수 있습니다.

보다 자세하게 말씀드리자면, CPU의 개수에 비례하여 작업을 담당할 쓰레드를 생성하는 것입니다.


또한, Overlapped 모델은 쓰레드를 알림이 가능한 상태로 만들어서 

Completion Routine을 호출하도록 구현해야 됬습니다.

즉, 상태 값을 제어하는 것이기 때문에 

내가 원하는 시점에 데이터를 가져오려면 그 상태를 만들어줘야 했었습니다.


IOCP는 IO 완료 포트 입니다..

포트는 항구, 정착, 정박한곳... 같은 의미를 가지고 있지요...

즉, 해당 모델에서 포트라는 용어는 완료가 머무르는 곳, 완료가 정착한 곳이라고 볼 수 있겠지요...


IOCP는 커널 객체 입니다.

따라서 IOCP라는 커널 객체를 만들면 기존의 모델 방식들과 차이를 두는 것이지요...

IOCP 객체를 만들어 내면 이 안에는 자체적으로 IOCP 의 큐가 만들어집니다.

그리고 그 곳에 완료 정보들이 발생하게 되는 것입니다.

그렇다면, 원하는 시점에 완료 정보를 가져갈 수 있게 됩니다.

쓰레드가 여러대이더라도 그냥 완료 정보만 가져가면 될 뿐이지요...


IOCP 모델에서도 똑같이 main thread는 accept() 역할

즉, 클라이언트와 접속을 담당하는 역할만 수행합니다.

그리고, IOCP 모델에서는 IOCP 객체가 반드시 있어야 되기 때문에 IOCP 객체를 만들어야합니다.


해당 모델의 대표적인 함수 중 하나는 바로 CreateIoCompletionPort() 함수입니다.



해당 함수는 '생성'과 '연결'에 대한 특징이 있습니다.

우선적으로 IOCP 객체를 만들어야겠지요...



객체를 생성할 때에는 -1(INVALID_HANDLE_VALUE), NULL 을 인자로 포함시키면 생성을 나타냅니다.



그리고 클라이언트 소켓 번호와 IOCP 번호를 인자로 넘길 시 연결을 해주지요...

소켓의 완료보고... 즉, 비동기 입출력을 완료했다면 IOCP(hcp)에 알리라는 뜻입니다...

세번째 인자는 키 값입니다.

그리고 마지막 인자는 다시 언급하겠지만, 깨우고자하는 쓰레드의 개수를 뜻합니다.

0이면 최대 개수이지요...


그리고, IOCP의 큐에 완료 보고가 들어오는지 감시를 해야하는 쓰레드가 필요합니다.

이때 IOCP의 큐를 감시하는 함수는 바로 GetQueuedCompletionStatus() 함수입니다.


 

해당 함수는 블록킹 함수입니다.

첫번째 인수로는 IOCP의 내용물을 읽겠다는 내용,

두번째 인수로는 몇 바이트에 대한 정보를 읽었다는 내용,

세번째 인수로는 IOCP 연결을 구분하는 키 값이 들어갑니다.

IOCP 키 값은 연결을 구분하는 키 값으로, 이 값은 우리가 할당할 수 있습니다.


키 값은 연결을 구분하기 위해서 사용하는 것입니다.

구분자이지요... 즉, 같으면 키 값을 사용할 의미가 없습니다.

키 값에는 uniqe하고 꼭 있어야한다는 의미가 들어가 있습니다.

그래서 여기서는 소켓 번호를 Key 값을 씁니다.

연결이 지어져있는 소켓 번호를 찾을 수 있는 장점이 있습니다.

아무튼... 소켓 번호를 IOCP와 소켓의 연결에 대한 키 값으로 쓸 수 있다는 사실을 기억하시길 바랍니다.


네번째 인수는 우리가 전에 배운 Completion Routine에서 

Overlapped 구조체의 주소를 가져오듯이,

비동기 입출력이 완료 시 그 완료된 Overlapped 구조체의 주소가 들어갑니다.

마지막 다섯번째 인수는 TimeOut을 설정합니다. 즉, 대기 시간을 지정하는 것입니다.



자... 클라이언트 소켓이 만들어지게 되면

가장 먼저 클라이언트 소켓이 IOCP 객체에 완료 보고를 하도록 만들어야합니다.

즉, 소켓과 IOCP 객체의 연결이라고 할 수 있습니다.


아까 CreateCompletionPort() 함수를 사용해서 IOCP 객체를 생성했는데,

또 해당 함수를 통해서 IOCP 객체와 연결을 시키는 것입니다.

통신 소켓이 만들어지면 연결을 하는 작업을 해당 함수로 한다는 것입니다.


WSARecv(), WSASend() 와 같은 함수들이 작업을 끝내면

작업을 담당하던 쓰레드에 있는 GetQueuedCompletionStatus() 함수는 return 하게 됩니다.

뭐... GetQueuedCompletionStatus() 함수는 블록킹 함수이기 때문에

어떤 쓰레드가 깨어날지... 누가 정보를 가져갈지는 알 수 없죠...


아무튼... 이 return 한 값에는 많은 정보들이 들어가 있는데,

어떤 소켓이 완료 보고를 했는지에 대한 정보도 들어가 있습니다.

그 정보를 통해 작업을 하면 되는 것입니다.



해당 코드가 반환 되면, 완료 상태의 큐 내용물을 읽었다는 뜻으로,

IOCP의 내용물을 읽고 해당 정보들이 저장되게 됩니다.


전 모델들과 달리 Main Thread와 작업을 담당할 쓰레드들만 있으면 되니

굉장히 간단해집니다.


또한 여러 쓰레드들이 인과 관계가 없으니 독립적으로 데이터를 처리할 수 있습니다.

즉, 쓰레드에 의해 동시 다발적으로 작업이 수행된다는 것입니다.


자... 아무런 이벤트가 발생하지 않을 경우 모두 blocking 상태에 있을 것입니다.

그런데 만약 이벤트가 발생했을 경우에 대기하고 있던 쓰레드들은 깨어날 것입니다.

여기에는 장단점이 있습니다.

IOCP 모델은 굉장히 많은 소켓과의 동시적인 입출력을 위해 만들어놓은 모델입니다.

따라서 많은 클라이언트들과의 빈번한 입출력을 처리할 수 있지요...

만약, 어쩌다 한번씩 데이터가 날라오는 경우가 있다면

이 때에는 많은 쓰레드들이 깨어날 필요가 없습니다. 오히려 비 효율적이기 때문입니다.

굳이 많은 쓰레드들이 가끔 한번온 데이터를 처리하기 위해서 Ready 상태가 되야하니깐 말입니다...

그래서 IOCP 모델에서는 몇 대 까지의 쓰레드를 깨울지에 대한 결정을 할 수 있습니다.


이렇게 세세한 정보까지도 이 IOCP 객체는 모두 설정할 수 있습니다.

효율적으로 작업을 처리할 수 있다 이겁니다.

Overlapped 모델에서는 이런 기능이 없었지요...


다음시간에 뵙겠습니다.

이상 삽잡이였습니다!