Server/C++

TCP 소켓 프로그래밍

Juzdalua 2024. 8. 6. 01:30

 

IPv4에서 범위를 초과한 ip를 위해 생긴 IPv6

 


흐름/혼잡 제어

 

sendBuffer에 데이터를 저장하고 송신한 후, recvBuffer에서 데이터를 정확하게 수신한다는 보장이 없다.

100바이트의 데이터를 송신하여도 100바이트로 받지 않을 수 있다.


Client

소켓 준비 - 서버 연결

 

Server

소켓 준비 - Bind - Listen - Accept


기본 헤더

#include <WinSock2.h>
#include <MSWSock.h>
#include <WS2tcpip.h>
#pragma comment(lib, "ws2_32.lib")

 

/*
    af: Address Family (AF_INET = IPv4, AF_INTE6 = IPv6)
    type: TCP(SOCK_STREAM) vs UDP(SOCK_DGRAM)
    protocol: 0
    return: descriptor
*/
SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, 0);

https://learn.microsoft.com/ko-kr/windows/win32/api/winsock2/nf-winsock2-socket

 

socket 함수(winsock2.h) - Win32 apps

소켓 함수는 특정 전송 서비스 공급자에 바인딩된 소켓을 만듭니다.

learn.microsoft.com

 

serverAddr.sin_port = htons(PORT);
/*
    host to network short
    Little-Endian vs Big-Endian
    little: [0x78][0x56][0x34][0x12]
    big: [0x12][0x34][0x56][0x78] -> network 표준
*/

 

 

MSDN 홈페이지에서 함수 예제코드를 볼 수 있다.


Client

#include "pch.h"
#include <iostream>

#include <WinSock2.h>
#include <MSWSock.h>
#include <WS2tcpip.h>
#pragma comment(lib, "ws2_32.lib")

int main()
{
    // 1. Socket 초기화
    WSAData wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
        return 0;
    
    SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, 0);
    if (clientSocket == INVALID_SOCKET)
    {
        int32 errCode = WSAGetLastError();
        cout << "Socket ErrorCode: " << errCode << endl;
        return 0;
    }
    
    // 2. IP, PORT 설정 
    char IP[] = "127.0.0.1";
    u_short PORT = 7777;

    SOCKADDR_IN serverAddr; // IPv4
    memset(&serverAddr, 0, sizeof(serverAddr));

    serverAddr.sin_family = AF_INET;

    //serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); <- deprecated
    inet_pton(AF_INET, IP, &serverAddr.sin_addr);

    serverAddr.sin_port = htons(PORT);

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

    cout << "Connected To Server!" << endl;

    // 4. 데이터 송수신
    while (true)
    {
        char sendBuffer[100] = "Hello World!";
        int32 resultCode = send(clientSocket, sendBuffer, sizeof(sendBuffer), 0);
        if (resultCode == SOCKET_ERROR)
        {
            int32 errCode = WSAGetLastError();
            cout << "Send ErrorCode: " << errCode << endl;
            return 0;
        }

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

        // Echo Receiver
        char recvBuffer[1000];
        int32 recvLen = recv(clientSocket, recvBuffer, sizeof(recvBuffer), 0);
        if (recvLen <= 0)
        {
            int32 errCode = WSAGetLastError();
            cout << "Recv ErrorCode: " << errCode << endl;
            return 0;
        }

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

        this_thread::sleep_for(1s);
    }

    // 5. Socket 종료
    closesocket(clientSocket);
    WSACleanup();
}

 

Server

#include "pch.h"
#include "CorePch.h"
#include "CoreMacro.h"
#include "ThreadManager.h"
#include <iostream>

#include <WinSock2.h>
#include <MSWSock.h>
#include <WS2tcpip.h>
#pragma comment(lib, "ws2_32.lib")

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

	SOCKET listenSocket = socket(AF_INET, SOCK_STREAM, 0);
	if (listenSocket == INVALID_SOCKET)
	{
		int32 errCode = WSAGetLastError();
		cout << "Socket ErrorCode: " << errCode << endl;
		return 0;
	}

	u_short PORT = 7777;
	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(PORT);

	// bind
	if (::bind(listenSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
	{
		int32 errCode = WSAGetLastError();
		cout << "Bind ErrorCode: " << errCode << endl;
		return 0;
	}

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

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

		// 연결 성공
		char ipAddress[16];
		inet_ntop(AF_INET, &clientAddr.sin_addr, ipAddress, sizeof(ipAddress));
		cout << "Client Connected! IP: " << ipAddress << endl;
        
        while (true)
        {
            char recvBuffer[1000];
            int32 recvLen = recv(clientSocket, recvBuffer, sizeof(recvBuffer), 0);
            if (recvLen <= 0)
            {
                int32 errCode = WSAGetLastError();
                cout << "Recv ErrorCode: " << errCode << endl;
                return 0;
            }

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

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

	WSACleanup();
}

블로킹 함수

블로킹 함수는 특정 조건을 만족할 때까지 대기하는 함수이다.

TCP 통신에서 데이터를 송수신하는 send, recv 함수는 블로킹 함수이다.

CPU를 소모하는 대기상태가 아닌, 소유권을 커널에 넘겨주고(컨텍스트 스위칭) 대기하는 condition_variable처럼 sleep과 비슷한 대기상태이다.

send recv
sendBuffer에 데이터를 저장하는 역할 recvBuffer에서 데이터를 가져오는 역할
sendBuffer가 데이터를 저장할 공간이 부족하면 저장할 때까지 대기한다. recvBuffer에서 가져올 데이터가 없으면 데이터를 가져올 때가지 대기한다.

Client - Server간 데이터의 크기 차이

// client

char sendBuffer[100] = "Hello World!";
int32 resultCode = send(clientSocket, sendBuffer, sizeof(sendBuffer), 0);
cout << "Send Data! Len = " << sizeof(sendBuffer) << endl;
// sizeof(sendBuffer) = 100
// server

char recvBuffer[1000];
int32 recvLen = recv(clientSocket, recvBuffer, sizeof(recvBuffer), 0);
cout << "Recv Data! Len = " << recvLen << endl;
// recvBufferLen = 1000

위 코드처럼 클라이언트와 서버간의 다루는 동일한 데이터의 크기가 다를 수 있다.

서로 SendBuffer와 RecvBuffer의 크기가 다르기 때문이다.


VS 시작 설정

'Server > C++' 카테고리의 다른 글

UDP 소켓 프로그래밍  (0) 2024.08.06
TCP vs UDP  (0) 2024.08.06
TypeCase  (0) 2024.08.06
메모리 풀링  (0) 2024.08.05
STL Allocator  (0) 2024.08.05