WSAEventSelect Model

Date:     Updated:

카테고리:

태그:

인프런에 있는 루키스님의 게임 서버 강의를 듣고 정리한 내용입니다.


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 이벤트 탐지 등록



맨 위로 이동하기

Server 카테고리 내 다른 글 보러가기

댓글 남기기