IOCP 핵심 이슈

Jaemyeong Lee·2025년 1월 30일

게임 서버1

목록 보기
138/220

Re-posting 정책 (수신 재등록)

왜 필수인가

  • IOCP는 "등록된 비동기 요청"이 있어야 완료를 돌려줍니다.
  • 따라서 recv 완료 처리 후 다음 WSARecv를 등록하지 않으면 해당 세션 수신이 멈춥니다.

정책 선택

정책장점단점권장 상황
세션당 Recv 1개 outstanding단순, 디버깅 쉬움, 경쟁 적음최고 처리량 한계학습/초기 상용
세션당 Recv N개 outstanding피크 처리량 확보 가능동기화·순서·버퍼 관리 복잡고성능 튜닝 단계

기본 권장

  • 시작은 "세션당 Recv 1개" 정책으로 두고, 병목 측정 후 확장하세요.

종료 레이스와 댕글링 포인터

대표 크래시 시나리오

  1. Session*를 Completion Key/컨텍스트에 넣고 I/O 등록
  2. 연결 종료 처리에서 Session 메모리 해제
  3. 늦게 도착한 완료가 해제된 포인터를 참조 -> 크래시/메모리 오염

방지 전략

  • 세션 수명은 참조 카운트(shared_ptr 또는 intrusive refcount)로 제어
  • "I/O 등록 시 +1, 완료 처리 후 -1" 규칙을 팀 규약으로 고정
  • 종료 시에는 아래 순서를 지킵니다.
  1. closing 플래그 설정(신규 I/O 등록 금지)
  2. 소켓 종료/취소 처리
  3. 남은 완료 이벤트 회수
  4. 참조 카운트가 0일 때 최종 해제

자주 보는 종료 관련 오류 코드

코드흔한 의미대응
ERROR_NETNAME_DELETED원격 종료/연결 손실정상 종료 경로로 세션 정리
ERROR_OPERATION_ABORTED로컬 취소/종료 중 I/O 취소종료 플로우에서 예상 가능한 결과
WSAECONNRESET강제 연결 종료로그 후 세션 정리

스레드 세이프티: Recv와 Send는 다르다

Recv 경로

  • 세션당 Recv 1개 outstanding이면 해당 완료 처리는 보통 한 워커가 담당합니다.
  • 이 가정이 깨지는 구조(복수 Recv outstanding)라면 별도 동기화가 필요합니다.

Send 경로

  • 여러 스레드가 동시에 WSASend를 던지면 큐/플래그 없이 관리하기 어렵습니다.
  • 일반적으로 "세션별 송신 큐 + 전송 중 플래그" 패턴을 사용합니다.
// 개념 예시
Enqueue(packet);
if (!sending.exchange(true)) {
    PostNextSend(); // 큐의 첫 청크를 WSASend 등록
}

추가 주의

  • 송신 완료 시 잔여 바이트 확인 후 필요하면 다음 WSASend를 이어 등록합니다.
  • 큐 상한(Backpressure)을 두지 않으면 메모리 급증이 발생할 수 있습니다.

OverlappedEx 캐스팅 안정성

첫 번째 멤버 규칙

  • OVERLAPPED는 컨텍스트 구조체의 첫 멤버로 두는 것을 권장합니다.
  • 이렇게 하면 LPOVERLAPPED를 컨텍스트 포인터로 복원하기 쉽고 일관성이 생깁니다.

변환 헬퍼 예시

struct OverlappedEx {
    OVERLAPPED ov{};
    int ioType = 0;
    // ...
};

inline OverlappedEx* ToCtx(OVERLAPPED* ov) {
    return reinterpret_cast<OverlappedEx*>(ov);
}

재사용 규칙

  • 동일 컨텍스트를 재사용할 때는 "이전 I/O 완료 후"에만 재등록하세요.
  • 등록 전 OVERLAPPED 필드를 초기화하는 습관을 고정하면 실수를 줄일 수 있습니다.

GQCS 실패 분기 규칙 (운영 핵심)

분기 표준화

조건해석처리
ok == TRUE정상 완료ioType별 정상 처리
ok == FALSE && ov != nullptr실패 완료(오류 포함)오류 코드 기록 후 세션 정리/복구
ok == FALSE && ov == nullptr제어/종료 경로 가능성워커 종료 조건 확인

최소 로그 항목

  • sessionId, socket, ioType, bytesTransferred, GetLastError()
  • outstandingRecv, outstandingSend, closing 상태

운영 팁

  • 오류 코드별로 "정상 종료 분류"와 "경고/치명 분류"를 분리하면 노이즈가 줄어듭니다.

강의 시 유의사항

강조 포인트

  • IOCP 버그의 대부분은 API 문법이 아니라 수명·재등록·종료 순서에서 발생합니다.
  • "성능 최적화"보다 "정합성 규칙 고정"이 먼저입니다.
  • Part 18의 구조를 Part 19의 실패 시나리오와 반드시 연결해 설명하세요.

자주 하는 오해

오해바로잡기
재등록은 선택사항이다재등록 누락 시 수신이 멈춘다
소켓 닫으면 관련 완료는 다 사라진다늦게 도착하는 완료가 존재할 수 있다
Recv가 안전하면 Send도 자동으로 안전하다Send는 별도 큐/동기화 정책이 필요

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

  • 세션 해제 전에 어떤 조건이 만족되어야 안전한가?
  • ok == FALSE && ov != nullptr를 왜 "완료 실패 경로"로 따로 처리해야 하는가?
  • 세션별 송신 큐에 상한을 두지 않으면 어떤 장애가 발생할 수 있는가?

profile
李家네_공부방

0개의 댓글