KoreanFoodie's Study
[C++ 게임 서버] 3-11. Overlapped 모델 (콜백 기반) 본문
[C++ 게임 서버] 3-11. Overlapped 모델 (콜백 기반)
핵심 :
1. 콜백을 활용하여 Overlapped 모델을 만들면, 이벤트의 갯수 제한 없이(64개) 스레드가 특정 네트워크 이벤트를 받았을 때 원하는 함수를 호출하게 만들 수 있다.
2. 비동기 입출력을 시작하고 나서 스레드는 Alertable Wait 상태로 들어가게 되고, OS 에서 비동기 입출력이 완료된 후, APC 큐를 비워주며(한꺼번에 비워준다) 콜백 함수를 호출하게 되면, 스레드는 Alertable Wait 상태를 빠져나간다.
3. 콜백 기반은 이벤트에 대한 갯수 제한(64개)가 없으나, 모든 비동기 소켓 함수에서 사용 가능하지는 않으며(accept), Alertable Wait 상태로 빈번하게 전환되어 성능이 저하될 수 있다.
이제 본격적으로 IOCP 에 대해 배우기전, 마지막 준비운동(준비운동만하다 끝나면 안되는데...)으로, Overlapped 모델(콜백 기반)에 대해 알아보겠다.
이전에는 Overlap 객체에 네트워크 이벤트를 연동해서, Signal 상태가 되면 결과를 확인하고 특정 동작을 수행하게 만들었다.
다만 그런 방식으로 구현하면... 한 가지 문제가 있다.
WSAOVERLAPPED 객체를 보면...
typedef struct _OVERLAPPED {
ULONG_PTR Internal;
ULONG_PTR InternalHigh;
union {
struct {
DWORD Offset;
DWORD OffsetHigh;
} DUMMYSTRUCTNAME;
PVOID Pointer;
} DUMMYUNIONNAME;
HANDLE hEvent;
} OVERLAPPED, *LPOVERLAPPED;
위처럼 나타나는데, 이 때 Internal 과 InternalHigh 는 OS 에서 사용하는 거라 일단 신경 쓰지 않아도 된다 😄
중요한건 HANDLE 을 사용한다는 것인데... 문제는 Select 모델과 비슷하게, 사용할 수 있는 핸들의 갯수가 64개로 제한된다는 것이다.
반면 WSARecv 함수에 콜백을 넣어주면, 그러한 제한 없이 특정 네트워크 이벤트가 발생한 모든 소켓에 대해 우리가 지정한 콜백함수가 호출될 것이다! 😎
사실 코드는 이전 것과 크게 차이가 없다. WSAData 및 소켓을 만들고, 클라이언트 소켓을 연결하는 과정은 동일하니 넘어가도록 하자. 😅
이제 달라진 부분을 보면...
Session session = Session{ clientSocket };
//WSAEVENT wsaEvent = ::WSACreateEvent();
cout << "Client Connected !" << endl;
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)
{
// Pending
// Alertable Wait
::SleepEx(INFINITE, TRUE);
//::WSAWaitForMultipleEvents(1, &wsaEvent, TRUE, WSA_INFINITE, TRUE);
}
else
{
// TODO : 문제 있는 상황
break;
}
}
else
{
cout << "Data Recv Len = " << recvLen << endl;
}
}
::closesocket(session.socket);
//::WSACloseEvent(wsaEvent);
이제 WSAEVENT 를 만들 필요가 없으며, WSARecv 함수의 사용도 조금 달라졌음을 알 수 있다. 구체적으로는 마지막 인자에 RecvCallback 이라는 녀석이 들어갔는데...
void CALLBACK RecvCallback(DWORD error, DWORD recvLen, LPWSAOVERLAPPED overlapped, DWORD flags)
{
cout << "Data Recv Len Callback = " << recvLen << endl;
// TODO : 에코 서버를 만든다면 WSASend()
// (참고용) : overlapped 를 맨 처음 항목으로 옮김으로써 Session 으로 캐스팅이 가능해짐
Session* session = (Session*)overlapped;
}
이 녀석은 그냥 콜백함수다 😄
실제로, WSARecv 의 API 를 보면 다음과 같이 되어 있다 :
위의 lpCompletionRoutine 이 바로 우리가 넣을 콜백 함수를 의미한다!
또한 내부를 보면 아래 코드를 확인할 수 있는데...
if (::WSAGetLastError() == WSA_IO_PENDING)
{
// Pending
// Alertable Wait
::SleepEx(INFINITE, TRUE);
//::WSAWaitForMultipleEvents(1, &wsaEvent, TRUE, WSA_INFINITE, TRUE);
}
Alterable Wait 상태가 되면, Alertable(TRUE) 상태가 될 때까지 무한정(INFINITE) 대기를 하게 된다.
앗, 그런데 Alertable Wait 이 뭐지? 라는 생각이 들 수 있는데... 사실 Overlapped (콜백 기반) 모델은 다음과 같이 생겼다 :
먼저 비동기 입출력을 시작하고, 스레드는 Alertable Wait 상태로 들어간다. 그리고 비동기 입출력이 완료되면, APC 큐에서 콜백 함수를 호출하며 큐를 다 비워주고(완료 루틴 호출), 스레드는 Alertable Wait 상태를 탈출하게 되는 것이다!
이렇게 IOCP 까지 도달하기 전, 다른 모델들에 대한 사전 조사를 마쳤다. 각각의 모델들에 대한 장단점을 정리하면 대략 다음과 같을 것이다 :
Select 모델
- 장점) 윈도우/리눅스 공통.
- 단점) 성능 최하 (매번 등록 비용), 64개 제한
WSAEventSelect 모델
- 장점) 비교적 뛰어난 성능
- 단점) 64개 제한
Overlapped (이벤트 기반)
- 장점) 성능
- 단점) 64개 제한
Overlapped (콜백 기반)
- 장점) 성능
- 단점) 모든 비동기 소켓 함수에서 사용 가능하진 않음 (accept). 빈번한 Alertable Wait으로 인한 성능 저하
IOCP
- 끝판왕 (곧 등장)
위에서 '모든 비동기 소켓 함수에서 사용 가능하지 않다' 고 되어 있는데, 실제로 AcceptEx 함수를 보면 :
맨 마지막에 콜백 함수를 넣는 부분이 없음을 확인할 수 있다 😮
아, 참고로 Reactor/Proactor Pattern 이라는 용어를 사용하기도 하는데, 참고로만 알아두자 😉
Reactor Pattern
- (~뒤늦게. 논블로킹 소켓. 소켓 상태 확인 후 -> 뒤늦게 recv send 호출)
Proactor Pattern
- (~미리. Overlapped WSA~)
'Game Dev > Game Server' 카테고리의 다른 글
[C++ 게임 서버] 4-1. Socket Utils (0) | 2023.10.25 |
---|---|
[C++ 게임 서버] 3-12. Completion Port 모델 (0) | 2023.10.18 |
[C++ 게임 서버] 3-10. Overlapped 모델 (이벤트 기반) (0) | 2023.10.17 |
[C++ 게임 서버] 3-9. WSAEventSelect 모델 (0) | 2023.10.16 |
[C++ 게임 서버] 3-8. Select 모델 (0) | 2023.09.25 |