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 |