IOCP Object & Event

Jaemyeong Lee·2025년 3월 9일

게임 서버1

목록 보기
142/220

IocpObject: 완료를 받는 주체

한 줄 정의

IocpObject는 IOCP에 등록되어 완료 이벤트를 Dispatch 받는 엔진 객체의 공통 인터페이스입니다.

핵심 인터페이스

class IocpObject : public std::enable_shared_from_this<IocpObject>
{
public:
    virtual ~IocpObject() = default;
    virtual HANDLE GetHandle() = 0;
    virtual void Dispatch(class IocpEvent* iocpEvent, int32 numOfBytes = 0) = 0;
};

책임 경계

함수책임
GetHandle()IOCP 등록 대상 핸들 반환(소켓 등)
Dispatch(...)완료 이벤트를 타입별 처리 함수로 분기

Listener, Session 같은 구체 타입은 이 인터페이스를 구현해 "자기 이벤트"만 처리합니다.


IocpEvent: 요청 1건의 컨텍스트

핵심 개념

  • I/O 요청 1건(Recv/Send/Accept)마다 완료 시 되돌아올 컨텍스트가 필요합니다.
  • 이 컨텍스트가 IocpEvent이며, OVERLAPPED는 반드시 offset 0에 있어야 합니다.

권장 구조

enum class EventType { Accept, Connect, Recv, Send, Disconnect };

struct IocpEvent
{
    OVERLAPPED overlapped{};      // 반드시 첫 멤버(오프셋 0)
    EventType eventType = EventType::Recv;
    IocpObjectRef owner;          // 완료 시 수명 보장용 참조
};

struct RecvEvent : IocpEvent
{
    WSABUF wsaBuf{};
    uint8  buffer[8192]{};
};

중요한 규칙

  • Outstanding I/O 1개당 Event 1개를 사용해야 안전합니다.
  • 동일 Event를 동시에 여러 I/O에 재사용하면 완료 결과가 섞일 수 있습니다.

식별자 두 축: Completion Key와 OVERLAPPED*

GQCS에서 받는 값

의미주용도
Completion Key어느 객체의 완료인지Session/Listener 식별
OVERLAPPED*어떤 작업의 완료인지Recv/Send/Accept 식별

owner 기반 수명 보장

  • Event 안에 owner(shared_ptr)를 넣으면 완료 시점까지 객체 생존을 보장하기 쉽습니다.
  • Completion Key는 식별용으로 유지하고, 실제 수명 보장은 owner로 가져가는 하이브리드 전략이 실무에서 안전합니다.

복원 예시

OVERLAPPED* ov = /* GQCS 결과 */;
auto* event = reinterpret_cast<IocpEvent*>(ov);
IocpObjectRef owner = event->owner; // refcount +1
if (!owner)
    return; // 종료 경로

owner->Dispatch(event, static_cast<int32>(numOfBytes));

캐스팅/초기화 안전 규칙

캐스팅 안전성 보강

static_assert(offsetof(IocpEvent, overlapped) == 0, "OVERLAPPED must be first");

inline IocpEvent* ToIocpEvent(OVERLAPPED* ov)
{
    return reinterpret_cast<IocpEvent*>(ov);
}

재사용 시 초기화

void InitEvent(IocpEvent& e, EventType type, IocpObjectRef owner)
{
    ::ZeroMemory(&e.overlapped, sizeof(e.overlapped));
    e.eventType = type;
    e.owner = std::move(owner);
}

흔한 초기화 실수

  • shared_ptr, vector, string이 들어간 구조체 전체를 memset(this, 0, sizeof(*this)) 하면 UB입니다.
  • 반드시 OVERLAPPED 필드만 ZeroMemory로 초기화하세요.

상속 vs 포함(composition) 선택

방식예시장점주의
상속struct Evt : OVERLAPPED { ... };캐스팅 코드가 짧음가상 함수/가상 상속 금지, 레이아웃 주의
포함struct Evt { OVERLAPPED ov; ... };의도가 명확, 유지보수 쉬움ov를 첫 멤버로 고정 필요

실무에서는 가독성과 실수 방지를 위해 포함 방식을 선호하는 팀이 많습니다.


자주 터지는 버그와 대응

버그증상대응
Event 재사용 충돌잘못된 이벤트 타입/버퍼 처리I/O 1건당 Event 1개 정책
owner 미설정Completion Key 댕글링 크래시등록 전에 owner 설정 강제
eventType 누락Dispatch 오분기InitEvent 단일 진입점 사용
OVERLAPPED 미초기화예측 불가 실패 코드등록 직전 ZeroMemory(&overlapped, ...)

강의 시 유의사항

강조 포인트

  • Object는 완료를 받는 주체, Event는 완료를 식별하는 티켓입니다.
  • "누구(Key) + 무엇(Overlapped)" 두 축으로 생각하면 IOCP 디버깅이 쉬워집니다.
  • 레이아웃 규칙(offset 0)과 수명 규칙(owner)은 문법보다 더 중요합니다.

자주 하는 오해

오해바로잡기
Completion Key 하나만 있으면 충분하다작업 타입 구분은 Event/Overlapped가 담당한다
Event는 아무 때나 재사용 가능하다이전 I/O 완료 전 재사용하면 충돌한다
전체 memset이 가장 안전하다non-trivial 멤버가 있으면 UB 위험이 있다

체크 질문 (스스로 답해보기)

  • OVERLAPPED가 반드시 첫 멤버여야 하는가?
  • Completion Key와 Event의 역할을 분리하면 디버깅이 왜 쉬워지는가?
  • Event에 owner를 넣지 않으면 어떤 종료 레이스가 발생할 수 있는가?

profile
李家네_공부방

0개의 댓글