정리

GetQueuedCompletionStatus의 0 transferred bytes (3)

저장소/VC++

음.. 요렇게도 쓰는구나. +ㅁ+

근데 내가 찾는 것과는 다르다.



원문 출처 : http://ykpk3733.egloos.com/8780



IOCP 를 공부중에 우연히 0 size buffer 란 기법을 알게 되고 여기저기 자료를 보면서 공부한 내용을 정리한것이다.

 

조금이라도 도움이 되었으면 하는 마음으로 포스팅을 한다.

 

-----------------------------------------------------------------------------------

1. "기본 WSAOVERLAPPED의 구조"

다음 구조체는 WSARecv() 혹은 WSASend() 함수에 넘겨주어야 하는 일반적인WSAOVERLAPPED 구조체이다.

 

typedef struct PER_IO_DATA : public WSAOVERLAPPED {
 char  Buffer[DATA_BUF_SIZE];
 int operation;
}PER_IO_DATA;

 

이 구조체에는 Buffer라는 변수가 포함되어 있다.

즉, 이것은 0 SIZE 버퍼가 아닌 클라이언트마다 각각의 버퍼를 할당해주는 방식이다.


2. "0 SIZE 버퍼"

1번과 같은 방식은 대량 연결용 서버의 경우엔 불필요한 메모리를 너무 많이 잡아 먹고, 관리도 힘들어진다.

이런 경우에는 "0 SIZE 버퍼" 를 이용하는 방법이 있다.

아래는 "0 SIZE 버퍼" 의 구조체 정보이다.

 

typedef struct PER_IO_DATA : public WSAOVERLAPPED {
 int operation; // OP_RECV, OP_SEND
}

 

보는바 와 같이 버퍼를 아예 제거 해버렸다.

그리고, 다음과 같이 사용 하면 된다.

 

pData = new PER_IO_DATA;
if( pData == NULL )
 return FALSE;

pData->Operation = OP_RECV;

wsabuf.buf = NULL; // 버퍼를 아예 제공하지 않는다.
wsabuf.len = 0;  // Recv할 최대 크기를 0으로 만든다.
flag = 0;
ret = WSARecv( sock, &wsabuf, 1, &&bytes, &flag, pData, NULL );
if( ret == SOCKET_ERROR && WSAGetLastError() != ERROR_IO_PENDING ){
 delete pData;
 return FALSE;
}

 

이렇게 하면, 해당 소켓으로 데이터가 들어왔을때 IOCP는 Worker thread를 깨워

처리하도록 한다. 그런데, 문제는 버퍼가 없으니 받은 데이터가 없다.

3. Worker thread에서 데이터 처리하기

아래 코드는 Worker thread의 핵심이 되는 함수이다.

 

ret = GetQueuedCompletionStatus(
 m_cp,    // IOCP Handle
 &bytes,    // 송,수신된 byte 수
 (LPWORD)&sock,   // Completion Key
 (LPWSAOVERLAPPED *)&pid, // WSAOVERLAPPED 데이터
 INFINITE );

 

3번째 PARAMETER인 sock은 Completion Key 로 SOCKET HANDLE 을 바로 사용한 경우이다.

일반적으로 bytes 값이 "0"이면 연결이 끊긴 것으로 처리를 하였다.

하지만 "0 SIZE 버퍼"를 사용했을 경우엔 무조건 bytes 값이 "0" 로 리턴된다.

따라서, 이 경우에는 끊긴 것으로 판단하지 말고 일단 데이타가 들어온 것이라고 판단해야 한다.

버퍼를 0로 만들었기 때문에 IOCP는 소켓으로 수신된 데이터를 아직 손대지 않은 상태이다.

이제 이것을 읽어 내야 하는데, 방법은 간단하다.

일반적인 송,수신 소켓 함수로 읽어내면 된다.

프로토콜을 이쁘게 정의하면, 수신되어야할 데이터가 얼마인지를 알 수 있는 경우가 있는데,

이 경우는 그 만큼 읽혀 질때까지 recv()를 하면되고,

만일 모를 경우는 nonblocking 방식으로 WSAEWOULDBLOCK이 될 때까지 recieve를 하면 된다.

이렇게 데이터를 모두 읽어서 처리하였다면,

다시 WSARecv() 를 사용하여 IOCP에 요청을 한다.

물론 생성된 pid는 이곳에서 제거를 해도 좋고, 계속 사용해도 좋다.