비동기 소켓 방식으로 이벤트 기반 대신 콜백 함수를 기반으로 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 |