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

#023_Window_Network_비동기 소켓 입출력 모델_Overlapped 모델 (1)

shovelman 2015. 10. 13. 20:00


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


이번 시간에는 

비동기 소켓 입출력 모델 중 Overlapped 모델 방식에 대해서 알아보려고 합니다.

선 동작을 수행하는 함수들의 종류가 있는 입출력 모델 방식말입니다.

이 '선 동작을 수행하는 함수'들은 모두 비동기 함수라고 부릅니다.

곧, MS에서는 overlapped 함수라고 부른다 이겁니다.


이 모델 방식에서는 overlapped 구조체를 사용합니다.


 

첫번째, 두번째 인자는 OS가 사용합니다.

두번째 인수는 OS가 얼마나 Read하고 Write했는지에 대한 길이를 저장한다고 생각하시면 됩니다.

그리고 이 둘은 우선 사용전 초기화를 해줘야합니다.


어플리케이션에서 사용하는 세번째 네번째 인수를 알아봅시다.

Offset은 데이터 입출력을 시작하고자하는 위치를 의미합니다.

일반적으로 처음부터 입출력을 시작하니까 0 이겠지요..

OffsetHigh는 나중에 버전업이 됬을 때 사용하기 위한...

미래를 위해 준비해둔 것이라고 생각하시면 됩니다.

대부분 high가 붙은 놈들은 모두 다 미래를 위해 준비했다고 생각하시면 되죠...


마지막으로 hEvent... 커널 오브젝트 이벤트에 대한 핸들을 저장합니다.


참고로 I/O 함수로 제공되는 것들은 모두 다 비동기 함수로 제공해줍니다.


Overlapped 모델에서는 'non blocking' 소켓을 사용하지 않습니다.

blocking 소켓을 사용한다는 소리입니다.


blocking 소켓을 사용하더라도 

WSARecv, WSASend와 같은 비동기 함수들은 동작을 수행하기 위한 비동기 함수이기에,

OS에게 명령만을 내리면 되기 때문에 blocking 되지 않습니다.


비동기 함수의 종류는 여러가지이지만,

대표적인 비동기 함수는 WSARecv와 WSASend 함수입니다.



첫번째 인수는 보내고자하는 소켓 번호입니다.

두번째 인수는 보내고자하는 버퍼의 시작 주소입니다. 

세번째 인수는 몇개의 버퍼에 내용물을 보낼 것인지에 대한 설정 값입니다.

즉, 버퍼의 개수인데 일반적으로 1을 넣습니다.

네번째 인수는 실제 보낸 바이트 수입니다.

다섯번째 인수는 I/O Flag입니다. OOB 같은 것들 말입니다... 안쓰니까 0으로 처리합니다.

여섯번째 인수는 Overlapped 구조체의 주소를 말합니다.


Overlapped 구조체는 어플리케이션과 OS의 다리를 연결해주는 역할을 합니다.

서로 간의 정보를 공유하는 역할을 한다 이거죠...


마지막 인수는 아직은 사용하지 않으니 NULL로 처리합니다.



WSARecv() 함수는 반대겠지요...

이 함수는 예제를 보며 설명을 드리겠습니다.



이 함수는 받는 역할을 하지요...

소켓(ptr->sock)에 있는 버퍼(&ptr->wsabuf)에다가 실제 바이트 수(&recvbytes)를 한번(1)에 받아라.

Overlapped 주소(&ptr->overlapped)에 말입니다.


자... WSASend() 함수는 비동기 함수입니다.

호출을 하자마자 OS가 내부적으로 쓰레드를 씌워서 일을 수행해줍니다.



그러면 OS와 어플리케이션간에 정보 교류가 필요할 것입니다.

예를 들어 어디서 부터 시작했는지... 얼마만큼 기록을 하고 있는지 등등 말입니다...

즉, 정보 유지를 해야하는데, 

바로 여섯번째 인자인 'overlapped 구조체'를 통해 정보 유지를 하는 것입니다.


따라서 비동기 입출력을 하기 위해서는 다리 역할을 하는 놈이 필요한데,

이를 Overlapped 구조체를 통해 사용한다는 것입니다.

즉, 어플리케이션에서 비동기 입출력을 시작해달라고 요청을 하면, 

OS가 기능(작업)을 수행하는데

이 작업에 대한 내용을 서로 공유하기 위한 다리가 Overlapped 구조체라는 소리죠...


OS가 쓰는 내용들은 Overlapped 구조체에 internal에 기록을 합니다.

이 변수는 공개가 되있지 않습니다...

hEvent를 사용하게 된다면 보고를 받는 이벤트를 사용하게 되는 것입니다.


OS 작업이 완료되면 overlapped 모델에서 중요한 완료 보고를 해야합니다.

이를 hEvent를 신호 상태로 만듬으로 보고할 수 있다는 소리입니다...


이게 바로 Overlapped 모델 방식 중 하나인

Event를 사용하는 방법의 개념입니다.


WSARecv() 함수도 여전히 Overlapped 구조체가 있어야하고,

OS가 작업을 시작하려고 해도 Overlapped 구조체가 연결 고리를 해줘야합니다.

정보를 전달해주기도 하고, 작업 중인 기록을 기록하기도 하고...

또한, 다 완료가 되면 hEvent 핸들을 신호상태로 만들어

어플리케이션에 완료 보고를 하기도 하고...

이 이벤트 핸들을 신호 상태로 만들어 완료했다는 것을 나타낸다 이겁니다.



자... 소켓의 데이터를 보내고 받는데 데이터를 저장하고 보내기 위해서는 WSABUF 구조체를 사용합니다.



해당 구조체 안에는 길이와 buf를 가지고 있습니다.

즉, 보내고자 하는 메모리의 시작 위치는 buf에

보내고자하는 사이즈는 len에다가 표시를 하는 것입니다.

즉, WSABUF의 buf에는 보내고자하는 시작주소! len에는 보내고자하는 바이트 수!



지금 시간에는 완료 보고를 받는 방법 중 

'이벤트'를 사용하는 overlapped 모델 방식에 대해서 알아볼 것입니다.


어플리케이션으로 따지면,

완료 보고를 받고자 하는 배열과, 소켓에 대한 정보를 다는 배열 두 가지가 필요합니다.


그리고 더미 이벤트를 하나 만듭니다...

더미란, 

'사람을 대용하는', '진짜 사람처럼 만들어 놓은', '임시로 사용되는' 이라는 의미를 가지고 있습니다.


더미 이벤트를 만든 다음에는 

완료 보고를 받고자하는 배열의 0번째 index에다가 더미 이벤트를 집어넣는 것입니다.

또한, 완료 보고를 하고 일을 해주는 쓰레드도 필요합니다.


더미 이벤트를 만드는 이유는 

이벤트가 처음 실행 됬을 때 대기를 하도록 만들어주는 역할이 필요하기 때문입니다.

따라서 완료 보고를 담당하는 쓰레드에 WSAWaitForMultipleEvents() 함수를 사용해서 

신호상태로 대기할 수 있도록 만들어줘야합니다.


 

왜냐하면, 완료 보고를 해주는 쓰레드가 처음 실행시 완료보고를 할 것이 없기 때문에 

대기를 해줘야하는데 이를 더미 이벤트를 하나 생성해서 대기 상태로 만드는 것입니다.


그러면 쓰레드 하나를 통해 완료 보고를 할 수 있는 기능을 만들어 놨다면,

Main 함수를 처리하는 Primary Thread는 Accept() 함수에서 대기를 하며

클라이언트 접속 여부를 확인해줘야합니다.


클라이언트가 접속을 할 경우 Overlapped 구조체를 초기화 해주고,

커널 오브젝트 이벤트를 하나 생성하여 구조체에 저장을 해줍니다.


자... 이 처럼 등록을 해준다면,

만약 작업이 완료시 이벤트를 신호상태로 변경하여 알려줄텐데,

이벤트의 핸들이 저장되어있는 소켓의 구조체가 알수 있게 됩니다.


또한 아까 생성한 더미는 만약 클라이언트 접속으로 인한 이벤트가 생성됬을 때

이벤트 핸들의 개수를 갱신해주는 역할을 합니다.


이벤트 핸들은 OS가 작업 완료를 보고할 때 신호상태로 알려주기 위한 용도로 사용이 되는데,

만약 클라이언트와 새로운 연결이 됬다면, 새로운 이벤트가 하나 생성될 것입니다.

그런데 이미 WSAWaitForMultipleEvents() 함수에서 대기하고 있던 쓰레드는

새로 생성된 이벤트의 유무를 모르고 있는 문제가 있기 때문에 

이를 더미 이벤트를 신호상태로 변경시켜줘서 갱신시켜주는 것입니다.


정리하자면,

더미 이벤트는 새로운 이벤트들이 만들어 졌을 때 다시 대기할 수 있도록 만들어준다 이겁니다.

즉, 최초에 더미 이벤트는 완료 보고를 받기 위한 대기상태로 만들어주는 역할을 해주고

두번째로 새로운 클라이언트가 접속할 때마다 WSAWaitForMultipleEvents() 함수를 깨워서 

들어온 클라이언트 개수만큼 다시 대기할 수 있도록 만들어준다 이겁니다.



예를 들어보도록 하겠습니다.

만약, 82번이라는 소켓의 recv 버퍼에 'abcd'라는 데이터가 OS로 들어왔다고 가정해보겠습니다.

OS는 데이터가 82번 소켓이 저장되어 있는 구조체에 

데이터를 끌어 올려주고 OS 수준의 TCP 버퍼를 비웁니다.

그리고 OS는 완료 보고를 해주지요... 


데이터가 들어오면 OS의 역할에 의해서 이미 어플리케이션 버퍼에 들어가 있고

다 데이터가 들어왔다는 완료 보고를 받으면 되는데,

Overlapped 구조체에 등록되있는 이벤트에서 신호상태가 되어 알려주게 됩니다.

그리고 OS에 있는 해당 쓰레드는 사라지게 되지요...



해당 구조체는 Overlapped에 대한 결과치를 가져와주는 함수입니다.

이를 통해 종료도 확인이 가능합니다.

0을 반환하면 closesocket()을 통한 종료, -1이면 강제 종료말입니다...

그 외에는 받은 바이트 수를 반환하겠지요...



즉, 완료 보고가 떨어졌을 대 완료가 진행되고 있는 상황을 Overlapped 구조체에 담습니다.


Overlapped 모델 방식은 행동을 먼저합니다.

명령을 내리고 완료 보고가 올 때까지 대기를 하는 것입니다.


하지만 주의해야할 사항은

데이터가 다 받아졌는지 보장할 수 없기 때문에 사용자가 보장을 해줘야합니다.


마지막으로 종료시에는 

socket 정보가 들어있는 구조체의 메모리를 delete 시키고, 이벤트도 delete 시키고,

종료되있는 소켓의 정보가 보관되어 있는 배열도 사이즈를 감소시키고 해줘야겠군요...

그리고 다시 서버는 작업처리르 위해 대기 상태에 들어가야할 것이구요...


이상으로 Overlapped 방식 중 Event 방식을 사용한 모델에 대해서 알아봤습니다.


이상 삽잡이였습니다!