1. IOCP란?
IOCP(Input/Output Completion Port)는 Windows 환경에서 제공하는 고성능 비동기 입출력 처리 메커니즘이다.
다수의 I/O 요청을 효율적으로 처리하기 위해 설계된 API이다.
IOCP는 커널 레벨에서 I/O 작업 완료를 관리하는 큐 시스템이다.
이 큐는 비동기 I/O 작업이 완료될 때 알림을 대기 중인 스레드에게 효율적으로 전달한다.
IOCP의 핵심 목적은 다중 스레드 환경에서 최소한의 스레드로 최대한의 I/O 처리량을 달성하는 것이다.
2. IOCP의 구조
2.1. 완료 포트 객체의 구조
완료 포트는 커널 객체로 다음 주요 구성 요소를 포함한다.
- 완료 패킷 큐: I/O 작업 결과를 저장하는 FIFO 큐
- 대기 스레드 목록: 완료 패킷을 기다리는 스레드 목록
- 동시성 한도: 동시에 실행될 수 있는 최대 스레드 수
- 컨텍스트 키: I/O 핸들과 완료 포트를 연결하는 식별자
완료 패킷에는 완료된 I/O 작업에 대한 정보(작업 타입, 전송된 바이트 수, 오류 코드 등)와 애플리케이션 정의 컨텍스트가 포함된다.
2.2. 스레드 풀과의 관계
IOCP는 일반적으로 작업자 스레드 풀과 함께 사용된다.
이 스레드들은 GetQueuedCompletionStatus() 함수를 호출하여 완료 패킷을 기다린다.
스레드 풀 크기는 I/O 바운드 vs CPU 바운드 작업 비율이나 하드웨어 사양을 고려하여 적절하게 설정해야 한다.
이는 컨텍스트 스위칭 오버헤드와 병렬 처리 사이의 균형을 맞추기 위함이다.
IOCP는 동시에 실행되는 스레드 수를 제한하는 동시성 제한 메커니즘을 제공한다.
이는 시스템 부하를 관리하는 데 중요하다.
3. IOCP 구현 단계
1) 완료 포트 생성
완료 포트는 CreateIoCompletionPort() API를 사용하여 생성한다.
이 함수는 새로운 완료 포트 객체의 핸들을 반환한다.
ex)
HANDLE hCompletionPort = CreateIoCompletionPort(
INVALID_HANDLE_VALUE, // 새 완료 포트 생성
NULL, // 기존 완료 포트 없음
0, // 완료 키
0 // 시스템 기본 동시성 값 사용
);
동시성 값(마지막 매개변수)은 동시에 실행될 수 있는 스레드 수를 제한한다.
0은 시스템이 프로세서 수에 기반하여 자동으로 설정하도록 한다.
2) 소켓과 완료 포트 연결
소켓이나 기타 I/O 핸들을 완료 포트와 연결하려면 동일한 CreateIoCompletionPort() 함수를 다른 매개변수로 호출한다.
ex)
SOCKET clientSocket = WSASocket(...); // 소켓 생성
// 소켓을 완료 포트와 연결
CreateIoCompletionPort(
(HANDLE)clientSocket, // 소켓 핸들
hCompletionPort, // 기존 완료 포트
(ULONG_PTR)clientContext, // 완료 키(보통 클라이언트 컨텍스트)
0 // 동시성 값(무시됨)
);
완료 키(Completion Key)는 애플리케이션 정의 값으로, 일반적으로 I/O 요청과 관련된 컨텍스트 데이터를 가리키는 포인터다.
3) 작업 스레드 생성
IOCP 기반 서버는 일반적으로 여러 작업 스레드를 생성하여 완료 패킷을 처리한다.
이 스레드들은 GetQueuedCompletionStatus() 함수를 호출하여 완료 포트에서 패킷을 가져온다.
ex)
// 시스템 정보 획득
SYSTEM_INFO systemInfo;
GetSystemInfo(&systemInfo);
// CPU 코어 수에 기반한 작업 스레드 생성
for (DWORD i = 0; i < systemInfo.dwNumberOfProcessors * 2; i++) {
CreateThread(
NULL, // 기본 보안 속성
0, // 기본 스택 크기
WorkerThreadProc, // 스레드 함수
hCompletionPort, // 스레드 매개변수
0, // 기본 생성 플래그
NULL // 스레드 ID(불필요)
);
}
4) 비동기 I/O 요청 및 완료 처리
WSASend(), WSARecv() 등의 비동기 I/O 함수를 OVERLAPPED 구조체와 함께 사용하여 비동기 요청을 시작한다.
ex) 비동기 수신 요청 코드
WSABUF wsaBuf;
wsaBuf.buf = buffer;
wsaBuf.len = BUFFER_SIZE;
DWORD bytesReceived = 0;
DWORD flags = 0;
OVERLAPPED* overlapped = new OVERLAPPED();
ZeroMemory(overlapped, sizeof(OVERLAPPED));
// 비동기 수신 요청
WSARecv(
clientSocket, // 소켓
&wsaBuf, // 버퍼
1, // 버퍼 수
&bytesReceived, // 받은 바이트 수
&flags, // 플래그
overlapped, // OVERLAPPED 구조체
NULL // 완료 루틴(NULL = 사용 안 함)
);
작업 스레드는 GetQueuedCompletionStatus()를 호출하여 완료된 I/O 작업을 기다리고 처리한다.
ex) 완료 패킷 처리 코드
DWORD WINAPI WorkerThreadProc(LPVOID lpParam) {
HANDLE hCompletionPort = (HANDLE)lpParam;
DWORD bytesTransferred;
ULONG_PTR completionKey;
OVERLAPPED* overlapped;
while (true) {
// 완료 패킷 대기
BOOL result = GetQueuedCompletionStatus(
hCompletionPort,
&bytesTransferred,
&completionKey,
&overlapped,
INFINITE
);
// clientContext 획득
ClientContext* clientContext = (ClientContext*)completionKey;
// 오류 또는 연결 종료 처리
if (!result || bytesTransferred == 0) {
// 클라이언트 연결 정리 로직
CloseSocket(clientContext);
continue;
}
// 완료된 I/O 작업 유형에 따라 처리
if (IsReadOperation(overlapped)) {
// 읽기 작업 처리
ProcessReceivedData(clientContext, bytesTransferred);
// 다음 비동기 읽기 요청 게시
PostRead(clientContext);
}
else if (IsWriteOperation(overlapped)) {
// 쓰기 작업 처리
ProcessWriteCompletion(clientContext, bytesTransferred);
}
}
return 0;
}
I/O 완료 시 관련 컨텍스트 객체에 접근하여 적절한 후속 처리를 수행한다.
일반적으로 새로운 비동기 I/O 요청을 시작하는 것을 포함한다.
4. 결론
4.1. 주의사항
IOCP는 Windows 전용 기술이다.
크로스 플랫폼 애플리케이션 개발 시 다른 운영체제에 대한 추가 추상화 계층이 필요하다.
리눅스, macOS 등에서는 epoll, kqueue 같은 다른 비동기 I/O 모델을 사용해야 한다.
이는 코드 중복과 유지보수 복잡성을 증가시킨다.
하지만 크로스 플랫폼 라이브러리(libuv, Boost.Asio 등)로 이러한 차이를 추상화하는 데 도움이 될 수 있다.
4.2. 정리
IOCP는 Windows 환경에서 대규모 동시 연결을 처리하는 데 최적화된 고성능 비동기 I/O 메커니즘이다.
구현과 사용이 복잡하지만, 고성능 서버 개발을 위해서는 필수적인 기술이다.
고려사항:
- 적절한 스레드 풀 크기 설정
- 효율적인 버퍼 관리와 메모리 풀링
- 비차단 작업에 집중하여 스레드 활용도 극대화
- 철저한 오류 처리와 리소스 정리
'학습 > C++ 소켓 프로그래밍' 카테고리의 다른 글
I/O 모델 비교 (0) | 2025.03.31 |
---|---|
Overlapped I/O (0) | 2025.03.30 |
Non-Blocking 소켓 (0) | 2024.11.09 |
소켓 옵션 (0) | 2024.11.08 |
UDP 소켓 프로그래밍 (0) | 2024.10.31 |