C++/Win32

[Win32] select 서버

powergirl 2025. 12. 27. 15:40
  Thread model select model
Thread 클라이엉트 수만큼 1개
recv 블로킹 준비된 소켓만
구조 직관적 상태 기반
확장성 낮음 중간

select 는 recv 를 대신하는 게 아니라, recv 를 호출해도 되는 타이밍을 알려줌

 

select 서버 전체 흐름
while (true)
{
    select(...)
    for (읽기 가능한 소켓)
    {
        if (아직 길이 안 받음)
            recv 길이
        else
            recv 데이터
    }
}

 

 

select 서버에 필요한 구조체
struct ClientInfo
{
    SOCKET socket;
    int expectedSize;   // 다음에 받아야 할 데이터 크기
    int receivedSize;   // 지금까지 받은 크기
    char buffer[1024];
};

 

 

select 준비
fd_set readSet;
FD_ZERO(&readSet);
FD_SET(listenSock, &readSet);

for (auto& c : clients)
{
    FD_SET(c.socket, &readSet);
}

select(0, &readSet, NULL, NULL, NULL);

 

 

새 클라이언트 accept
if (FD_ISSET(listenSock, &readSet))
{
    SOCKET clientSock = accept(listenSock, NULL, NULL);

    ClientInfo ci;
    ci.socket = clientSock;
    ci.expectedSize = 0;
    ci.receivedSize = 0;

    clients.push_back(ci);
}

 

 

길이 수신
if (ci.expectedSize == 0)
{
    int ret = recv(
        ci.socket,
        (char*)&ci.expectedSize + ci.receivedSize,
        sizeof(int) - ci.receivedSize,
        0
    );

    if (ret <= 0)
    {
        // 연결 종료 처리
        continue;
    }

    ci.receivedSize += ret;

    if (ci.receivedSize == sizeof(int))
    {
        ci.expectedSize = ntohl(ci.expectedSize);
        ci.receivedSize = 0;
    }
}

 

 

데이터 수신
else
{
    int ret = recv(
        ci.socket,
        ci.buffer + ci.receivedSize,
        ci.expectedSize - ci.receivedSize,
        0
    );

    if (ret <= 0)
    {
        // 연결 종료 처리
        continue;
    }

    ci.receivedSize += ret;

    if (ci.receivedSize == ci.expectedSize)
    {
        ci.buffer[ci.expectedSize] = '\0';
        std::cout << "메시지: " << ci.buffer << std::endl;

        // 상태 초기화
        ci.expectedSize = 0;
        ci.receivedSize = 0;
    }
}

 

 

 

 

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

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

#define SERVER_PORT 9000
#define MAX_BUF 1024

// ================================
// 클라이언트 상태 구조체
// ================================
struct ClientInfo
{
    SOCKET socket;

    int expectedSize;     // 다음에 받아야 할 데이터 크기
    int receivedSize;     // 현재까지 받은 크기

    char buffer[MAX_BUF];
};

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 << "select 서버 시작 (Port: " << SERVER_PORT << ")\n";

    std::vector<ClientInfo> clients;

    while (true)
    {
        fd_set readSet;
        FD_ZERO(&readSet);

        FD_SET(listenSock, &readSet);
        SOCKET maxSock = listenSock;

        for (auto& c : clients)
        {
            FD_SET(c.socket, &readSet);
            if (c.socket > maxSock)
                maxSock = c.socket;
        }

        int ret = select(0, &readSet, NULL, NULL, NULL);
        if (ret <= 0)
            continue;

        // ================================
        // 새 클라이언트 접속
        // ================================
        if (FD_ISSET(listenSock, &readSet))
        {
            SOCKET clientSock = accept(listenSock, NULL, NULL);

            ClientInfo ci;
            ci.socket = clientSock;
            ci.expectedSize = 0;
            ci.receivedSize = 0;

            clients.push_back(ci);

            std::cout << "[접속] socket=" << clientSock << "\n";
        }

        // ================================
        // 클라이언트 데이터 처리
        // ================================
        for (int i = 0; i < clients.size(); )
        {
            ClientInfo& ci = clients[i];

            if (!FD_ISSET(ci.socket, &readSet))
            {
                ++i;
                continue;
            }

            // ----------------------------
            // 1️⃣ 길이 수신 단계
            // ----------------------------
            if (ci.expectedSize == 0)
            {
                int ret = recv(
                    ci.socket,
                    ((char*)&ci.expectedSize) + ci.receivedSize,
                    sizeof(int) - ci.receivedSize,
                    0
                );

                if (ret <= 0)
                {
                    std::cout << "[종료] socket=" << ci.socket << "\n";
                    closesocket(ci.socket);
                    clients.erase(clients.begin() + i);
                    continue;
                }

                ci.receivedSize += ret;

                if (ci.receivedSize == sizeof(int))
                {
                    ci.expectedSize = ntohl(ci.expectedSize);

                    // 방어 코드
                    if (ci.expectedSize <= 0 || ci.expectedSize > MAX_BUF)
                    {
                        closesocket(ci.socket);
                        clients.erase(clients.begin() + i);
                        continue;
                    }

                    ci.receivedSize = 0;
                }
            }
            // ----------------------------
            // 2️⃣ 데이터 수신 단계
            // ----------------------------
            else
            {
                int ret = recv(
                    ci.socket,
                    ci.buffer + ci.receivedSize,
                    ci.expectedSize - ci.receivedSize,
                    0
                );

                if (ret <= 0)
                {
                    std::cout << "[종료] socket=" << ci.socket << "\n";
                    closesocket(ci.socket);
                    clients.erase(clients.begin() + i);
                    continue;
                }

                ci.receivedSize += ret;

                if (ci.receivedSize == ci.expectedSize)
                {
                    ci.buffer[ci.expectedSize] = '\0';

                    std::cout << "[메시지] "
                              << ci.buffer << "\n";

                    // 상태 초기화
                    ci.expectedSize = 0;
                    ci.receivedSize = 0;
                }
            }

            ++i;
        }
    }

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