OVERLAPPED Callback Model

Yongs12 ㅣ 2024. 6. 10. 18:38

비동기 소켓 방식으로 이벤트 기반 대신 콜백 함수를 기반으로 IOCP 모델보다 구현이 단순하면서 이벤트 객체 없이도 비동기 작업을 효율적으로 처리할 수 있는 모델이다.

WSARecv(), WSASend() 호출 후 즉시 반환 되며 이벤트 방식이 아닌 I/O 완료 시 지정된 콜백 함수가 자동 호출된다.

 

 

OVERLAPPED Callback 흐름

1. 소켓 생성 및 비동기 I/O 설정

2. WSARecv() 또는 WSASend() 호출 시 Callback Func 등록

3. I/O 작업이 완료되면 시스템이 자동으로 콜백 함수를 호출

4. 콜백 함수 내부에서 추가적인 I/O 작업 수행 또는 클라이언트 응답 처리

5. 작업이 끝난 소켓 정리

 

 

예제

#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>

#pragma comment(lib, "ws2_32.lib")

constexpr int PORT{ 7777 };
constexpr int BUFFER_SIZE{ 1024 };

// 클라이언트 정보 구조체
struct Client
{
    WSAOVERLAPPED overlapped{};
    SOCKET socket{};
    char buffer[BUFFER_SIZE]{};
    WSABUF wsaBuf{};
    DWORD recvBytes{};
};

// 비동기 I/O 작업이 완료되면 호출되는 콜백 함수
void CALLBACK IoCompletionCallback(DWORD error, DWORD bytes, LPWSAOVERLAPPED overlapped, DWORD flag)
{
    if (error != 0 || bytes == 0)
    {
        std::cerr << "클라이언트 연결 종료 또는 오류 발생" << std::endl;
        ::closesocket(reinterpret_cast<Client*>(overlapped)->socket);
        delete reinterpret_cast<Client*>(overlapped);
        return;
    }

    // 받은 데이터 출력
    Client* client{ reinterpret_cast<Client*>( overlapped ) };
    client->buffer[bytes] = '\0';
    std::cout << "받은 데이터: " << client->buffer << std::endl;

    // 받은 데이터 다시 전송
    // Echo
    ::WSASend(client->socket, &client->wsaBuf, 1, nullptr, 0, &client->overlapped, IoCompletionCallback);
}

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::cout << "서버가 포트 " << PORT << "에서 대기 중..." << std::endl;

    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;
        }

        // 클라이언트 정보 할당
        Client* client{ new Client{} };
        client->socket = clientSocket;
        ZeroMemory(&client->overlapped, sizeof(client->overlapped));
        client->wsaBuf.buf = client->buffer;
        client->wsaBuf.len = BUFFER_SIZE;

        std::cout << "새로운 클라이언트 연결 (SOCKET: " << clientSocket << ")" << std::endl;

        // 비동기 recv 요청 콜백 함수 등록
        DWORD flags{};
        if (::WSARecv(client->socket, &client->wsaBuf, 1, &client->recvBytes, &flags, &client->overlapped, IoCompletionCallback) == SOCKET_ERROR)
        {
            if (::WSAGetLastError() != WSA_IO_PENDING)
            {
                std::cerr << "WSARecv() 실패: " << WSAGetLastError() << std::endl;
                ::closesocket(client->socket);
                delete client;
            }
        }
    }

    ::closesocket(listenSocket);
    ::WSACleanup();
    return 0;
}

'Socket 관련' 카테고리의 다른 글

OVERLAPPED Event Model  (0) 2024.06.09
WSAEventSelect Model  (0) 2024.06.08
Select Model  (0) 2024.06.07
Windows 소켓 통신  (2) 2024.06.06
winsock2 라이브러리 추가하기  (0) 2024.06.05