상세 컨텐츠

본문 제목

TCP서버 실습

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

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

본문

TCP 클라이언트, 서버 코드 몇 가지 실습하면서 TCP 통신의 특징을 살펴보자.

client server 모두 메세지 송 수신 할 수 있다.

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)
    {
        int32 errCode = ::WSAGetLastError();
        cout << "Socket ErrorCode : " << errCode << endl;
        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);

    if (::connect(clientSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)))
    {
        int32 errCode = ::WSAGetLastError();
        cout << "Socket ErrorCode : " << errCode << endl;
        return 0;
    }

    // ----------------------------
    // 연결 성공! 이제부터 데이터 송수신 가능!

    cout << "Connected to Server!" << endl;
    while (true)
    {
        // TODO
        char sendBuffer[100] = "Hello World!";

        int32 resultCode = ::send(clientSocket, sendBuffer, sizeof(sendBuffer), 0);
        if (resultCode == SOCKET_ERROR)
        {
            int32 errCode = ::WSAGetLastError();
            cout << "Socket ErrorCode : " << errCode << endl;
            return 0;
        }

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

        char recvBuffer[1000];

        int32 recvLen = ::recv(clientSocket, recvBuffer, sizeof(recvBuffer), 0);
        if (recvLen <= 0)
        {
            int32 errCode = ::WSAGetLastError();
            cout << "Socket ErrorCode : " << errCode << endl;
            return 0;
        }

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

        this_thread::sleep_for(1s);
    }

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

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

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

 

GameServer.cpp

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

    SOCKET listenSocket = ::socket(AF_INET, SOCK_STREAM, 0);
    if (listenSocket == INVALID_SOCKET)
    {
        int32 errCode = ::WSAGetLastError();
        cout << "Socket ErrorCode : " << errCode << endl;
        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(listenSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
    {
        int32 errCode = ::WSAGetLastError();
        cout << "Socket ErrorCode : " << errCode << endl;
        return 0;
    }

    if (::listen(listenSocket, 10) == SOCKET_ERROR)
    {
        int32 errCode = ::WSAGetLastError();
        cout << "Socket ErrorCode : " << errCode << endl;
        return 0;
    }

    // ----------------------------
    while (true)
    {
        SOCKADDR_IN clientAddr; // IPv4
        ::memset(&clientAddr, 0, sizeof(clientAddr));
        int32 addrLen = sizeof(clientAddr);
        SOCKET clientSocket = ::accept(listenSocket, (SOCKADDR*)&clientAddr, &addrLen);
        if (clientSocket == INVALID_SOCKET)
        {
            int32 errCode = ::WSAGetLastError();
            cout << "Socket ErrorCode : " << errCode << endl;
            return 0;
        }

        // 손님 입장!
        char ipAddress[16];
        ::inet_ntop(AF_INET, &clientAddr.sin_addr, ipAddress, sizeof(ipAddress));
        cout << "Client Connected! IP = " << ipAddress << endl;

        // TODO
        while (true)
        {
            char recvBuffer[1000];

            this_thread::sleep_for(1s);

            int32 recvLen = ::recv(clientSocket, recvBuffer, sizeof(recvBuffer), 0);
            if (recvLen <= 0)
            {
                int32 errCode = ::WSAGetLastError();
                cout << "Socket ErrorCode : " << errCode << endl;
                return 0;
            }

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

            int32 resultCode = ::send(clientSocket, recvBuffer, recvLen, 0);
            if (resultCode == SOCKET_ERROR)
            {
                int32 errCode = ::WSAGetLastError();
                cout << "Socket ErrorCode : " << errCode << endl;
                return 0;
            }
        }
    }

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


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

listenSocket은 처음 Socket만들때만 쓰이고 실제 통신은 clientSocket을 통해서 하는 점에 유의한다.

 

현재 사용하는 send, receive 함수는 모두 block함수이다. send를 했는데 receive를 안한다면 block되는지 실험해 보자

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)
    {
        int32 errCode = ::WSAGetLastError();
        cout << "Socket ErrorCode : " << errCode << endl;
        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);

    if (::connect(clientSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)))
    {
        int32 errCode = ::WSAGetLastError();
        cout << "Socket ErrorCode : " << errCode << endl;
        return 0;
    }

    // ----------------------------
    // 연결 성공! 이제부터 데이터 송수신 가능!

    cout << "Connected to Server!" << endl;
    while (true)
    {
        // TODO
        char sendBuffer[100] = "Hello World!";

        int32 resultCode = ::send(clientSocket, sendBuffer, sizeof(sendBuffer), 0);
        if (resultCode == SOCKET_ERROR)
        {
            int32 errCode = ::WSAGetLastError();
            cout << "Socket ErrorCode : " << errCode << endl;
            return 0;
        }

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

        this_thread::sleep_for(1s);
    }

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

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

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

GameServer.cpp

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

    SOCKET listenSocket = ::socket(AF_INET, SOCK_STREAM, 0);
    if (listenSocket == INVALID_SOCKET)
    {
        int32 errCode = ::WSAGetLastError();
        cout << "Socket ErrorCode : " << errCode << endl;
        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(listenSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
    {
        int32 errCode = ::WSAGetLastError();
        cout << "Socket ErrorCode : " << errCode << endl;
        return 0;
    }

    if (::listen(listenSocket, 10) == SOCKET_ERROR)
    {
        int32 errCode = ::WSAGetLastError();
        cout << "Socket ErrorCode : " << errCode << endl;
        return 0;
    }

    // ----------------------------
    while (true)
    {
        SOCKADDR_IN clientAddr; // IPv4
        ::memset(&clientAddr, 0, sizeof(clientAddr));
        int32 addrLen = sizeof(clientAddr);
        SOCKET clientSocket = ::accept(listenSocket, (SOCKADDR*)&clientAddr, &addrLen);
        if (clientSocket == INVALID_SOCKET)
        {
            int32 errCode = ::WSAGetLastError();
            cout << "Socket ErrorCode : " << errCode << endl;
            return 0;
        }

        // 손님 입장!
        char ipAddress[16];
        ::inet_ntop(AF_INET, &clientAddr.sin_addr, ipAddress, sizeof(ipAddress));
        cout << "Client Connected! IP = " << ipAddress << endl;

        // TODO
        while (true)
        {
            // char recvBuffer[1000];

            // this_thread::sleep_for(1s);

            // int32 recvLen = ::recv(clientSocket, recvBuffer, sizeof(recvBuffer), 0);
            // if (recvLen <= 0)
            // {
            // 	int32 errCode = ::WSAGetLastError();
            // 	cout << "Socket ErrorCode : " << errCode << endl;
            // 	return 0;
            // }

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

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


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

receive하지 않으면 block 될 거라 기대했지만 send이후 성공메세지가 콘솔에 찍힌다.

Socket통신을 시작하면 콘솔레벨에서 client, server 각각 RecvBuffer, SendBuffer를 준비한다.

send명령이 하는 일은 콘솔레벨 SendBuffer에 내용을 복사하는 것이고 복사가 완료되면 성공이다. 이후 운영체제에서 SendBuffer를 Target RecvBuffer에 송, 수신하는 일을 처리한다. 반면, receive함수는 RecvBuffer에 아무 내용도 없으면 block된 채 기다린다.

만약 send함수를 호출했는데 SendBuffer가 가득 차 있으면 공간이 생길 때까지 block된다.

 

send의 경우

SendBuffer 공간 있음? Blolck?
O X
X O

recv의 경우

RecvBuffer Data 있음? Block?
O X
X O

 

만약 send를 짧은시간 여러 번 보내고 조금 느리게 recv를 하면 어떻게 될까?

DummyClient.cpp

...
while (true)
{
    // TODO
    char sendBuffer[100] = "Hello World!";

    for (int32 i = 0; i < 10; i++)
    {
        int32 resultCode = ::send(clientSocket, sendBuffer, sizeof(sendBuffer), 0);
        if (resultCode == SOCKET_ERROR)
        {
            int32 errCode = ::WSAGetLastError();
            cout << "Socket ErrorCode : " << errCode << endl;
            return 0;
        }
    }

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

    this_thread::sleep_for(1s);
}
...

GameServer.cpp

...
while (true)
{
    char recvBuffer[1000];

    this_thread::sleep_for(1s);

    int32 recvLen = ::recv(clientSocket, recvBuffer, sizeof(recvBuffer), 0);
    if (recvLen <= 0)
    {
        int32 errCode = ::WSAGetLastError();
        cout << "Socket ErrorCode : " << errCode << endl;
        return 0;
    }

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

send는 100byte씩 10번 보내지만 recv는 한 번에 1,000byte씩 받는다.

TCP는 이 처럼 데이터 수신에 boundary개념이 없다.

 

1. send 와 recv는 블락 함수이다. send 후 recv 하지 않으면 예상되는 동작과 이유는?

- send 정상 호출하고 block 되지 않는다.

- send 가 하는 일은 send buffer에 내용을 복사하는 일이다. 만약 buffer 공간이 부족하면 block 된다.

- recv 가 하는 일은 recv buffer에서 내용을 복사해 오는 일이다. recv buffer가 비어있으면 block 된다.

2. 짧은 시간 여러 번 send, 보다 긴 시간에 한 번 recv 하면 예상되는 동작과 이유는?

- 짧게 여러 번 보내도 recv할 때 한꺼번에 받은 데이터를 읽는다.

- Tcp 통신은 데이터 수신할 때, boundary 개념이 없기 때문.

 

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

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

UDP 서버실습  (0) 2021.11.06
TCP vs UDP  (0) 2021.11.05
소켓 프로그래밍 기초 #2  (0) 2021.11.04
소켓 프로그래밍 기초 #1  (0) 2021.11.03
TypeCast  (0) 2021.10.29

관련글 더보기

댓글 영역