상세 컨텐츠

본문 제목

UDP 서버실습

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

by 성댕쓰 2021. 11. 6. 14:25

본문

TCP 서버 실습에서 만든 기능과 같은 일을 하면서 통신 방식만 UDP인 서버를 만들어보자.

 

GameServer.cpp

void HandleError(const char* cause)
{
    int32 errCode = ::WSAGetLastError();
    cout << cause << " ErrorCode : " << errCode << endl;
}

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

    SOCKET serverSocket = ::socket(AF_INET, SOCK_DGRAM, 0);
    if (serverSocket == INVALID_SOCKET)
    {
        HandleError("Socket");
        return 0;
    }

    SOCKADDR_IN serverAddr; // IPv4
    ::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(serverSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
    {
        HandleError("Bind");
        return 0;
    }

    while (true)
    {
        SOCKADDR_IN clientAddr;
        ::memset(&clientAddr, 0, sizeof(clientAddr));
        int32 addrLen = sizeof(clientAddr);

        this_thread::sleep_for(1s);

        char recvBuffer[1000];
        int32 recvLen = ::recvfrom(serverSocket, recvBuffer, sizeof(recvBuffer), 0,
            (SOCKADDR*)&clientAddr, &addrLen);

        if (recvLen <= 0)
        {
            HandleError("RecvFrom");
            return 0;
        }

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

        int32 errorCode = ::sendto(serverSocket, recvBuffer, recvLen, 0,
            (SOCKADDR*)&clientAddr, sizeof(clientAddr));

        if (errorCode == SOCKET_ERROR)
        {
            HandleError("SendTo");
            return 0;
        }

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

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

socket 생성할 때 SOCK_DGRAM 파라미터를 넣어준다. UDP는 연결지향 방식이 아니기 때문에 connect가 필요없다.

TCP에서 사용한 recv함수와 같은 기능을 하는 recvfrom을 사용하고 send함수와 같은 기능을 하는 sendto 함수를 사용한다.

 

DummyClient.cpp

void HandleError(const char* cause)
{
    int32 errCode = ::WSAGetLastError();
    cout << cause << " ErrorCode : " << errCode << endl;
}

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

    SOCKET clientSocket = ::socket(AF_INET, SOCK_DGRAM, 0);
    if (clientSocket == INVALID_SOCKET)
    {
        HandleError("Socket");
        return 0;
    }

    SOCKADDR_IN serverAddr; // IPv4
    ::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);

    // ----------------------------

    while (true)
    {
        for (int32 i = 0; i < 10; i++)
        {
            char sendBuffer[100] = "Hello World!";

            // 나의 IP 주소 + 포트 번호 설정
            int32 resultCode = ::sendto(clientSocket, sendBuffer, sizeof(sendBuffer), 0,
                (SOCKADDR*)&serverAddr, sizeof(serverAddr));


            if (resultCode == SOCKET_ERROR)
            {
                HandleError("SendTo");
                return 0;
            }

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

        SOCKADDR_IN recvAddr;
        ::memset(&recvAddr, 0, sizeof(recvAddr));
        int32 addrLen = sizeof(recvAddr);

        char recvBuffer[1000];

        // Unconnected UDP
        int32 recvLen = ::recvfrom(clientSocket, recvBuffer, sizeof(recvBuffer), 0,
            (SOCKADDR*)&recvAddr, &addrLen);


        if (recvLen <= 0)
        {
            HandleError("RecvFrom");
            return 0;
        }

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

        this_thread::sleep_for(1s);
    }

    // ---------------------------

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

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

UDP 통신은 TCP 통신과 달리 data에 boundary개념이 있기 때문에 100Byte씩 10번을 보내면 항상 100Byte씩 받는다.

(반면, boundary개념이 없는 TCP는 100Byte보다 크게 받을 가능성이 있다.)

 

그러나 전송순서를 보장하는 TCP와 달리 UDP는 보장하지 않기 때문에 먼저 보낸 패킷이 나중에 도착할 가능성이 있다.

 

위에서 살펴본 UDP통신 방식은 default Unconnected 방식이다. Connected 방식도 있는데, 실제로 Connection을 맺는 것이 아닌, 즐겨찾기에 보내고자 하는 목적지의 주소를 등록하여 사용하는 방식이다.

 

DummyClient.cpp

void HandleError(const char* cause)
{
    int32 errCode = ::WSAGetLastError();
    cout << cause << " ErrorCode : " << errCode << endl;
}

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

    SOCKET clientSocket = ::socket(AF_INET, SOCK_DGRAM, 0);
    if (clientSocket == INVALID_SOCKET)
    {
        HandleError("Socket");
        return 0;
    }

    SOCKADDR_IN serverAddr; // IPv4
    ::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);

    // Connected UDP
    ::connect(clientSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr));

    // ----------------------------

    while (true)
    {
        for (int32 i = 0; i < 10; i++)
        {
            char sendBuffer[100] = "Hello World!";

            // 나의 IP 주소 + 포트 번호 설정

            // Unconnected UDP
            /*int32 resultCode = ::sendto(clientSocket, sendBuffer, sizeof(sendBuffer), 0,
                (SOCKADDR*)&serverAddr, sizeof(serverAddr));*/

            // Connected UDP
            int32 resultCode = ::send(clientSocket, sendBuffer, sizeof(sendBuffer), 0);

            if (resultCode == SOCKET_ERROR)
            {
                HandleError("SendTo");
                return 0;
            }

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

        SOCKADDR_IN recvAddr;
        ::memset(&recvAddr, 0, sizeof(recvAddr));
        int32 addrLen = sizeof(recvAddr);

        char recvBuffer[1000];

        // Unconnected UDP
        /*int32 recvLen = ::recvfrom(clientSocket, recvBuffer, sizeof(recvBuffer), 0,
            (SOCKADDR*)&recvAddr, &addrLen);*/

        // ConnectedUDP
        int32 recvLen = ::recv(clientSocket, recvBuffer, sizeof(recvBuffer), 0);

        if (recvLen <= 0)
        {
            HandleError("RecvFrom");
            return 0;
        }

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

        this_thread::sleep_for(1s);
    }

    // ---------------------------

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

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

 

TCP, UDP방식모두 send할 때 보내는 곳의 port를 별도 설정하지 않았는데, 그러면 OS가 알맞은 port를 랜덤할당한다.

 

참조 : https://www.inflearn.com/course/언리얼-3d-mmorpg-4/

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

논블록킹 소켓  (0) 2021.11.09
소켓 옵션  (0) 2021.11.08
TCP vs UDP  (0) 2021.11.05
TCP서버 실습  (0) 2021.11.05
소켓 프로그래밍 기초 #2  (0) 2021.11.04

관련글 더보기

댓글 영역