Re-posting 정책 (수신 재등록)
왜 필수인가
- IOCP는 "등록된 비동기 요청"이 있어야 완료를 돌려줍니다.
- 따라서 recv 완료 처리 후 다음
WSARecv를 등록하지 않으면 해당 세션 수신이 멈춥니다.
정책 선택
| 정책 | 장점 | 단점 | 권장 상황 |
|---|
| 세션당 Recv 1개 outstanding | 단순, 디버깅 쉬움, 경쟁 적음 | 최고 처리량 한계 | 학습/초기 상용 |
| 세션당 Recv N개 outstanding | 피크 처리량 확보 가능 | 동기화·순서·버퍼 관리 복잡 | 고성능 튜닝 단계 |
기본 권장
- 시작은 "세션당 Recv 1개" 정책으로 두고, 병목 측정 후 확장하세요.
종료 레이스와 댕글링 포인터
대표 크래시 시나리오
Session*를 Completion Key/컨텍스트에 넣고 I/O 등록
- 연결 종료 처리에서 Session 메모리 해제
- 늦게 도착한 완료가 해제된 포인터를 참조 -> 크래시/메모리 오염
방지 전략
- 세션 수명은 참조 카운트(
shared_ptr 또는 intrusive refcount)로 제어
- "I/O 등록 시 +1, 완료 처리 후 -1" 규칙을 팀 규약으로 고정
- 종료 시에는 아래 순서를 지킵니다.
closing 플래그 설정(신규 I/O 등록 금지)
- 소켓 종료/취소 처리
- 남은 완료 이벤트 회수
- 참조 카운트가 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를 이어 등록합니다.
- 큐 상한(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를 왜 "완료 실패 경로"로 따로 처리해야 하는가?
- 세션별 송신 큐에 상한을 두지 않으면 어떤 장애가 발생할 수 있는가?