상세 컨텐츠

본문 제목

논블록킹 소켓

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

by 성댕쓰 2021. 11. 9. 22:13

본문

지금까지 작성한 소켓통신 코드는 모두 블록킹 코드이다. 여기에 한 가지 설정만 더하면 논블록킹 소켓으로 만들 수 있다.

해당 설정은 ::ioctlsocket(...)이다. 이를 이용해 논블록킨 소켓 서버, 클라이언트를 만들어보자.

 

GameServer.cpp

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

    // 블록킹(Blocking)소켓
    // accept -> 접속한 클라가 있을 때
    // connet -> 서버 접속 성공했을 때
    // 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;

    SOCKADDR_IN clientAddr;
    int32 addrLen = sizeof(clientAddr);

    while (true)
    {
        SOCKET clientSocket = ::accept(listenSocket, (SOCKADDR*)&clientAddr, &addrLen);
        if (clientSocket == INVALID_SOCKET)
        {
            // 원래 블록했어야 했는데... 너가 논블로킹으로 하라며?
            if (::WSAGetLastError() == WSAEWOULDBLOCK)
                continue;

            // Error
            break;
        }

        cout << "Client Connected!" << endl;

        // Recv
        while (true)
        {
            char recvBuffer[1000];
            int32 recvLen = ::recv(clientSocket, recvBuffer, sizeof(recvBuffer), 0);
            if (recvLen == SOCKET_ERROR)
            {
                // 원래 블록했어야 했는데... 너가 논블로킹으로 하라며?
                if (::WSAGetLastError() == WSAEWOULDBLOCK)
                    continue;

                // Error
                break;
            }
            else if (recvLen == 0)
            {
                // 연결 끊김
                break;
            }

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

            // Send
            while (true)
            {
                if (::send(clientSocket, recvBuffer, recvLen, 0) == SOCKET_ERROR)
                {
                    // 원래 블록했어야 했는데... 너가 논블로킹으로 하라며?
                    if (::WSAGetLastError() == WSAEWOULDBLOCK)
                        continue;

                    // Error
                    break;
                }

                cout << "Send Data! Len = " << recvLen << endl;
                break;
            }
        }
    }

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

DummyClient.cpp

int main()
{
    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";

    // Send
    while (true)
    {
        if (::send(clientSocket, sendBuffer, sizeof(sendBuffer), 0) == SOCKET_ERROR)
        {
            // 원래 블록했어야 했는데... 너가 논블로킹으로 하라며?
            if (::WSAGetLastError() == WSAEWOULDBLOCK)
                continue;

            // Error
            break;
        }

        cout << "Send Data! Len = " << sizeof(sendBuffer) << endl;
        
        while (true)
        {
            char recvBuffer[1000];
            int32 recvLen = ::recv(clientSocket, recvBuffer, sizeof(recvBuffer), 0);
            if (recvLen == SOCKET_ERROR)
            {
                // 원래 블록했어야 했는데... 너가 논블로킹으로 하라며?
                if (::WSAGetLastError() == WSAEWOULDBLOCK)
                    continue;

                // Error
                break;
            }
            else if (recvLen == 0)
            {
                // 연결 끊김
                break;
            }

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

        this_thread::sleep_for(1s);
    }

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

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

논블록이긴 하지만 실행하려는 함수가 성공할 때까지 무한 반복을 하는 한계를 가지고 있다.

 

1. 서버에서 accept 했는데, 아무도 들어오지 않으면 함수 흐름은?

- accept 가 INVALID_SOCKET 을 반환, WSAGetLastError 는 WSAEWOULDBLOCK 반환

- continue, 재시도

2. recv 했는데, recvbuffer에 없으면 반환하는 값은?

- SOCEKT_ERROR 반환 WSAGetLastError WSAEWOULDBLOCK 반환

- continue, 재시도

- recvLen == 0 이면 연결 끊긴 것.

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

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

WSAEventSelect 모델  (0) 2021.11.16
Select 모델  (0) 2021.11.11
소켓 옵션  (0) 2021.11.08
UDP 서버실습  (0) 2021.11.06
TCP vs UDP  (0) 2021.11.05

관련글 더보기

댓글 영역