Overlapped Model (Callback)
카테고리: Server
인프런에 있는 루키스님의 게임 서버 강의를 듣고 정리한 내용입니다.
Overlapped Model
void CALLBACK RecvCallback(DWORD error, DWORD recvLen, LPWSAOVERLAPPED overlapped, DWORD flags)
{
cout << "Data Recv Len Callback = " << recvLen << endl;
Session* session = (Session*)overlapped;
// TODO : 에코 서버를 만든다면 WSASend()
}
// Accept client socket
while (true)
{
WSABUF wsaBuf;
wsaBuf.buf = session.recvBuffer;
wsaBuf.len = BUFSIZE;
DWORD recvLen = 0;
DWORD flags = 0;
if (::WSARecv(clientSocket, &wsaBuf, 1, &recvLen, &flags, &session.overlapped, RecvCallback) == SOCKET_ERROR)
{
if (::WSAGetLastError() == WSA_IO_PENDING)
{
::SleepEx(INFINITE, TRUE);
}
else
{
// 에러 처리
break;
}
}
else
{
cout << "Data Recv Len =" << recvLen << endl;
}
}
ioctlsocket()
: 비동기 입출력 소켓 생성WSARecv()
/WSASend()
: 비동기 입출력 함수 호출 (콜백 함수 전달)- 작업이 가능하면 바로 처리
- 바로 완료되지 않으면 PENDING 오류 코드 리턴
SleepEx()
: 펜딩 오류시 해당 스레드를 Alertable Wait 상태로 변경- ex) WaitForSingleObjectEx, WaitForMultipleObjectsEx, SleepEx, WSAWAitForMultipleEvents
- IO 작업이 완료되면 Completion Routine Callback 함수 호출
- Alertable Wait 상태 빠져 나옴
WSARecv
int WSARecv(
SOCKET s, // 소켓 핸들
LPWSABUF lpBuffers, // 수신 버퍼 배열
DWORD dwBufferCount, // 버퍼 개수
LPDWORD lpNumberOfBytesRecvd, // 수신된 바이트 수
LPDWORD lpFlags, // 추가적인 플래그 (옵션)
LPWSAOVERLAPPED lpOverlapped, // 비동기 I/O를 위한 OVERLAPPED 구조체
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine // 비동기 완료 콜백 함수
);
- 비동기 입출력 함수(WSARecv, WSASend)는 WSABUF 구조체 배열을 이용
- Overlapped 구조체, Completion Routine 콜백 함수 받음
- 이벤트 객체를 이용한 방식은 갯수 제한이 존재하는데, 콜백 함수 기반은 제한이 없음
Completion Routine Callback Function
typedef
void
(CALLBACK * LPWSAOVERLAPPED_COMPLETION_ROUTINE)(
IN DWORD dwError, // 오류 코드 (0이면 성공)
IN DWORD cbTransferred, // 전송된 바이트 수
IN LPWSAOVERLAPPED lpOverlapped, // OVERLAPPED 구조체 포인터
IN DWORD dwFlags // 추가적인 플래그
);
WSARecv()
함수에 전달하는 콜백 함수의 구조체(함수 포인터)는 위와 같음- 주목해야 할 점은 소켓에 대한 정보는 없고, overlapped 구조체만 전달되는 형태
struct Session
{
WSAOVERLAPPED overlapped = {};
SOCKET socket = INVALID_SOCKET;
char recvBuffer[BUFSIZE] = {};
int32 recvBytes = 0;
};
void CALLBACK RecvCallback(DWORD error, DWORD recvLen, LPWSAOVERLAPPED overlapped, DWORD flags)
{
cout << "Data Recv Len Callback = " << recvLen << endl;
Session* session = (Session*)overlapped;
// TODO...
}
- 따라서 Session 구조체의 첫 번째 변수를 overlapped 구조체로 지정
- 그럼 overlapped 포인터와 Session 포인터의 주소가 동일해짐
- overlapped 포인터를 캐스팅하여 세션에 대한 정보를 얻는 방식 이용
Alertable Wait & APC Queue
if (::WSARecv(clientSocket, &wsaBuf, 1, &recvLen, &flags, &session.overlapped, RecvCallback) == SOCKET_ERROR)
{
if (::WSAGetLastError() == WSA_IO_PENDING)
{
::SleepEx(INFINITE, TRUE);
}
else
{
// 에러 처리
break;
}
}
모든 스레드는 APC(Asynchronous Procedure Call) Queue를 가지고 있다. 해당 큐에는 비동기 입출력 작업 관련 콜백 함수들이 쌓여 있다. 콜백 함수들을 따로 모아두는 이유는 아무때나 호출되는 상황을 막기 위해서이다. 예를 들어 스레드가 lock을 걸고 중요한 작업을 처리 중인데, 비동기 콜백 함수가 호출되면 난감하다. 따라서 콜백 함수들이 아무때나 호출되는 것을 막아두고, 스레드가 Alertable Wait 상태일 때에만 콜백 함수들을 실행한다. Alertalble Wait 상태에 들어갔을 때 APC 큐에 함수들이 쌓여있으면, 해당 함수들을 전부 호출하고 스레드는 alertable wait 상태에서 탈출한다. 만약 APC 큐가 비어있다면 지정한 시간만큼 대기하는데, 콜백 함수가 발생하면 즉시 호출하고 alertable wait 상태에서 벗어나게 된다.
댓글 남기기