내게맞는 Winsock 입출력Model 선택하기 #5 - Overlapped I/O Model (2024)

The Overlapped Model
이 모델은 지금까지 얘기했던 모델보다 좋은 성능을 제공한다. 이 모델의 기본 설계는 오버랩데이타구조체를 사용하여 하나 또는 그 이상의 I/O 요청을 한번에 어플리케이션이 보내도록 한다. 이 모델은 윈도우즈 오버랩 I/O 매커니즘을 (ReadFile/WriteFile) 기본으로 한다. 윈도우CE를 제외한 모든 윈도우 플랫폼에서 가능하다.
윈속2 버전이 나왔지만, 윈도우NT,윈도우2000에서는 여전히 ReadFile,WriteFile함수를 사용할 수 있다. 그러나 다른 플랫폼과 호환을 위해서는 WSARecv,WSASend 함수를 사용하도록 하자.

오버랩I/O함수들

WSASend
WSASendTo
WSARecv
WSARecvFrom
WSAIoctl
WSARecvMsg
AcceptEx
ConnectEx
TransmitFile
TransmitPackets
DisconnectEx
WSANSPIoctl

오버랩I/O를 사용하기 위해서는 각 함수는 오버랩구조체를 변수로써 가져가야 한다. 오버랩 I/O와 IOCP(다음 섹션에서 거론)는 입출력 요청( Recv,Send 함수호출의 의미) 과 입출력 완료( 정말로 Recv,Send를 완료 했다라는 의미)가 다른 시점에서 일어난다. 그래서 입출력 요청처리와 입출력 완료처리를 각각 다른곳에서 구현한다.입출력 완료처리 방식에는 2가지 방법이 있다.

* event object notification

* completion routines

간단히 설명하자면,event object notification은 이벤트 객체의 변화를 통해서 입출력의 완료여부를 판단하겠다는 것이고, completion routines은 입출력완료가 일어나면, 지정된 함수로 콜백함수 처럼 수행된다는 의미이다.

위의 리스트에 있는 함수들 중 처음 6개는 또 다른 변수를 갖는다. WSAOVERLAPPED_COMPLETION_ROUTINE. 이는 중첩된 요청을 완료할때 호출되는 완료루틴함수포인터이다. 여기서는 event notification방법에 대해 얘기할 것이다. 오버랩 이벤트를 이용한 이 방법은 한번에 최대 64개 이벤트만을처리할 수 있다는 단점이 있다. 이를 완료루틴은 보완해 줄 수 있지만 완료루틴의 경우, 루틴함수내에 데이타 처리가 오래 걸리는 논리가 있다면, 이전것이 끝나기도 전에 다시 루틴함수가 호출되어 동기화에 문제를 일으키는 단점이 있다. 완료루틴에 대한 설명은 생략하겠다. 궁극의 서버 I/O모델은 다름아닌 IOCP ( Completion Port )이기에 다음 포스트에는 바로 이에 대해 설명하고자한다.


Event Notification( 이벤트 통지 )
오버랩I/O 이벤트 통지는 WSAOVERLAPPED구조체를 갖는 윈도우즈 이벤트객체를 요구한다. WSASend,WSARecv 등의 함수가 WSAOVERLAPPED구조체로 사용되어 지면 즉각적으로 리턴한다. 일반적으로 이들 함수의 리턴값이 SOCKET_ERROR 혹은 WSAGetLastError를 통해 WSA_IO_PENDING값일 수가 있는데 이는 실제 에러를 의미하는 것은 아니고 입출력이 진행중임을 나타내는 것이다. 나중에 어플리케이션은 언제 오버랩 입출력의 요청을 완료할 지를 결정할 필요가 있을 것이다. WSAOVERLAPPED구조체는 오버랩입출력요청의 초기화와 그 이후 완료처리를 매개하는 역할을 한다.

typedef struct WSAOVERLAPPED
{
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
WSAVENT hEvent;
} WSAOVERLAPPED, FAR * LPWSAOVERLAPPED;

Internal,InternalHigh,Offset,OffsetHigh는 시스템이 내부적으로 사용하는 것이므로 어플리케이션에서 바로 건드리면 안된다. 하지만 hEvent의 경우에는 사용할 수 있다.
오버랩입출력요청이 마지막으로 완료될때, 어플리케이션은 오버랩결과를 반환해야할 의무가 있다. 이벤트 통지방식에서, 오버랩 요청이 완료될때, 윈속은 WSAOVERLAPPED구조체있는 이벤트객체 상태를 non-signaled-> signaled 로 바꾼다. 이벤트 객체가 WSAOVERLAPPED 구조체안에 있기에, 오버랩입출력 콜이 언제 완료할지를 쉽게 결정할 수 있다. WSAEventSelect I/O모델에서 설명했던 WSAWaitForMultipleEvents함수를 호출함으로써 이를 알 수 있다. WSAWaitForMultipleEvents함수는 하나 혹은 그이상의 이벤트가 signaled되는지를 정해진 시간만큼 기다릴 수 있다. 오버랩요청이 완료되었는지를 결정할때 WSAGetOverlappedResult를 호출함으로써 오버랩 콜의 성공혹은 실패결과를 결정할 필요가 있다.

BOOL WSAGetOverlappedResult(
SOCKET s
,LPWSAOVERLAPPED lpOverlapped
,LPDWORD lpcbTransfer
,BOOL fWait
,LPDWORD lpdwFlags
);

s: 오버랩시에 사용된 소켓
lpOverlapped : 오버랩사용시 사용된 오버랩 구조체 포인터
lpcbTransfer : 오버랩함수 send,recv 호출시 실질적으로 읽거나 보낸 바이트 수 포인터
fWait : true, 완료될때까지 리턴하지 않는다. false 이면서 여전히 pending중이면, WSAGetOverlappedresult는 WSA_IO_INCOMPLETE를 나타내며 false를 리턴한다. signaled 이벤트를 기다리기때문에 이 변수는 영향이 없다.
lpdwFlags: 오버랩콜이 WSARecv 또는 WSARecvFrom함수의해 만들어졌는지 결과플랙을 받는 포인터이다.

만약 이 함수가 성공하면 true를 리턴한다. 이는 오버랩운영이 성공적으로 완료되었다는 것을 의미하고 lpcbTransfer가 가리키는 값이 들어왔다는 것을 의미한다. 만약 false를 리턴하면, 다음의 경우중 하나이다.

* 오버랩입출력이 여전히 진행중이다.
* 오버랩운행이 에러로 완료되었다.
* 변수들 중에 잘못되어서 오버랩운행을 완료할 수 없다.

에러의 경우는 lpcbTransfer가 가리키는 값은 갱신되지 않는다. 에러의 원인을 확인하기 위해서는 WSAGetLastError함수를 호출해야한다.
다음 예제코드는 오버랩 입출력을 이용한 간단한 서버이다.

// ExOverlapeedSvr.cpp : 콘솔 응용 프로그램에 대한 진입점을 정의합니다.
//

#include "stdafx.h"
#include "winsock2.h"
#pragma comment( lib,"ws2_32.lib")


#define DATA_BUFSIZE1024
int _tmain(int argc, _TCHAR* argv[])
{

WSABUF DataBuf;
char buffer[DATA_BUFSIZE]={0,};

DWORD EventTotal = 0
, RecvBytes = 0
, BytesTransfered = 0
, Flags = 0;
WSAEVENT EventArray[WSA_MAXIMUM_WAIT_EVENTS];
WSAOVERLAPPED AcceptOverlapped;
SOCKET ListenSocket,AcceptSocket;

//Step 1
//Start Winsock and set up listening socket
WSADATA wsaData;
WSAStartup( MAKEWORD(2,2), &wsaData );

ListenSocket = socket( AF_INET, SOCK_STREAM, NULL );
if( ListenSocket == SOCKET_ERROR ){
printf("[ERROR]socket\n");
WSACleanup();
return 0;
}

SOCKADDR_IN addr;
addr.sin_addr.s_addr = htonl( INADDR_ANY );
addr.sin_family = AF_INET;
addr.sin_port = htons ( 5150 );

bind( ListenSocket,(const sockaddr *)&addr , sizeof( addr ) );

if( listen( ListenSocket, 5 ) == SOCKET_ERROR ){
printf("[ERROR]listen\n");
WSACleanup();
return 0;
}


//Step 2
//Accept an inbound connection
AcceptSocket = accept( ListenSocket, NULL,NULL );

//Step 3
//Set up overlapped structure
EventArray[EventTotal] = WSACreateEvent();
ZeroMemory( &AcceptOverlapped, sizeof(WSAOVERLAPPED ) );
AcceptOverlapped.hEvent = EventArray[EventTotal];

DataBuf.buf = buffer;
DataBuf.len = DATA_BUFSIZE;
EventTotal++;

printf("Index=%d Accept\n", EventTotal -1 );


//Step 4
//Post WSARecv request to begin receiving data on the socket
if( WSARecv( AcceptSocket, &DataBuf, 1, &RecvBytes, &Flags, &AcceptOverlapped, NULL )
== SOCKET_ERROR )
{
if( WSAGetLastError() != WSA_IO_PENDING )
{
printf("[ERROR]WSARecv\n");
return 0;
}
}
printf("Index=%d WSARecv\n", EventTotal -1 );
//Process Overlapped Receives on the socket
while( true )
{
DWORD Index;
//Step 5
//Wait for overlapped I/O call to complete
Index = WSAWaitForMultipleEvents( EventTotal, EventArray, FALSE, WSA_INFINITE, FALSE );
printf("Index=%d WSAWaitForMultipleEvents\n", Index - WSA_WAIT_EVENT_0 );
//Index should be 0 because we have only one event handle in EventArray

//Step 6
//Reset The Signaled event
WSAResetEvent( EventArray[ Index - WSA_WAIT_EVENT_0] );

//Step 7
//Determine the status of the overlapped request
WSAGetOverlappedResult( AcceptSocket, &AcceptOverlapped, &BytesTransfered, FALSE, &Flags );

//First check to see whether the peer has closed the connection, and if so, close the socket
if( BytesTransfered == 0 )
{
printf("Closing socket %d\n",AcceptSocket );
closesocket( AcceptSocket );
WSACloseEvent( EventArray[ Index - WSA_WAIT_EVENT_0 ] );
WSACleanup();
return 0;
}

// Do something with the received data DataBuf contains the received data

printf( "Index=%d Recv Data=[%s]\n",Index - WSA_WAIT_EVENT_0, DataBuf.buf );


//Step 8
//Post another WSARecv request on the socket
Flags = 0;
ZeroMemory( &AcceptOverlapped, sizeof( WSAOVERLAPPED ) );
AcceptOverlapped.hEvent = EventArray[ Index - WSA_WAIT_EVENT_0 ];
DataBuf.len = DATA_BUFSIZE;
DataBuf.buf = buffer;

if( WSARecv( AcceptSocket, &DataBuf, 1, &RecvBytes, &Flags, &AcceptOverlapped, NULL ) == SOCKET_ERROR )
{
if( WSAGetLastError() != WSA_IO_PENDING )
{
printf("[ERROR]WSARecv\n");
break;
}
}
printf( "Index=%d WSARecv\n",Index - WSA_WAIT_EVENT_0);

}

WSACleanup();

return 0;
}

1.소켓생성과 정해진 포트로 리스닝
2.Accept
3.WSAOVERLAPPED 구조체 생성. 이벤트 객체핸들을 구조체에 할당. 이벤트배열에도 할당.
4.비동기 WSARecv요청
5.WSAWaitForMultipleEvents 호출. signaled될때까지 이벤트 대기
6.WSAGetOverlappedResult사용으로 오버랩호출의 결과상태결정
7.이벤트객체리셋
8.또 다른 WSARecv요청
9. 5~8 반복


이 샘플은 소켓 하나의 이벤트에 대해서만 움직인다. 다수 소켓에 대해서는 확장하여 만들 수 있을 것이다.

윈속함수가 오버랩방식( 오버랩구조체 이벤트를 사용하거나 완료루틴을 사용할때 )으로 사용되었을때, 때때로 수행이 바로 완료될때가 있다. 예를 들어 데이타가 이미 수신되거나buffered되었을때 WSARecv함수를 호출할 경우, 이 함수는 NO_ERROR를 리턴한다.

내게맞는 Winsock 입출력Model 선택하기 #5 - Overlapped I/O Model (2024)

References

Top Articles
Latest Posts
Article information

Author: Delena Feil

Last Updated:

Views: 6075

Rating: 4.4 / 5 (45 voted)

Reviews: 84% of readers found this page helpful

Author information

Name: Delena Feil

Birthday: 1998-08-29

Address: 747 Lubowitz Run, Sidmouth, HI 90646-5543

Phone: +99513241752844

Job: Design Supervisor

Hobby: Digital arts, Lacemaking, Air sports, Running, Scouting, Shooting, Puzzles

Introduction: My name is Delena Feil, I am a clean, splendid, calm, fancy, jolly, bright, faithful person who loves writing and wants to share my knowledge and understanding with you.