이벤트 셀렉트 기반 멀티플렉싱 서버는 윈도우 소켓 API(WSA)를 활용하여 소켓의 입출력 이벤트를 효율적으로 처리하는 방법입니다. 기존의 select 방식에서 확장된 구조로, 소켓과 이벤트 객체를 매핑하여 더욱 효율적인 동작을 제공합니다.
1. 기존 select 방식과의 차이점
1.1 select 방식
- FD_SET 배열을 사용하여 소켓 감시.
- 최대 64개의 소켓만 처리 가능.
- 소켓 변화 감지 후, 다시 모든 소켓을 순회하며 작업 수행.
- 단점:
- 소켓이 많아질수록 성능 저하.
- 매번 전체 소켓을 순회하는 비효율성.
1.2 이벤트 셀렉트 방식
- WSA 이벤트 객체를 사용하여 소켓과 매핑.
- 각 소켓의 특정 이벤트(연결, 데이터 수신 등)에만 반응.
- 특정 이벤트가 발생한 소켓만 처리하므로 불필요한 순회 제거.
- 장점:
- 스레드 없이도 효율적인 비동기 입출력 구현 가능.
- 이벤트와 소켓 매핑으로 명확한 처리 가능.
2. 이벤트 셀렉트 서버의 주요 구성
2.1 이벤트 배열과 소켓 배열
SOCKET g_hSocket; // 서버 리슨 소켓
int g_nListIndex; // 소켓 및 이벤트 배열 인덱스
WSAEVENT g_aListEvent[WSA_MAXIMUM_WAIT_EVENTS]; // 이벤트 객체 배열
SOCKET g_aListSocket[WSA_MAXIMUM_WAIT_EVENTS]; // 소켓 배열
설명
- WSA_MAXIMUM_WAIT_EVENTS:
- 감시 가능한 최대 소켓 개수 (64개).
- g_aListEvent:
- 각 소켓에 대한 이벤트 객체를 저장.
- g_aListSocket:
- 각 이벤트와 매핑되는 소켓 저장.
2.2 소켓 이벤트 대기
이벤트 셀렉트 방식에서는 WSAWaitForMultipleEvents를 통해 이벤트를 감시:
DWORD dwIndex;
while (TRUE)
{
dwIndex = ::WSAWaitForMultipleEvents(
g_nListIndex + 1, // 감시할 이벤트 개수
g_aListEvent, // 이벤트 배열
FALSE, // 하나의 이벤트만 대기
100, // 타임아웃 (100ms)
FALSE // 호출 스레드 상태 변경 안 함
);
if (dwIndex == WSA_WAIT_FAILED)
continue;
// 이벤트 처리
}
설명
- WSAWaitForMultipleEvents:
- 등록된 이벤트 객체를 감시.
- 이벤트가 발생하면 해당 소켓의 인덱스를 반환.
- 감시 결과 확인:
- 반환값으로 이벤트가 발생한 소켓을 식별.
- 감시 중 오류 발생 시 WSA_WAIT_FAILED 반환.
2.3 이벤트 처리
if (::WSAEnumNetworkEvents(
g_aListSocket[dwIndex], // 이벤트 발생 소켓
g_aListEvent[dwIndex], // 이벤트 객체
&netEvent // 발생한 네트워크 이벤트 정보
) == SOCKET_ERROR)
continue;
설명
- WSAEnumNetworkEvents:
- 이벤트가 발생한 소켓의 상태와 발생한 이벤트 정보를 확인.
- 이벤트 종류:
- FD_ACCEPT: 클라이언트 연결 요청.
- FD_READ: 데이터 수신 가능.
- FD_CLOSE: 소켓 종료.
2.4 클라이언트 연결 및 데이터 처리
클라이언트 연결 처리
if (netEvent.lNetworkEvents & FD_ACCEPT)
{
SOCKADDR_IN clientAddr = {0};
int addrLen = sizeof(clientAddr);
SOCKET hClient = ::accept(g_hSocket, (SOCKADDR*)&clientAddr, &addrLen);
if (hClient != INVALID_SOCKET)
{
WSAEVENT hEvent = ::WSACreateEvent(); // 이벤트 객체 생성
g_aListEvent[g_nListIndex] = hEvent; // 이벤트 등록
g_aListSocket[g_nListIndex] = hClient; // 소켓 등록
g_nListIndex++;
}
}
데이터 수신 및 전송
if (netEvent.lNetworkEvents & FD_READ)
{
char szBuffer[1024] = {0};
int nReceive = ::recv(g_aListSocket[dwIndex], szBuffer, sizeof(szBuffer), 0);
if (nReceive > 0)
{
// 수신된 데이터를 모든 클라이언트에게 전송
for (int i = 0; i < g_nListIndex; ++i)
send(g_aListSocket[i], szBuffer, nReceive, 0);
}
else
{
// 연결 종료
::closesocket(g_aListSocket[dwIndex]);
::WSACloseEvent(g_aListEvent[dwIndex]);
g_aListSocket[dwIndex] = INVALID_SOCKET;
}
}
설명
- FD_ACCEPT:
- 새로운 클라이언트 연결을 수락(accept).
- 연결된 소켓과 이벤트 객체를 배열에 등록.
- FD_READ:
- 클라이언트로부터 데이터 수신.
- 수신된 데이터를 모든 클라이언트에 브로드캐스팅.
- FD_CLOSE:
- 소켓 종료 처리 및 이벤트 객체 삭제.
3. 이벤트 셀렉트 방식의 장점
3.1 효율적인 이벤트 감지
- 소켓 이벤트와 이벤트 객체가 매핑되어 불필요한 순회 제거.
- 이벤트가 발생한 소켓만 처리하므로 성능 최적화.
3.2 단일 스레드로 구현 가능
- 스레드 생성 및 관리 비용이 없음.
- 모든 작업이 단일 스레드에서 동작.
3.3 명확한 이벤트 기반 처리
- 각 소켓의 이벤트를 개별적으로 처리 가능.
- 코드 구조가 깔끔하고 유지보수 용이.
4. 코드 흐름
- 초기화:
- 리슨 소켓 생성 및 이벤트 객체 초기화.
- 이벤트 감시:
- WSAWaitForMultipleEvents로 소켓 이벤트 대기.
- 이벤트 처리:
- 클라이언트 연결(FD_ACCEPT), 데이터 수신(FD_READ), 소켓 종료(FD_CLOSE) 처리.
- 종료:
- 모든 소켓 및 이벤트 객체 해제.
5. 한계 및 개선 방안
5.1 소켓 수 제한
- 최대 64개의 소켓만 감시 가능(WSA_MAXIMUM_WAIT_EVENTS 제한).
- 개선:
- **IOCP (I/O Completion Port)**로 대규모 클라이언트 처리.
5.2 이벤트 관리
- 이벤트 객체와 소켓의 매핑 관리가 복잡해질 수 있음.
- 개선:
- 배열 대신 동적 자료구조 활용(예: std::map).
이벤트 셀렉트 기반 멀티플렉싱 서버는 효율적인 소켓 관리와 명확한 이벤트 기반 처리를 제공합니다. 하지만 제한된 소켓 수와 복잡한 매핑 관리가 단점으로 작용할 수 있습니다. 소규모 네트워크 애플리케이션에는 적합하지만, 대규모 서버에는 IOCP와 같은 고급 기술이 더 적합합니다.
'프로그래밍 > 소켓 프로그래밍 입문' 카테고리의 다른 글
네트워크 송수신에서의 장애와 대응 방안 (0) | 2025.01.20 |
---|---|
소켓 기반 파일 송/수신의 구조와 원리 (0) | 2025.01.20 |
I/O 멀티플렉싱 채팅 서버 구현 (0) | 2025.01.17 |
I/O 멀티플렉싱 서버의 개념과 동작 원리 (0) | 2025.01.17 |
TCP 연결의 비정상 종료와 좀비 세션 문제 (0) | 2025.01.16 |