C++/Win32

[Win32] 서버 확장 : Thread 도입

powergirl 2025. 12. 27. 14:20
서버 스레드 설계
main thread >> listen / accept
client thread >> recv / send / disconnect

 

 

스레드 함수
DWORD WINAPI ClientThread(LPVOID lpParam)
{
    ClientInfo* info = (ClientInfo*)lpParam;
    SOCKET clientSocket = info->socket;
    SOCKADDR_IN clientAddr = info->addr;

    printf("클라이언트 처리 시작: %s:%d\n",
        inet_ntoa(clientAddr.sin_addr),
        ntohs(clientAddr.sin_port));

    int timeout = 5000;
    setsockopt(clientSocket, SOL_SOCKET, SO_RCVTIMEO,
        (char*)&timeout, sizeof(timeout));

    while (true)
    {
        int value = 0;
        int ret = recv(clientSocket, (char*)&value, sizeof(int), 0);

        if (ret > 0)
        {
            printf("[%s] %d 수신\n",
                inet_ntoa(clientAddr.sin_addr), value);
        }
        else if (ret == 0)
        {
            printf("[%s] 정상 종료\n",
                inet_ntoa(clientAddr.sin_addr));
            break;
        }
        else
        {
            int err = WSAGetLastError();
            if (err == WSAETIMEDOUT)
            {
                continue;
            }
            else
            {
                printf("[%s] recv 에러: %d\n",
                    inet_ntoa(clientAddr.sin_addr), err);
                break;
            }
        }
    }

    closesocket(clientSocket);
    delete info;

    printf("클라이언트 스레드 종료\n");
    return 0;
}

 

메인 서버 코드 (accept + 스레드 생성)
while (true)
{
    SOCKADDR_IN clientAddr = {};
    int addrSize = sizeof(clientAddr);

    SOCKET clientSocket =
        accept(listenSocket, (SOCKADDR*)&clientAddr, &addrSize);

    ClientInfo* info = new ClientInfo;
    info->socket = clientSocket;
    info->addr = clientAddr;

    HANDLE hThread = CreateThread(
        nullptr,
        0,
        ClientThread,
        info,
        0,
        nullptr
    );

    CloseHandle(hThread); // 핸들 누수 방지
}

 

구조체로 전달
struct ClientInfo
{
    SOCKET socket;
    SOCKADDR_IN addr;
};

 

 

 

서버
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#include <process.h>
#include <iostream>

#pragma comment(lib, "ws2_32.lib")

#define SERVER_PORT 9000
#define BUF_SIZE 1024

// ================================
// 클라이언트 정보 구조체 (실무 정석)
// ================================
struct ClientInfo
{
    SOCKET socket;
    SOCKADDR_IN addr;
};

// ================================
// 클라이언트 처리 스레드
// ================================
unsigned int __stdcall ClientThread(void* arg)
{
    ClientInfo* pClient = (ClientInfo*)arg;

    SOCKET clientSock = pClient->socket;
    SOCKADDR_IN clientAddr = pClient->addr;

    char ip[32];
    inet_ntop(AF_INET, &clientAddr.sin_addr, ip, sizeof(ip));

    std::cout << "[접속] IP: " << ip
              << " PORT: " << ntohs(clientAddr.sin_port) << std::endl;

    char buf[BUF_SIZE];

    while (true)
    {
        int recvBytes = recv(clientSock, buf, BUF_SIZE, 0);

        if (recvBytes <= 0)
        {
            std::cout << "[종료] IP: " << ip << std::endl;
            break;
        }

        // 받은 데이터 출력
        std::cout << "[수신] (" << ip << ") "
                  << std::string(buf, recvBytes) << std::endl;
    }

    closesocket(clientSock);
    delete pClient;   // ⭐ 반드시 힙 해제

    return 0;
}

// ================================
// main
// ================================
int main()
{
    WSADATA wsa;
    WSAStartup(MAKEWORD(2, 2), &wsa);

    SOCKET listenSock = socket(AF_INET, SOCK_STREAM, 0);

    SOCKADDR_IN serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    serverAddr.sin_port = htons(SERVER_PORT);

    bind(listenSock, (SOCKADDR*)&serverAddr, sizeof(serverAddr));
    listen(listenSock, SOMAXCONN);

    std::cout << "서버 시작 (Port: " << SERVER_PORT << ")" << std::endl;

    while (true)
    {
        SOCKADDR_IN clientAddr;
        int addrLen = sizeof(clientAddr);

        SOCKET clientSock = accept(
            listenSock,
            (SOCKADDR*)&clientAddr,
            &addrLen
        );

        if (clientSock == INVALID_SOCKET)
        {
            continue;
        }

        // ================================
        // 클라이언트 정보 힙 할당
        // ================================
        ClientInfo* pClient = new ClientInfo;
        pClient->socket = clientSock;
        pClient->addr = clientAddr;

        _beginthreadex(
            NULL,
            0,
            ClientThread,
            pClient,
            0,
            NULL
        );
    }

    closesocket(listenSock);
    WSACleanup();
    return 0;
}