소켓 작업을 비동기 방식으로 실행하고 이벤트 객체를 이용해 완료 여부를 확인하는 방식의 모델이다.
비동기 방식으로 I/O작업이 완료될 때까지 블로킹 되지 않으며 WSAOVERLAPPED 구조체와 WSAEVENT를 사용하여 I/O 작업 완료 상태를 이벤트 기반으로 확인한다.
Select, WSAEventSelect Model 보다는 성능이 좋지만 소켓당 하나의 이벤트 객체가 필요하다.
OVERLAPPED Event 흐름
1. 소켓 생성 및 이벤트 객체 WSAEVENT 생성 -> WSACreateEvent()
2. WSAOVERLAPPED 구조체를 초기화하고 비동기 I/O 작업 요청 -> WSASend(), WSARecv()
3. WSAWaitForMultipleEvents()를 사용해 이벤트 발생 대기
4. WSAGetOverlappedResult()를 호출해 I/O 작업 결과 확인
5. 이벤트 재설정 후 반복 -> WSAResetEvent()
예제
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>
#include <vector>
#pragma comment(lib, "ws2_32.lib")
constexpr int PORT{ 7777 };
constexpr int BUFFER_SIZE{ 1024 };
// 클라이언트 정보 구조체
struct Client
{
SOCKET socket{};
WSAEVENT event{};
WSAOVERLAPPED overlapped{};
char buffer[BUFFER_SIZE]{};
WSABUF wsaBuf{};
DWORD recvByte{};
};
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(PORT);
// 소켓 바인딩
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;
}
std::vector<Client> clientVec;
while (true)
{
// 새로운 클라이언트 연결 처리
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;
continue;
}
// 클라이언트 이벤트 객체 생성
WSAEVENT clientEvent{ ::WSACreateEvent() };
if (clientEvent == WSA_INVALID_EVENT)
{
std::cerr << "이벤트 생성 실패: " << ::WSAGetLastError() << std::endl;
::closesocket(clientSocket);
continue;
}
// 클라이언트 정보 설정
Client client{};
client.socket = clientSocket;
client.event = clientEvent;
ZeroMemory(&client, sizeof(client));
client.overlapped.hEvent = clientEvent;
client.wsaBuf.buf = client.buffer;
client.wsaBuf.len = BUFFER_SIZE;
clientVec.push_back(client);
std::cout << "새로운 클라이언트 연결 (SOCKET: " << clientSocket << ")" << std::endl;
// 비동기 recv 요청
DWORD flags{};
if (WSARecv(client.socket, &client.wsaBuf, 1, &client.recvByte, &flags, &client.overlapped, nullptr) == SOCKET_ERROR)
{
if (::WSAGetLastError() != WSA_IO_PENDING)
{
std::cerr << "WSARecv() 실패: " << ::WSAGetLastError() << std::endl;
::closesocket(client.socket);
::WSACloseEvent(client.event);
clientVec.pop_back();
}
}
// 이벤트 감시
while (!clientVec.empty())
{
std::vector<WSAEVENT> eventArray;
for (const auto& client : clientVec)
{
eventArray.push_back(client.event);
}
// 이벤트 발생 대기
DWORD idx{ WSAWaitForMultipleEvents(static_cast<DWORD>( eventArray.size() ), eventArray.data(), FALSE, WSA_INFINITE, FALSE) };
if (idx == WSA_WAIT_FAILED)
{
std::cerr << "WSAWaitForMultipleEvents 실패: " << ::WSAGetLastError() << std::endl;
break;
}
int clientIndex{ static_cast<int>( idx - WSA_WAIT_EVENT_0 ) };
Client& client{ clientVec[clientIndex] };
// I/O 작업 완료 확인
DWORD transferredBytes;
if (!WSAGetOverlappedResult(client.socket, &client.overlapped, &transferredBytes, FALSE, nullptr) || transferredBytes == 0)
{
std::cout << "클라이언트 연결 종료 (SOCKET: " << client.socket << ")" << std::endl;
::closesocket(client.socket);
::WSACloseEvent(client.event);
clientVec.erase(clientVec.begin() + clientIndex);
continue;
}
// 받은 데이터를 클라이언트에게 에코
std::cout << "받은 데이터: " << client.buffer << std::endl;
WSASend(client.socket, &client.wsaBuf, 1, &transferredBytes, 0, &client.overlapped, nullptr);
// 이벤트 재설정
WSAResetEvent(client.event);
}
}
// 정리
for (auto& c : clientVec)
{
::closesocket(c.socket);
::WSACloseEvent(c.event);
}
::closesocket(listenSocket);
::WSACleanup();
return 0;
}'Socket 관련' 카테고리의 다른 글
| OVERLAPPED Callback Model (0) | 2024.06.10 |
|---|---|
| WSAEventSelect Model (0) | 2024.06.08 |
| Select Model (0) | 2024.06.07 |
| Windows 소켓 통신 (2) | 2024.06.06 |
| winsock2 라이브러리 추가하기 (0) | 2024.06.05 |