OVERLAPPED Event Model

Yongs12 ㅣ 2024. 6. 9. 22:59

소켓 작업을 비동기 방식으로 실행하고 이벤트 객체를 이용해 완료 여부를 확인하는 방식의 모델이다.

비동기 방식으로 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