상세 컨텐츠

본문 제목

Overlapped 모델(이벤트기반)

똑똑한 개발/C++ 게임개발

by 성댕쓰 2021. 11. 17. 22:19

본문

Select 모델과 WSAEventSelect 모델은 non-blocking sync 방식이었다. 즉 함수 내부 로직이 완료될때까지 기다리지 않지만 함수 호출을 하면 함수 실행이 되는 방식이었다.

이번에 알아볼 Overlapped 모델은 async 방식이다. 함수 호출을 하면 다른 시점에 함수가 실행될 가능성이 있다.

 

GameServer.cpp

const int32 BUFSIZE = 1000;

struct Session
{
    SOCKET socket;
    char recvBuffer[BUFSIZE] = {};
    int32 recvBytes = 0;
    WSAOVERLAPPED overlapped = {};
};

int main()
{
    WSAData wsaData;
    if (::WSAStartup(MAKEWORD(2, 2), &wsaData))
        return 0;

    // 블록킹(Blocking)소켓
    // accept -> 접속한 클라가 있을 때
    // connect -> 서버 접속 성공했을 때
    // send, sendto -> 요청한 데이터를 송신 버퍼에 복사했을 때
    // recv, recvfrom -> 수신 버퍼에 도착한 데이터가 있고, 이를 유저레벨 버퍼에 복사했을 때

    // 논블록킹(Non-Blocking)

    SOCKET listenSocket = ::socket(AF_INET, SOCK_STREAM, 0);
    if (listenSocket == INVALID_SOCKET)
        return 0;

    u_long on = 1;
    if (::ioctlsocket(listenSocket, FIONBIO, &on) == INVALID_SOCKET)
        return 0;

    SOCKADDR_IN serverAddr;
    ::memset(&serverAddr, 0, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = ::htonl(INADDR_ANY);
    serverAddr.sin_port = ::htons(7777);

    if (::bind(listenSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
        return 0;

    if (::listen(listenSocket, SOMAXCONN) == SOCKET_ERROR)
        return 0;
    
    cout << "Accept" << endl;

    // Overlapped IO (비동기 + 논블록킹)
    // - Overlapped 함수를 건다 (WSARecv, WSASend)
    // - Overlapped 함수가 성공했는지 확인 후,
    // -> 성공했으면 결과 얻어서 처리
    // -> 실패했으면 사유를 확인

    // 1) 비동기 입출력 소켓
    // 2) WSABUf 배열의 시작 주소 + 개수 // Scatter-Gather
    //    char sendBuffer[100];
    //    WSABUF wsaBuf[2];
    //    wsaBuf[0].buf = sendBuffer;
    //    wsaBuf[0].len = 100;
    //    char sendBuffer2[100];
    //    wsaBuf[1].buf = sendBuffer2;
    //    wsaBuf[1].len = 100;
    // 3) 보내고/받은 바이트 수
    // 4) 상세 옵션 0
    // 5) WSAOVERLAPPED 구조체 주소값
    // 6) 입출력이 완료되면 OS가 호출할 콜백 함수
    // WSASend
    // WSARecv

    // Overlapped 모델(이벤트 기반)
    // - 비동기 입출력 지원하는 소켓 생성 + 통지 받기 위한 이벤트 객체 생성
    // - 비동기 입출력 함수 호출(1에서 만든 이벤트 객체를 같이 넘겨줌)
    // - 비동기 작업이 바로 완료되지 않으면, WSA_IO_PENDING 오류 코드
    // 운영체제는 이벤트 객체를 signaled 상태로 만들어서 완료 상태 알려줌
    // - WSAWaitforMultipleEvents 함수 호출해서 이벤트 객체의 signal 판별
    // - WSAGetOverlappedResult 호출해서 비동기 입출력 결과 확인 및 데이터 처리

    // 1) 비동기 소켓
    // 2) 넘겨준 overlapped 구조체
    // 3) 전송된 바이트 수
    // 4) 비동기 입출력 작업이 끝날때까지 대기할지
    // false
    // 5) 비동기 입출력 작업 관련 부가 정보. 거의 사용 안 함.
    // WSAGetOverlappedResult
    
    while (true)
    {
        SOCKADDR_IN clientAddr;
        int32 addrLen = sizeof(clientAddr);

        SOCKET clientSocket;
        while (true)
        {
            clientSocket = ::accept(listenSocket, (SOCKADDR*)&clientAddr, &addrLen);
            if (clientSocket != INVALID_SOCKET)
                break;

            if (::WSAGetLastError() == WSAEWOULDBLOCK)
                continue;

            // 문제 있는 상황
            return 0;
        }

        Session session = Session{ clientSocket };
        WSAEVENT wsaEvent = ::WSACreateEvent();
        session.overlapped.hEvent = wsaEvent;

        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, nullptr) == SOCKET_ERROR)
            {
                if (::WSAGetLastError() == WSA_IO_PENDING)
                {
                    // Pending
                    ::WSAWaitForMultipleEvents(1, &wsaEvent, TRUE, WSA_INFINITE, FALSE);
                    ::WSAGetOverlappedResult(session.socket, &session.overlapped, &recvLen, FALSE, &flags);
                }
                else
                {
                    // TODO : 문제 있는 상황
                    break;
                }
            }

            cout << "Data Recv Len = " << recvLen << endl;
        }

        ::closesocket(session.socket);
        ::WSACloseEvent(wsaEvent);
    }

    // 윈속 종료
    ::WSACleanup();
}

 

DummyClient.cpp

int main()
{
    this_thread::sleep_for(1s);

    WSAData wsaData;
    if (::WSAStartup(MAKEWORD(2, 2), &wsaData))
        return 0;

    SOCKET clientSocket = ::socket(AF_INET, SOCK_STREAM, 0);
    if (clientSocket == INVALID_SOCKET)
        return 0;

    u_long on = 1;
    if (::ioctlsocket(clientSocket, FIONBIO, &on) == INVALID_SOCKET)
        return 0;

    SOCKADDR_IN serverAddr;
    ::memset(&serverAddr, 0, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    ::inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr);
    serverAddr.sin_port = ::htons(7777);

    // Connect
    while (true)
    {
        if (::connect(clientSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
        {
            // 원래 블록했어야 했는데 ... 너가 논블로킹으로 하라며?
            if (::WSAGetLastError() == WSAEWOULDBLOCK)
                continue;

            if (::WSAGetLastError() == WSAEISCONN)
                break;

            // Error
            break;
        }
    }

    cout << "Connected to Sever!" << endl;
    
    char sendBuffer[100] = "Hello World";
    WSAEVENT wsaEvent = ::WSACreateEvent();
    WSAOVERLAPPED overlapped = {};
    overlapped.hEvent = wsaEvent;

    // Send
    while (true)
    {
        WSABUF wsaBuf;
        wsaBuf.buf = sendBuffer;
        wsaBuf.len = 100;

        DWORD sendLen = 0;
        DWORD flags = 0;
        if (::WSASend(clientSocket, &wsaBuf, 1, &sendLen, flags, &overlapped, nullptr) == SOCKET_ERROR)
        {
            if (::WSAGetLastError() == WSA_IO_PENDING)
            {
                // Pending
                ::WSAWaitForMultipleEvents(1, &wsaEvent, TRUE, WSA_INFINITE, FALSE);
                ::WSAGetOverlappedResult(clientSocket, &overlapped, &sendLen, FALSE, &flags);
            }
            else
                // 진짜 문제 있는 상황
                break;
        }

        cout << "Send Data! Len = " << sizeof(sendBuffer) << endl;

        this_thread::sleep_for(1s);
    }

    // 소켓 리소스 반환
    ::closesocket(clientSocket);

    // 윈속 종료
    ::WSACleanup();
}

 

Overlapped 이벤트 기반 모델은 async 방법이지만 ::WSAWaitForMultipleEvents에서 block되는 단점이 있다.

 

1. 블록킹과 동기의 차이점은? 논블록킹과 비동기의 차이점은?

- 함수의 흐름이 멈춰야 하는 상황을 블록킹이라고 함, 함수 호출과 함수 실행이 동시에 일어나는 것을 동기라고 함.

2. Select 모델이 블록킹/비동기 인 이유는?

- 이벤트 통지를 받을 때까지 코드 흐름이 차단된다는 점에서 블록킹, 이벤트 통지를 추후에 받는 다는 점에서 비동기임.

3. Overlapped 모델이 다른 모델들과 다른 점은?

- 논블록킹/비동기 모델이다. 하지만 이번에 설명한 이벤트 기반은 블록킹/비동기 모델이다.

4. WSAGetOverlappedResult 하는 일과 4번째 파라미터에 false 넣는 이유는?

- 비동기 입출력 결과 확인.

- 비동기 작업이 끝날때까지 기다릴지 여부인데, 그 이전에 event wait 하였으므로 기다리지 않음.

참조 : [C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버 - 인프런 | 강의 (inflearn.com)

'똑똑한 개발 > C++ 게임개발' 카테고리의 다른 글

Completion Port 모델  (0) 2021.11.19
Overlapped모델 (콜백기반)  (0) 2021.11.18
WSAEventSelect 모델  (0) 2021.11.16
Select 모델  (0) 2021.11.11
논블록킹 소켓  (0) 2021.11.09

관련글 더보기

댓글 영역