소켓 프로그래밍에서 동시에 여러 클라이언트의 요청을 처리할 필요가 있을 때 select() 함수를 활용한 I/O 멀티플렉싱 기법이다.
select()
여러 소켓의 상태를 동시에 감시하여 읽기, 쓰기 또는 예외 조건이 발생한 소켓을 찾아낸다.
FD_ZERO(), FD_SET() 함수를 사용하여 감시할 소켓들을 집합에 추가한다.

Select Model 흐름
1. FD_SET을 사용해 감시할 소켓들을 FD_ZERO()로 초기화 후 등록
2. select() 함수로 이벤트 발생 대기
3. select() 함수의 반환값을 확인하여 이벤트 발생 확인
4. FD_ISSET()을 이용해 이벤트가 발생한 소켓 검사
5. 이벤트에 맞게 FD_ACCEPT, FD_READ, FD_CLOSE 처리
6. 초기화 설정 반복
예제
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>
#pragma comment(lib, "ws2_32.lib")
int main()
{
WSADATA wsaData{};
if (::WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
std::cerr << "Winsock 초기화 실패" << std::endl;
return 1;
}
// 서버 소켓 생성
SOCKET listenSocket{ ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) };
if (listenSocket == INVALID_SOCKET)
{
std::cerr << "소켓 생성 실패: " << ::WSAGetLastError() << std::endl;
::WSACleanup();
return 1;
}
sockaddr_in serverAddr{};
ZeroMemory(&serverAddr, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = INADDR_ANY;
serverAddr.sin_port = ::htons(7777);
;
// 소켓 바인딩
if (::bind(listenSocket, reinterpret_cast<sockaddr*>( &serverAddr ), sizeof(serverAddr)) == SOCKET_ERROR)
{
std::cerr << "바인드 실패: " << ::WSAGetLastError() << std::endl;
::closesocket(listenSocket);
::WSACleanup();
return 1;
}
// 클라이언트 연결 대기
if (::listen(listenSocket, SOMAXCONN) == SOCKET_ERROR)
{
std::cerr << "리스닝 실패: " << ::WSAGetLastError() << std::endl;
::closesocket(listenSocket);
::WSACleanup();
return 1;
}
// select()를 위한 파일 디스크립터 집합
FD_SET fdSet{};
FD_ZERO(&fdSet);
FD_SET(listenSocket, &fdSet);
SOCKET fdMax{ listenSocket };
while (true)
{
FD_SET readSet{ fdSet }; // 매 루프마다 읽기 집합 초기화
// select() 호출 (타임아웃 없이 블로킹)
const int ret{ ::select(0, &readSet, nullptr, nullptr, nullptr) };
if (ret == SOCKET_ERROR)
{
std::cerr << "select() 실패: " << ::WSAGetLastError() << std::endl;
break;
}
// 이벤트가 발생한 소켓 처리
for (SOCKET i = 0; i <= fdMax; ++i)
{
if (FD_ISSET(i, &readSet))
{
if (i == listenSocket)
{
// 새로운 클라이언트 연결 수락
sockaddr_in clientAddr;
int clientAddrSize = sizeof(clientAddr);
SOCKET clientSocket{ ::accept(listenSocket, (sockaddr*)&clientAddr, &clientAddrSize) };
if (clientSocket == INVALID_SOCKET)
{
std::cerr << "accept() 실패: " << ::WSAGetLastError() << std::endl;
}
else
{
FD_SET(clientSocket, &fdSet);
if (clientSocket > fdMax)
{
fdMax = clientSocket;
}
std::cout << "새로운 클라이언트 연결 (SOCKET: " << clientSocket << ")" << std::endl;
}
}
else
{
// 클라이언트로부터 데이터 수신
char buffer[1024]{};
const int bytesReceived{ ::recv(i, buffer, sizeof(buffer), 0) };
if (bytesReceived <= 0)
{
if (bytesReceived == 0)
{
std::cout << "클라이언트 연결 종료 (SOCKET: " << i << ")" << std::endl;
}
else
{
std::cerr << "recv() 실패: " << ::WSAGetLastError() << std::endl;
}
::closesocket(i);
FD_CLR(i, &fdSet);
}
else
{
// 받은 데이터를 클라이언트에게 에코
std::cout << "받은 데이터: " << buffer << std::endl;
int bytesSent = send(i, buffer, bytesReceived, 0);
if (bytesSent == SOCKET_ERROR)
{
std::cerr << "send() 실패: " << ::WSAGetLastError() << std::endl;
::closesocket(i);
FD_CLR(i, &fdSet);
}
}
}
}
}
}
// 소켓 및 Winsock 정리
::closesocket(listenSocket);
::WSACleanup();
return 0;
}'Socket 관련' 카테고리의 다른 글
| OVERLAPPED Event Model (0) | 2024.06.09 |
|---|---|
| WSAEventSelect Model (0) | 2024.06.08 |
| Windows 소켓 통신 (2) | 2024.06.06 |
| winsock2 라이브러리 추가하기 (0) | 2024.06.05 |
| TCP, UDP (0) | 2024.06.02 |