WSAEventSelect Model
카테고리: Server
인프런에 있는 루키스님의 게임 서버 강의를 듣고 정리한 내용입니다.
Select Model
fd_set reads;
fd_set writes;
// ...
::select(0, &reads, &writes, nullptr, nullptr);
Select 모델의 경우 관찰 대상 소켓들을 set에 등록한 다음 살아남은 소켓들의 데이터를 처리하는 방식이었다. Pooling 방식을 이용하기 때문에 구현이 간단하고, 소수의 소켓을 다룰 때 유용하다. 하지만 매번 set을 초기화 해야하고, set에 등록된 모든 소켓들을 블로킹 방식으로 검사해야 한다. 즉, 모든 소켓들의 검사가 끝나야만 select
함수가 리턴된다. 딱 봐도 많은 수의 소켓을 다루기에 적합하지 않은 것 같다. 이번 포스팅에서 다루어 볼 WSAEventSelect 방식은 Event기반(논블로킹) 이다.
WSAEventSelect Model
WSAEventSelect 방식은 이벤트 객체를 소켓과 바인딩하고, 이벤트 객체를 통해 네트워크 이벤트를 감지한다. 기존 Select 방식과 달리 매번 Set을 초기화 할 필요가 없고, 이벤트가 발생한 소켓에 대해서만 이벤트를 처리해주면 된다.
WSACreateEvent
: 이벤트 객체 생성WSAEventSelect
: 소켓 & 이벤트 객체 바인딩, 변수로 지정한 이벤트들 감지 (non-blocking)- listen 소켓은 accept, close 이벤트만 발생
WSAWaitForMultipleEvents
: 소켓 & 이벤트 객체 바인딩, 이벤트 감지 (non-blocking)WSAEnumNetworkEvent
: 구체적인 네트워크 이벤트 리턴 & 해당 이벤트 객체 초기화WSACloseEvent
: 이벤트 객체 삭제
WSAEventSelect
vector<WSAEVENT> wsaEvents;
vector<Session> sessions;
WSAEVENT listenEvent = ::WSACreateEvent();
wsaEvents.push_back(listenEvent);
sessions.push_back(Session{ listenSocket });
if (::WSAEventSelect(listenSocket, listenEvent, FD_ACCEPT | FD_CLOSE) == SOCKET_ERROR)
return 0;
- 소켓 & 이벤트 객체 바인딩, 변수로 지정한 이벤트들 감지 (non-blocking)
- FD_ACCEPT : 접속한 클라가 있음 (accept)
- FD_READ : 데이터 수신 가능 (recv, recvfrom)
- FD_WRITE : 데이터 송신 가능 (send, sendto)
- FD_CLOSE : 상대가 접속 종료
- FD_CONNETC : 통신을 위한 연결 절차 완료
- FD_OOB : MSG_OOB로 보내는 데이터
WSAEnumNetworkEvents
while (true)
{
int32 index = ::WSAWaitForMultipleEvents(wsaEvents.size(), &wsaEvents[0], FALSE, WSA_INFINITE, FALSE);
if (index == WSA_WAIT_FAILED)
continue;
index -= WSA_WAIT_EVENT_0;
WSANETWORKEVENTS networkEvents;
// 두 번째 인자에 이벤트 객체를 넣어주면 리셋해줌
if (::WSAEnumNetworkEvents(sessions[index].socket, wsaEvents[index], &networkEvents) == SOCKET_ERROR)
continue;
// Listener 소켓 체크
if (networkEvents.lNetworkEvents & FD_ACCEPT)
{
// Error Check
if (networkEvents.iErrorCode[FD_ACCEPT_BIT] != 0)
continue;
SOCKADDR_IN clientAddr;
int32 addrLen = sizeof(clientAddr);
SOCKET clientSocket = ::accept(listenSocket, (SOCKADDR*)&clientAddr, &addrLen);
if (clientSocket != INVALID_SOCKET)
{
cout << "Client Connected" << endl;
WSAEVENT clientEvent = ::WSACreateEvent();
wsaEvents.push_back(clientEvent);
sessions.push_back(Session{ clientSocket });
if (::WSAEventSelect(clientSocket, clientEvent, FD_READ | FD_WRITE | FD_CLOSE) == SOCKET_ERROR)
return 0;
}
}
// Client Socket 처리..
}
WSAWaitForMultipleEvents
-
DWORD WSAWaitForMultipleEvents( DWORD cEvents, // 대기할 이벤트 객체의 수 const WSAEVENT *lphEvents, // 이벤트 객체 배열 BOOL fWaitAll, // 모든 이벤트 대기 여부 (TRUE: 모두, FALSE: 하나만) DWORD dwTimeout, // 타임아웃 (밀리초 단위, INFINITE: 무한 대기) BOOL fAlertable // 알림 가능한 상태 사용 여부 );
- 리턴 값은 (이벤트 객체 인덱스 + WSA_WAIT_EVENT_0)
-
WSAEnumNetworkEvents
: 이벤트 객체 index와 대응되는 소켓을 통해 구체적인 네트워크 이벤트 반환- 네트워크 이벤트는 세번 째 인자로 넘어감
- 두번 째 파라미터에 이벤트 객체를 넣어주면 간편하게 초기화 가능
lNetworkEvents
타입(네트워크 이벤트 값)은 비트 연산을 통해 처리 가능- ACCEPT 이벤트 발생할 때마다 clinet socket 등록
- READ, WRITE, CLOSE 이벤트 탐지 등록
댓글 남기기