본문 바로가기
DEV

[C++] IOCP 설명 / 예제

by 아노앤유노 2023. 6. 30.
반응형

c++
iocp

IOCP 란?

IOCP(입출력 완료 포트, Input/Output Completion Port)는 Windows 운영 체제에서 비동기 입출력 작업을 처리하기 위한 메커니즘입니다. IOCP는 I/O 작업에 대한 효율성과 확장성을 제공하여 다중 클라이언트와 서버 애플리케이션에서 높은 성능을 달성할 수 있습니다.

 

IOCP는 주요 구성 요소

  • I/O 완료 포트(IO Completion Port): I/O 작업의 완료를 추적하고 관리하는 객체입니다. 커널에서는 I/O 작업이 완료되면 IOCP에 알립니다.
  • 소켓: 네트워크 통신에 사용되는 소켓 객체입니다. 클라이언트와 서버 간의 데이터 통신을 담당합니다.
  • 오버랩 구조체(Overlap Structure): 비동기 I/O 작업의 정보를 포함하는 구조체입니다. 오버랩 구조체는 I/O 작업을 요청하고 완료된 작업의 결과를 처리합니다.

IOCP는 비동기 입출력 모델인 Overlapped I/O와 결합하여 사용됩니다. Overlapped I/O는 데이터의 비동기적인 송수신을 가능하게 하는 메커니즘으로, 작업을 요청한 후에 결과를 기다리지 않고 다른 작업을 처리할 수 있습니다. 이를 통해 다중 클라이언트와 동시에 효율적인 네트워크 통신을 수행할 수 있습니다.

반응형
#include <iostream>
#include <WinSock2.h>
#include <MSWSock.h>

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

constexpr int MAX_BUFFER_SIZE = 1024;

// 클라이언트 정보를 저장하는 구조체
struct ClientContext {
    SOCKET socket;
    WSABUF wsaBuf;
    char buffer[MAX_BUFFER_SIZE];
    OVERLAPPED overlapped;
};

int main() {
    // WinSock 초기화
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        std::cerr << "Failed to initialize WinSock." << std::endl;
        return -1;
    }

    // 소켓 생성
    SOCKET listenSocket = WSASocket(AF_INET, SOCK_STREAM, 0, nullptr, 0, WSA_FLAG_OVERLAPPED);
    if (listenSocket == INVALID_SOCKET) {
        std::cerr << "Failed to create socket." << std::endl;
        WSACleanup();
        return -1;
    }

    // 바인딩
    sockaddr_in serverAddr{};
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    serverAddr.sin_port = htons(12345);
    if (bind(listenSocket, reinterpret_cast<sockaddr*>(&serverAddr), sizeof(serverAddr)) == SOCKET_ERROR) {
        std::cerr << "Failed to bind." << std::endl;
        closesocket(listenSocket);
        WSACleanup();
        return -1;
    }

    // 리스닝
    if (listen(listenSocket, SOMAXCONN) == SOCKET_ERROR) {
        std::cerr << "Failed to listen." << std::endl;
        closesocket(listenSocket);
        WSACleanup();
        return -1;
    }

    // IOCP 생성
    HANDLE iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, 0);
    if (iocp == nullptr) {
        std::cerr << "Failed to create IOCP." << std::endl;
        closesocket(listenSocket);
        WSACleanup();
        return -1;
    }

    // IOCP에 listenSocket 등록
    if (CreateIoCompletionPort(reinterpret_cast<HANDLE>(listenSocket), iocp, 0, 0) == nullptr) {
        std::cerr << "Failed to register listen socket to IOCP." << std::endl;
        closesocket(listenSocket);
        CloseHandle(iocp);
        WSACleanup();
        return -1;
    }

    // 클라이언트 연결을 받아들이기 위한 작업 예약
    ClientContext* context = new ClientContext{};
    context->socket = WSASocket(AF_INET, SOCK_STREAM, 0, nullptr, 0, WSA_FLAG_OVERLAPPED);
    context->wsaBuf.len = MAX_BUFFER_SIZE;
    context->wsaBuf.buf = context->buffer;
    context->overlapped.hEvent = nullptr;

    DWORD recvBytes = 0;
    DWORD flags = 0;
    sockaddr_in clientAddr{};
    int addrSize = sizeof(clientAddr);

    while (true) {
        // 클라이언트 연결 대기
        if (AcceptEx(listenSocket, context->socket, context->buffer, 0,
            sizeof(sockaddr_in) + 16, sizeof(sockaddr_in) + 16,
            &recvBytes, &context->overlapped) == FALSE) {
            if (WSAGetLastError() != ERROR_IO_PENDING) {
                std::cerr << "Failed to accept connection." << std::endl;
                break;
            }
        }

        // 클라이언트 연결 처리 완료 대기
        DWORD transferredBytes = 0;
        ULONG_PTR completionKey = 0;
        LPOVERLAPPED overlapped = nullptr;

        if (GetQueuedCompletionStatus(iocp, &transferredBytes, &completionKey, &overlapped, INFINITE) == FALSE) {
            std::cerr << "Failed to get completion status." << std::endl;
            break;
        }

        // 클라이언트와 통신
        if (WSARecv(context->socket, &context->wsaBuf, 1, nullptr, &flags, &context->overlapped, nullptr) == SOCKET_ERROR) {
            if (WSAGetLastError() != ERROR_IO_PENDING) {
                std::cerr << "Failed to receive data." << std::endl;
                break;
            }
        }

        // 클라이언트 연결 처리 완료
        std::cout << "Accepted a new client connection." << std::endl;

        // 새로운 클라이언트 연결을 받아들이기 위해 새로운 컨텍스트 할당
        context = new ClientContext{};
        context->socket = WSASocket(AF_INET, SOCK_STREAM, 0, nullptr, 0, WSA_FLAG_OVERLAPPED);
        context->wsaBuf.len = MAX_BUFFER_SIZE;
        context->wsaBuf.buf = context->buffer;
        context->overlapped.hEvent = nullptr;
    }

    // 정리
    closesocket(listenSocket);
    CloseHandle(iocp);
    WSACleanup();

    return 0;
}

 

위의 예시는 IOCP를 사용하여 비동기적으로 클라이언트 연결을 받아들이고, 클라이언트와의 데이터 통신을 처리하는 간단한 소켓 서버 모델을 구현한 것입니다. 이 예시에서는 Windows 운영 체제의 WinSock 라이브러리를 사용하고 있으며, IOCP와 Overlapped I/O를 조합하여 비동기적인 네트워크 통신을 수행합니다.

참고: 위의 예시는 단순한 IOCP의 구현 방법을 보여주기 위한 것이며, 실제로는 에러 처리, 클라이언트와의 통신 처리 등을 추가로 구현해야 합니다. 또한, 네트워크 프로그래밍은 보안과 안정성에 관련된 많은 고려 사항이 있으므로, 실제 프로덕션 환경에서 사용하기 전에 충분한 검증과 테스트를 수행해야 합니다.

2023.07.06 - [DEV] - [C++] 포인터 pointer 란?

 

[C++] 포인터 pointer 란?

포인터 pointer 란? C++에서 포인터(pointer)는 메모리 주소를 저장하는 변수입니다. 포인터는 다른 변수 또는 데이터 구조에 대한 참조를 저장하고 사용하는 데 사용됩니다. 포인터를 사용하여 메모

iknowandyouknow.tistory.com

2023.06.30 - [DEV] - [C++] Thread 란? + 예제

 

[C++] Thread 란? + 예제

Thread 란? C++의 스레드(Thread)는 동시에 실행될 수 있는 독립적인 실행 흐름을 나타내는 개념입니다. 각각의 스레드는 별도의 스택을 가지며, 코드의 일부분을 동시에 실행할 수 있습니다. 멀티스

iknowandyouknow.tistory.com

2023.06.30 - [DEV] - [C++] STL 이란? + 예시

 

[C++] STL 이란? + 예시

STL 이란? C++의 STL(Standard Template Library)은 C++ 표준 라이브러리의 중요한 구성 요소로, 일반적인 데이터 구조와 알고리즘을 제공하여 프로그래머들이 간편하게 사용할 수 있도록 돕습니다. STL은 컨

iknowandyouknow.tistory.com

 

반응형

'DEV' 카테고리의 다른 글

[JAVA] spring model 사용하기  (0) 2023.06.30
[C++] Thread 란? + 예제  (0) 2023.06.30
[C++] STL 이란? + 예시  (0) 2023.06.30
[ORACLE] LOCK 확인 및 해제  (0) 2023.06.19
[ORALCE] DDL, DML, DCL, TCL 이란?  (0) 2023.06.19