상세 컨텐츠

본문 제목

Overlapped모델 (콜백기반)

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

by 성댕쓰 2021. 11. 18. 22:08

본문

지난 이벤트 기반 overlapped 모델은 이벤트 받을 수 있는 개수가 64개로 제한되어 있었다.

 

이번에 알아볼 콜백기반 overlapped 모델은 이벤트 기반 모델의 단점을 해결한 모델이다.

GameServer.cpp

const int32 BUFSIZE = 1000;

struct Session
{
    WSAOVERLAPPED overlapped = {};
    SOCKET 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;
    // TODO : 에코 서버를 만든다면 WSASend()

    // param 중 유용한 정보는 overlapped.
    // 구조체를 변형하여 원하는 구조체로 casting하여 사용한다.
    Session* session = (Session*)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 모델(Completion Routine 콜백 기반)
    // - 비동기 입출력 지원하는 소켓 생성
    // - 비동기 입출력 함수 호출(완료 루틴의 시작 주소를 넘겨준다)
    // - 비동기 작업이 바로 완료되지 않으면, WSA_IO_PENDING 오류 코드
    // - 비동기 입출력 함수 호출한 쓰레드를 -> Alertable Wait 상태로 만든다
    // ex) WaitForSingleObjectEx, WaitForMultipleObjectsEx, SleepEx, WSAWaitForMultipleEvents
    // - 비동기 IO 완료되면, 운영체제는 완료 루틴 호출
    // - 완료 루틴 호출이 모두 끝나면, 쓰레드는 Alertable Wait 상태에서 빠져나온다.

    // 1) 오류 발생시 0 아닌 값
    // 2) 전송 바이트 수
    // 3) 비동기 입출력 함수 호출 시 넘겨준 WSAOVERLAPPED 구조체의 주소값
    // 4) 0
    // void CompletionRoutine()

    // Select 모델
    // - 장점) 윈도우/리눅스 공통.
    // - 단점) 성능 최하 (매번 등록 비용), 64개 제한
    // WSAEventSelect 모델
    // - 장점) 비교적 뛰어난 성능
    // - 단점) 64개 제한
    // Overlapped (이벤트 기반)
    // - 장점) 성능
    // - 단점) 64개 제한
    // Overlapped (콜백 기반)
    // - 장점) 성능
    // - 단점) 모든 비동기 소켓 함수에서 사용 가능하진 않음(accept). 빈번한 Alertable Wait으로 인한 성능 저하

    // pattern 용어
    // Reactor Pattern (~뒤늦게. 논블록킹 소켓. 소켓 상태 확인 후 -> 뒤늦게 recv send 호출)
    // Proactor Pattern (~미리, Overlapped WSA~)

    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();

        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
                    // alertable wait apc 검색해보기
                    // 코드 진행
                    // - alertable wait 상태 > apc 큐 모두 비우기 > alertable wait 해줬던 이후부터 다시 진행
                    ::SleepEx(INFINITE, TRUE);
                    // 아래 방식은 기다릴 수 있는 event 개수가 64개로 정해져 있어 불편
                    // socket과 event를 1:1로 매핑해줘야 해서 불편
                    //::WSAWaitForMultipleEvents(1, &wsaEvent, TRUE, WSA_INFINITE, TRUE);
                    
                }
                else
                {
                    // TODO : 문제 있는 상황
                    break;
                }
            }

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

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

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

콜백기반 역시 blocking 부분이 존재한다. alertable wait 상태에서 thread가 기다리는 부분이 있다.

 

콜백 함수 RecvCallback에서 WSAOVERLAPPED 포인터를 Session 포인터로 바꾸기 위해 Session 구조체의 첫 변수를  WSAOVERLAPPED로 배치하여 사용한 부분 잘 기억해두자.

 

1. 이벤트방식보다 나은 점은 무엇?

- 소켓과 이벤트 1:1 매칭해서 기억해야 할 필요 없음.

2. 운영체제가 작업이 완료되었다고 판단하고 콜백함수를 알아서 부르지 않는데, 운영체제가 콜백함수를 호출하기 위해 필요한 작업은?

- 비동기 입출력 함수 호출한 쓰레드를 Alertable Wait 상태로 만들면 그때 완료 루틴이 호출된다.

- 완료 루틴 호출 모두 끝나면 쓰레드는 Alertable Wait 상태에서 빠져나온다.

3. Alertable wait 상태로 만들어주기 위한 함수는 어떤 것이 있나?

- SleepEx(INFINITE, TRUE), WSAWaitForMultipleEvents(1, &wsaEvent, TRUE, WSA_INFINITE, TRUE);

4. Alertable wait 상태가 종료되면 다음 함수 흐름은 무엇?

- Alertable wait 상태 만든 함수 다음 함수가 실행됨.

5. 콜백함수에 overlapped 구조체 포인터가 들어와도 소켓에 관해 알 수 있는 정보가 없는데, 해결방법은?

- 내가 정의한 구조체에 overlapped 구조체 멤버변수가 첫번째로 위치하게 한 후, 콜백함수에서 내가 정의한 구조체로 캐스팅하여 사용한다.

6. 지금까지 본 모델 중 리눅스환경에도 동작하는 모델은?

- Select 모델

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

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

네트워크 라이브러리 제작(Socket Utils)  (0) 2021.11.27
Completion Port 모델  (0) 2021.11.19
Overlapped 모델(이벤트기반)  (0) 2021.11.17
WSAEventSelect 모델  (0) 2021.11.16
Select 모델  (0) 2021.11.11

관련글 더보기

댓글 영역