WSAEventSelect Model은 비동기 소켓 프로그래밍을 지원하는 이벤트 기반 모델이다.
연결요청, 데이터 수신, 연결 종료등을 감지할 수 있도록 WSAEventSelect()함수로 WSAEVENT 객체를 소켓과 연결하고
WSAWaitForMultipleEvents() 함수를 이용해 여러 소켓의 이벤트를 감시한다.
FD_SET을 반복 검사하는 Select Model과 달리 이벤트가 발생할 때만 반응하여 성능 향상이 있으나
Select Model과 같이 최대 64개 까지 감시가 가능하다.
WSAEventSelect 흐름
1. WSAEventSelect()를 사용해 소켓과 이벤트 객체 연결
2. WSAWaitForMultipleEvents()로 이벤트가 발생할 때까지 대기
3. WSAEnumNetworkEvents()로 어떤 이벤트가 발생했는지 확인
4. 이벤트에 맞는 FD_ACCEPT, FD_READ, FD_CLOSE 처리
5. WSAResetEvent()를 통해 이벤트 상태 초기화
예제
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>
#include <vector>
#pragma comment(lib, "ws2_32.lib")
struct Client
{
SOCKET socket{};
HANDLE event{};
};
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;
}
// listenSocket에 대한 이벤트 생성 및 등록
HANDLE listenEvent{ ::WSACreateEvent() };
if (::WSAEventSelect(listenSocket, listenEvent, FD_ACCEPT | FD_CLOSE) == SOCKET_ERROR)
{
std::cerr << "listenSocket 이벤트 등록 실패: " << ::WSAGetLastError() << std::endl;
::closesocket(listenSocket);
::WSACleanup();
return 1;
}
// 클라이언트 소켓과 이벤트를 저장할 벡터
std::vector<Client> clientVec;
while (true)
{
// 이벤트 배열: 첫번째는 listenEvent 그 뒤로 클라이언트 이벤트들
std::vector<HANDLE> eventVec;
eventVec.push_back(listenEvent);
for (const auto& client : clientVec)
{
eventVec.push_back(client.event);
}
// 모든 이벤트가 발생할 때까지 대기
DWORD waitResult{ ::WSAWaitForMultipleEvents(static_cast<DWORD>( eventVec.size() ), eventVec.data(), FALSE, WSA_INFINITE, FALSE) };
if (waitResult == WSA_WAIT_FAILED)
{
std::cerr << "WSAWaitForMultipleEvents 실패: " << ::WSAGetLastError() << std::endl;
break;
}
const int eventIndex{ static_cast<int>( waitResult - WSA_WAIT_EVENT_0 ) };
// listenSocket 이벤트가 발생한 경우
if (eventIndex == 0)
{
WSANETWORKEVENTS networkEvent{};
if (::WSAEnumNetworkEvents(listenSocket, listenEvent, &networkEvent) == SOCKET_ERROR)
{
std::cerr << "WSAEnumNetworkEvents (listenSocket) 실패: " << ::WSAGetLastError() << std::endl;
break;
}
// accept
if (networkEvent.lNetworkEvents & FD_ACCEPT)
{
sockaddr_in clientAddr{};
int clientAddrSize{ sizeof(clientAddr) };
SOCKET clientSocket{ ::accept(listenSocket, reinterpret_cast<sockaddr*>( &clientAddr ), &clientAddrSize) };
if (clientSocket == INVALID_SOCKET)
{
std::cerr << "accept() 실패: " << ::WSAGetLastError() << std::endl;
}
else
{
// 클라이언트 소켓에 대한 이벤트 생성 및 등록
HANDLE clientEvent = ::WSACreateEvent();
if (::WSAEventSelect(clientSocket, clientEvent, FD_READ | FD_CLOSE) == SOCKET_ERROR)
{
std::cerr << "클라이언트 이벤트 등록 실패: " << ::WSAGetLastError() << std::endl;
::closesocket(clientSocket);
::WSACloseEvent(clientEvent);
}
else
{
Client info { clientSocket, clientEvent };
clientVec.push_back(info);
std::cout << "새로운 클라이언트 연결 (SOCKET: " << clientSocket << ")" << std::endl;
}
}
}
if (networkEvent.lNetworkEvents & FD_CLOSE)
{
std::cerr << "listenSocket FD_CLOSE 이벤트 발생" << std::endl;
break;
}
::WSAResetEvent(listenEvent);
}
// 클라이언트 소켓 이벤트 처리
else
{
// eventIndex 0은 listenEvent이므로, 클라이언트 인덱스는 eventIndex - 1
int clientIndex{ eventIndex - 1 };
if (clientIndex < 0 || clientIndex >= static_cast<int>(clientVec.size()))
{
continue;
}
Client& client{ clientVec[clientIndex] };
WSANETWORKEVENTS networkEvents{};
if (::WSAEnumNetworkEvents(client.socket, client.event, &networkEvents) == SOCKET_ERROR)
{
std::cerr << "WSAEnumNetworkEvents (클라이언트) 실패: " << ::WSAGetLastError() << std::endl;
continue;
}
// Read
if (networkEvents.lNetworkEvents & FD_READ)
{
char buffer[1024]{};
int bytesReceived = ::recv(client.socket, buffer, sizeof(buffer), 0);
if (bytesReceived <= 0)
{
if (bytesReceived == 0)
{
std::cout << "클라이언트 연결 종료 (SOCKET: " << client.socket << ")" << std::endl;
}
else
{
std::cerr << "recv() 실패: " << ::WSAGetLastError() << std::endl;
}
::closesocket(client.socket);
::WSACloseEvent(client.event);
clientVec.erase(clientVec.begin() + clientIndex);
continue;
}
else
{
std::cout << "받은 데이터: " << buffer << std::endl;
int bytesSent = ::send(client.socket, buffer, bytesReceived, 0);
if (bytesSent == SOCKET_ERROR)
{
std::cerr << "send() 실패: " << ::WSAGetLastError() << std::endl;
::closesocket(client.socket);
::WSACloseEvent(client.event);
clientVec.erase(clientVec.begin() + clientIndex);
continue;
}
}
}
if (networkEvents.lNetworkEvents & FD_CLOSE)
{
std::cout << "클라이언트 연결 종료 (SOCKET: " << client.socket << ")" << std::endl;
::closesocket(client.socket);
::WSACloseEvent(client.event);
clientVec.erase(clientVec.begin() + clientIndex);
continue;
}
::WSAResetEvent(client.event);
}
}
// 모든 클라이언트 정리
for (auto& client : clientVec)
{
::closesocket(client.socket);
::WSACloseEvent(client.event);
}
::WSACloseEvent(listenEvent);
::closesocket(listenSocket);
::WSACleanup();
return 0;
}
'Socket 관련' 카테고리의 다른 글
OVERLAPPED Callback Model (0) | 2024.06.10 |
---|---|
OVERLAPPED Event Model (0) | 2024.06.09 |
Select Model (0) | 2024.06.07 |
Windows 소켓 통신 (2) | 2024.06.06 |
winsock2 라이브러리 추가하기 (0) | 2024.06.05 |