[게임 서버] 3.9 IOCP

게시:     수정

카테고리:

태그:

📦 3. 소켓 프로그래밍

👉🏻 항목 9: IOCP

🎯 IOCP란?

정의:

  • Overlapped I/O가 완료되면 감지해서 사용자에게 알려주는 역할을 함
  • Overlapped I/O와 IOCP를 함께 사용하면 최고 성능을 낼 수 있음
  • epoll에서는 I/O 입력이 가능하면 알려주었으므로, 서로 비슷함

💻 IOCP 기본 사용법

iocp = new iocp();
// (실제로는 CreateIoCompletionPort로 IOCP 핸들을 만든다)

foreach (s in sockets) {
    iocp.add(s, GetUserPtr(s));
    // 이 소켓의 Overlapped I/O 완료 통지는 이 IOCP로 오게 된다.
    // GetUserPtr(s)는 나중에 이벤트에서 "누구(어떤 세션) 이벤트인지" 구분하는 completion key.

    s.OverlappedReceive(data[s], receiveOverlapped[s]);
    // (실제로는 WSARecv)
    // data[s] 버퍼 포인터와, receiveOverlapped[s]의 주소(OVERLAPPED*)를 커널에 넘겨서
    // "recv 요청"을 걸어 둔다.
    // 완료되면 IOCP 이벤트에 overlappedPtr로 이 포인터가 그대로 돌아온다.
}

events = iocp.wait(100ms);
// (GetQueuedCompletionStatus(Ex))
// IOCP 큐에서 완료된 I/O들을 꺼낸다(최대 100ms 대기).
// 각 이벤트는 (completionKey=userPtr, overlappedPtr, bytes, error)를 포함한다.

foreach (event in events) {
    userPtr = event.userPtr;       // completion key (보통 Session*)
    ov      = event.overlappedPtr; // WSARecv에 넘긴 OVERLAPPED* 그대로
    s = GetSocketFromUserPtr(userPtr);

    if (ov == &receiveOverlapped[s]) { // "이 완료는 recv 완료다"를 구분
        Process(s, userPtr, data[s]);  // 실제론 event.bytes만큼 처리해야 함

        s.OverlappedReceive(data[s], receiveOverlapped[s]);
        // 다음 데이터를 받기 위해 recv를 다시 건다.
    }
}

핵심 동작:

  1. IOCP 핸들 생성 (CreateIoCompletionPort)
  2. 각 소켓을 IOCP에 등록하고 completion key 설정
  3. Overlapped I/O 요청 (WSARecv 등)
  4. 완료된 I/O 이벤트 대기 및 처리 (GetQueuedCompletionStatus)
  5. 이벤트 처리 후 다음 I/O 요청

🔌 IOCP Accept 처리

AcceptEx 처리 과정:

  1. 초기 설정:
    • listen socket을 IOCP에 연결해두고, AcceptEx를 Overlapped로 미리 걸어둠
    • 새 연결이 들어올 때마다 AcceptEx 완료 이벤트가 IOCP로 들어온다
  2. 완료 이벤트 처리:
    • IOCP에서 AcceptEx 완료 이벤트를 받으면, 그 이벤트의 OVERLAPPED로 요청 컨텍스트를 찾아 accept socket을 꺼냄
    • SO_UPDATE_ACCEPT_CONTEXT를 적용해 연결 소켓을 완성
    • 이후 이 accept socket을 IOCP에 연결하고 recv를 건다
  3. 다음 연결 대기:
    • 다음 accept를 위해 AcceptEx를 다시 게시한다

🧵 스레드 풀 구현의 차이

IOCP:

  • IOCP의 경우 스레드 풀을 쉽게 구현할 수 있다

epoll:

  • epoll은 그렇지 않다

⚖️ epoll의 스레드 처리 문제

epoll의 한계:

  • epoll은 I/O 여부와 관계없이 I/O 가능 이벤트가 온다
  • 한 epoll에 대해 여러 스레드가 동시에 이벤트 발생을 기다리는 경우 문제가 있다
  • 연동된 소켓 하나가 UDP 데이터 2개를 수신 큐에 가지고 있으면, epoll 이벤트는 같은 소켓에 대해 두 스레드에서 동시에 꺼낸다
  • 데이터 순서는 알기 힘들다

✅ IOCP의 스레드 처리 장점

IOCP의 강점:

  • 소켓에 대해 Overlapped I/O를 하지 않으면, 소켓에 대한 완료 신호가 발생하지 않는다
  • 즉, 소켓 하나에 대한 완료 신호를 스레드 하나만 처리하게 보장할 수 있다
  • IOCP 하나를 여러 스레드가 기다리게 구현하기 쉽다

📊 IOCP/epoll 장단점 비교

구분 IOCP epoll
블로킹 없애는 수단 Overlapped I/O 논블록 소켓
블로킹 없는 처리 순서 1. Overlapped I/O 걸기
2. 완료 신호 꺼내기
3. 완료 신호에 대한 나머지 처리
4. 끝난 후, 다시 Overlapped I/O 걸기
1. I/O 이벤트 꺼내기
2. 꺼낸 이벤트에 대응하는 소켓에 대한 논블록 I/O 실행
지원 플랫폼 윈도우 리눅스, 안드로이드
스레드 풀 구현 쉬움 어려움
동시 처리 안정성 소켓당 하나의 스레드만 처리 보장 동시 처리 시 순서 문제 발생 가능

🧐 정리

IOCP의 핵심 특징:

  • Overlapped I/O의 완료를 효율적으로 감지하고 처리한다
  • completion key를 통해 세션/연결을 구분한다
  • OVERLAPPED 구조체 포인터로 I/O 요청 타입을 구분한다
  • 스레드 풀 구현이 용이하고 안정적이다

IOCP vs epoll:

  • IOCP는 완료 기반, epoll은 준비 완료 기반
  • IOCP는 Windows 전용, epoll은 Linux/Android 전용
  • IOCP는 멀티스레드 환경에서 더 안정적이다
  • 둘 다 고성능 서버 구현을 위한 필수 기술이다

사용 시나리오:

  • Windows 서버: IOCP + Overlapped I/O
  • Linux 서버: epoll + 논블록 소켓
  • 크로스 플랫폼: 플랫폼별 추상화 레이어 구현 필요

GameServer 카테고리 내 다른 글 보러가기

댓글남기기