서버 스레드 설계
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;
}