한 소켓에서 수신과 송신을 동시에 제출했습니다. IOCP 완료를 받을 때 어느 연결의 어느 작업인지 completion key와 OVERLAPPED로 어떻게 구분하나요?
잠깐, 답변을 보기 전에
먼저 내 말로 설명해 볼까요?
답변과 점검 포인트 펼치기답변 접기
구두 답변
정의를 외우기보다, 원리와 조건을 연결해 설명해 보세요.
completion key는 보통 완료 포트에 연결된 핸들, 즉 어느 연결이나 리소스의 완료인지 찾는 데 사용하고, OVERLAPPED는 개별 중첩 I/O 작업을 찾는 데 사용합니다. 한 소켓에서 수신과 송신을 동시에 제출할 수 있으므로 같은 completion key가 도착해도 작업 종류·버퍼·세대는 OVERLAPPED를 포함한 사용자 컨텍스트에서 구분해야 합니다. 포인터를 key에 넣었다고 객체 수명이 자동으로 보장되는 것도 아닙니다.
연결 식별과 작업 식별
CreateIoCompletionPort로 소켓을 포트에 연결할 때 completion key를 지정하면 해당 핸들의 완료 패킷에 그 값이 돌아옵니다. 연결 객체 포인터나 안정적인 연결 ID를 넣을 수 있지만, key만으로 수신인지 송신인지 알 수는 없습니다. WSARecv와 WSASend를 각각 다른 OVERLAPPED에 제출하고, OVERLAPPED를 포함한 IoContext에 작업 종류, 버퍼, 요청 ID, 제출 세대를 저장합니다. 이를 리소스 수준 식별자(completion key)와 작업 수준 식별자(overlapped context)의 분리라고 부르겠습니다.
완료를 받으면 반환된 key로 연결을 찾고 OVERLAPPED에서 IoContext를 얻어 바이트 수와 오류를 해석합니다. 수신·송신 컨텍스트는 서로 다른 주소여야 하며 미완료 작업에 같은 OVERLAPPED를 재사용하면 안 됩니다. 연결 객체를 key로 사용할 때는 포트에 연결된 핸들의 완료가 모두 drain될 때까지 객체를 유지하고, 늦은 완료가 논리적으로 닫힌 연결에 도착하면 세대·종료 상태로 상위 적용만 막습니다. 세대 번호는 이미 해제한 메모리 접근을 안전하게 만들지 못하므로 실제 drain 전에는 포인터와 컨텍스트를 재사용하지 않겠습니다.
사용자 패킷과 수명
completion key는 값 전달이지 참조 카운트나 소유권 획득이 아닙니다. PostQueuedCompletionStatus로 직접 넣은 패킷은 OVERLAPPED가 null일 수 있으므로 payload 규약으로 종료·깨움 패킷을 실제 I/O와 구분합니다. 포트와 연결 수명을 작업 참조와 분리해, 연결이 논리적으로 폐기된 뒤에도 모든 미완료 완료가 소비될 때까지 컨텍스트를 살립니다. 테스트는 동시 수신·송신, 종료 중 늦은 완료, 컨텍스트 주소 재사용 시도, 사용자 패킷과 커널 완료를 검증합니다.
추가 조건과 판단
예를 들어 연결 A에 receive-17과 send-28을 제출했다면 두 완료는 같은 연결 key를 가지지만 서로 다른 OVERLAPPED 주소를 돌려줍니다. 워커는 작업 종류에 따라 수신 바이트를 파서에 넘기거나 송신 버퍼를 정리합니다. 같은 연결의 완료를 두 워커가 동시에 처리할 수 있으므로 key가 같다는 사실만으로 연결 필드의 수정이 직렬화되지는 않습니다. 작업 식별과 상태 동기화는 별도 책임입니다.
OVERLAPPED에서 바깥 객체를 복원하려면 실제 포함 관계와 타입 레이아웃이 일치해야 합니다. 임의 포인터를 같은 형태라고 캐스팅하지 않고, 표준 레이아웃의 명확한 컨텍스트나 안전한 매핑을 사용하겠습니다. key는 포인터 크기에 맞는 ULONG_PTR 계약을 따라 저장해 64비트 주소를 잘라 버리지 않아야 합니다. 사용자 패킷과 커널 패킷을 함께 쓴다면 사용자 패킷의 discriminator와 참조 소유자를 명시합니다.
작업 기록은 최소한 제출·완료·반환 상태를 구분하고 해당 버퍼의 마지막 소비자가 누구인지 포함해야 합니다. 파싱을 별도 실행기에 넘기면 커널 완료는 끝났어도 데이터 소비는 끝나지 않았을 수 있습니다. 미완료 I/O 참조를 후속 처리 참조로 안전하게 넘겨, 풀 주소가 새 작업에 재사용되기 전에 이전 소비가 끝났는지 확인하겠습니다.
득점 포인트
이해가 드러나는 지점을 확인해 보세요.
- completion key를 연결·핸들 맥락으로, OVERLAPPED를 개별 작업 맥락으로 정확히 구분한다.
- 수신·송신마다 독립된 OVERLAPPED와 컨텍스트가 필요함을 설명한다.
- key가 객체 수명을 보장하지 않으며 종료 중 참조 수명이 별도임을 짚는다.
- 사용자 정의 완료 패킷과 null OVERLAPPED 규약을 함께 고려한다.
감점 포인트
놓치기 쉬운 조건과 오해를 짚어 보세요.
- completion key 하나만으로 수신·송신과 버퍼를 모두 구분할 수 있다고 말한다.
- 미완료 I/O에 같은 OVERLAPPED를 재사용한다.
- key에 포인터를 넣으면 연결 객체가 자동으로 살아 있다고 본다.
더 파고들 거리
하나 더 질문하며 이해의 범위를 넓혀 보세요.
- 컨텍스트 풀 주소가 재사용될 때 늦은 완료를 세대 번호로 어떻게 걸러낼까요?
- 작업별 참조와 연결별 참조를 제출·완료·취소에서 언제 증감할까요?
- IOCP에 사용자 패킷을 직접 게시합니다. 커널 I/O 완료와 종료·제어 패킷을 어떤 타입 규약으로 구분하나요?
득점·감점 포인트는 학습용 자기 점검 기준이며, 특정 회사의 실제 채점표가 아닙니다.
원리부터 공부하고 싶다면
답변을 보충하는 개념·예제·슈도코드를 읽어 보세요.