C++/Win32

[Win32] IOCP 기반 + 길이 프레임 + Worker Thread 풀

powergirl 2025. 12. 27. 19:14
IOCP
+ OVERLAPPED 기반 비동기 recv/send
+ 길이(4byte) + 데이터 프레임
+ CPU 코어 기반 Worker Thread

 

Win32 서버 구조
main
 ├─ WSAStartup
 ├─ listen socket 생성
 ├─ IOCP 생성
 ├─ Worker Thread 풀 생성
 ├─ accept loop
 │    ├─ ClientContext 생성
 │    ├─ CreateIoCompletionPort (socket 등록)
 │    └─ 최초 WSARecv 요청
 └─ 종료 처리

 

 


전체 구조
MFC Project
 ├─ CMainDlg / CMainFrame (UI)
 ├─ IocpServer.h
 ├─ IocpServer.cpp
 └─ ClientContext.h

 

 

ClientContext.h
#pragma once
#include <winsock2.h>
#include <mswsock.h>

struct ClientContext
{
    SOCKET socket;
    OVERLAPPED overlapped;
    WSABUF wsaBuf;

    char buffer[4096];

    int recvBytes;
    int expectedBytes;

    ClientContext()
    {
        ZeroMemory(this, sizeof(ClientContext));
    }
};

순수 Win32 구조체 유지

 

IocpServer.h
#pragma once
#include <winsock2.h>
#include <vector>
#include "ClientContext.h"

class CIocpServer
{
public:
    CIocpServer();
    ~CIocpServer();

    bool Start(int port);
    void Stop();

private:
    static DWORD WINAPI WorkerThread(LPVOID param);

    void AcceptLoop();
    void CloseAllClients();

private:
    SOCKET m_listenSocket;
    HANDLE m_hIOCP;

    std::vector<HANDLE> m_workerThreads;
    bool m_running;
};

 

 

IocpServer.cpp
#include "IocpServer.h"
#include <process.h>

#define IOCP_EXIT_KEY 0xFFFFFFFF

CIocpServer::CIocpServer()
    : m_listenSocket(INVALID_SOCKET),
      m_hIOCP(NULL),
      m_running(false)
{
}

// 서버 시작
bool CIocpServer::Start(int port)
{
    WSADATA wsa;
    WSAStartup(MAKEWORD(2, 2), &wsa);

    m_listenSocket = socket(AF_INET, SOCK_STREAM, 0);

    SOCKADDR_IN addr{};
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = INADDR_ANY;

    bind(m_listenSocket, (SOCKADDR*)&addr, sizeof(addr));
    listen(m_listenSocket, SOMAXCONN);

    m_hIOCP = CreateIoCompletionPort(
        INVALID_HANDLE_VALUE,
        NULL,
        0,
        0
    );

    SYSTEM_INFO si;
    GetSystemInfo(&si);
    int workerCount = si.dwNumberOfProcessors * 2;

    for (int i = 0; i < workerCount; i++)
    {
        HANDLE hThread = CreateThread(
            NULL,
            0,
            WorkerThread,
            this,
            0,
            NULL
        );

        m_workerThreads.push_back(hThread);
    }

    m_running = true;

    AcceptLoop();
    return true;
}

// accept 루프
void CIocpServer::AcceptLoop()
{
    while (m_running)
    {
        SOCKET client = accept(m_listenSocket, NULL, NULL);
        if (client == INVALID_SOCKET)
            break;

        ClientContext* ctx = new ClientContext;
        ctx->socket = client;

        ctx->expectedBytes = sizeof(int);
        ctx->recvBytes = 0;

        ctx->wsaBuf.buf = ctx->buffer;
        ctx->wsaBuf.len = sizeof(int);

        CreateIoCompletionPort(
            (HANDLE)client,
            m_hIOCP,
            (ULONG_PTR)ctx,
            0
        );

        DWORD flags = 0;
        WSARecv(
            client,
            &ctx->wsaBuf,
            1,
            NULL,
            &flags,
            &ctx->overlapped,
            NULL
        );
    }
}

// Worker Thread
DWORD WINAPI CIocpServer::WorkerThread(LPVOID param)
{
    CIocpServer* server = (CIocpServer*)param;

    while (true)
    {
        DWORD bytes;
        ULONG_PTR key;
        OVERLAPPED* ov;

        GetQueuedCompletionStatus(
            server->m_hIOCP,
            &bytes,
            &key,
            &ov,
            INFINITE
        );

        if (key == IOCP_EXIT_KEY)
            break;

        ClientContext* ctx = (ClientContext*)key;

        if (bytes == 0)
        {
            closesocket(ctx->socket);
            delete ctx;
            continue;
        }

        ctx->recvBytes += bytes;

        if (ctx->recvBytes < ctx->expectedBytes)
        {
            ctx->wsaBuf.buf = ctx->buffer + ctx->recvBytes;
            ctx->wsaBuf.len = ctx->expectedBytes - ctx->recvBytes;

            WSARecv(ctx->socket, &ctx->wsaBuf, 1, NULL, 0, &ctx->overlapped, NULL);
            continue;
        }

        if (ctx->expectedBytes == sizeof(int))
        {
            int size;
            memcpy(&size, ctx->buffer, sizeof(int));
            ctx->expectedBytes = ntohl(size);
            ctx->recvBytes = 0;

            ctx->wsaBuf.buf = ctx->buffer;
            ctx->wsaBuf.len = ctx->expectedBytes;

            WSARecv(ctx->socket, &ctx->wsaBuf, 1, NULL, 0, &ctx->overlapped, NULL);
        }
        else
        {
            ctx->buffer[ctx->expectedBytes] = '\0';
            // 메시지 처리 지점

            ctx->expectedBytes = sizeof(int);
            ctx->recvBytes = 0;

            ctx->wsaBuf.buf = ctx->buffer;
            ctx->wsaBuf.len = sizeof(int);

            WSARecv(ctx->socket, &ctx->wsaBuf, 1, NULL, 0, &ctx->overlapped, NULL);
        }
    }

    return 0;
}

// 서버 종료
void CIocpServer::Stop()
{
    m_running = false;
    closesocket(m_listenSocket);

    for (size_t i = 0; i < m_workerThreads.size(); i++)
    {
        PostQueuedCompletionStatus(
            m_hIOCP,
            0,
            IOCP_EXIT_KEY,
            NULL
        );
    }

    WaitForMultipleObjects(
        (DWORD)m_workerThreads.size(),
        m_workerThreads.data(),
        TRUE,
        INFINITE
    );

    CloseHandle(m_hIOCP);
    WSACleanup();
}

 

 

MFC에서 사용하는 방법
CIocpServer m_server;

BOOL CMainDlg::OnInitDialog()
{
    m_server.Start(9000);
    return TRUE;
}

void CMainDlg::OnDestroy()
{
    m_server.Stop();
    CDialogEx::OnDestroy();
}

 

 

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

[Win32] IOCP Worker Thread 다중화  (0) 2025.12.27
[Win32] IOCP 서버  (0) 2025.12.27
[Win32] select 서버  (0) 2025.12.27
[Win32] 네트워크 메시지 경계 처리 - Length Prefix  (0) 2025.12.27
[Win32] 서버 확장 : Thread 도입  (0) 2025.12.27