| 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;
}
'C++ > Win32' 카테고리의 다른 글
| [Win32] IOCP Worker Thread 다중화 (0) | 2025.12.27 |
|---|---|
| [Win32] IOCP 서버 (0) | 2025.12.27 |
| [Win32] 네트워크 메시지 경계 처리 - Length Prefix (0) | 2025.12.27 |
| [Win32] 서버 확장 : Thread 도입 (0) | 2025.12.27 |
| [Win32] Windows Socket 기초 - recv 무한 대기 방지 (0) | 2025.12.27 |