- 입력과 출력의 완료를 담당할 포트를 지정해서 처리하겠다는 의미로, 프로그램이 소켓이나 파일 등의 핸들에 대해서 IO 처리를 할 때, 블록되지 않게 하여 프로그램의 대기시간을 줄여주는 방법
[IOCP 흐름]
- 입출력이 완료되면 입출력 완료 대기열(큐)에 쌓인다.
대기열에 쌓이면 워커 쓰레드가 대기열(큐)에 있는 완료보고를 감지하고 사용자 버퍼를 읽어 작업(처리)한다.
[IOCP 장점]
- 처리할 소켓 수 증가에 따른 쓰레드 개수를 최소화
** 클라이언트 연결이 있을때 마다 쓰레드를 생성하는 멀티 쓰레드 방식과 달리 미리 일정한 쓰레드를 만들어 두고 I/O 완료(변화)를 기다릴 수 있음(보통 시스템 cpu 개수 * 2 개의 쓰레드 생성)
** 쓰레드 풀과 비슷해 보이지만 쓰레드 풀 방식에서는 쓰레드에 작업을 할당하기 위해서 개발자가 직접 구현, 하지만 IOCP 는 OS 가 알아서 쓰레드를 선택하여 작업 처리를 해줌
- Select 방식과 비교했을 때
** Select 의 경우 I/O 들의 변화를 감지하기 위해서 I/O 상태를 나타내는 배열을 루프를 돌며 일일이 하나씩 검사해야 하기 때문에, 생성된 I/O 만큼의 시간 지연
** IOCP 는 작업 쓰레드 내에서 I/O 를 Completion Queue 로 부터 감지하여 처리하기 때문에 따로 변화감지를 위해서 일일이 검사하는 루프를 돌지 않음
- Linux Epoll 방식과 비교했을 때
** Linux epoll 의 경우 커널 영역의 데이터 스트림을 유저영역으로 복사하는 만큼 지연
** IOCP 는 이벤트를 받아 이벤트 루틴이 깨어날 때 이미 데이터 스트림이 유저 영역에 있어 바로 작업 쓰레드로 넘겨 지연 없이 다음 이벤트를 기다릴 수 있음
[IOCP 단점]
- 클라이언트가 적다면 비교적 구현이 간단한 select 방식 이용
[IOCP 를 사용하려면...]
- OS : Windows 2000, 2003, 2008 Server
- Library : Kernel32.lib (Kernel32.dll)
- Function : CreateIoCompletionPort(), GetQueuedCompletionStatus()
- Winsock2.2 function : WSAStartup(), WSACleanup(), WSASocket(), WSAAccept() etc...
[IOCP 사용 과정]
- IOCP 핸들 생성 및 IOCP 핸들에 소켓 등록
- 소켓에 대한 I/O 작업이 완료되면 큐에 기록되고 쓰레드가 큐를 읽어 작업 수행(IOCP 감시)
[IOCP 핸들 생성 및 IOCP 핸들에 소켓 등록]
- 함수
HANDLE CreateIoCompletionPort(
HANDLE FileHandle,
HANDLE ExistingCompletionPort,
ULONG_PTR CompletionKey,
DWORD NumberOfConcurrentThreads)
- 파라미터
FileHandle : IOCP에 등록할 파일(or소켓) 핸들
ExistingCompletionPort : 새로운 포트를 생성하려면 NULL, 기존의 IOCP 에 연결하려면, CreateIoCompletionPort()가 이전에 리턴했던 핸들 사용
CompletionKey : 핸들에 대한 작업이 끝나고 알려줄 때 핸들 값 자체를 넘겨주는 것이 아니라, 키 값을 알려준다. 여러 개의 핸들을 등록할 때 키 값을 각각의 핸들마다 고유해야 한다.
NumberOfConcurrentThreads : IOCP 가 입출력 작업할 때 얼마나 많은 쓰레드를 사용할지 설정한다. 가장 최적화된 쓰레드 개수를 지정하려면 0으로 설정한다.
- 사용예
// IOCP 핸들 생성
HANDLE g_hIOCP = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, NULL, NULL);
// IOCP 핸들에 소켓 등록
SOCKET client_socket;
int key = 1;
g_hIOCP = ::CreateIoCompletionPort(client_socket, g_hIOCP, key, 0);
[IOCP 감시(쓰레드에서 사용)]
- 함수
BOOL GetQueuedCompletionStatus(
HANDLE CompletionPort,
LPDWORD lpNumberOfBytes,
PULONG_PTR lpCompletionKey,
LPOVERLAPPED* lpOverlapped,
DWORD dwMilliseconds)
- 파라미터
CompletionPort : IOCP 핸들
lpNumberOfBytes : 완료된 I/O 의 총 바이트 수
lpCompletionKey : 핸들간의 구분을 위한 키
lpOverlapped : Overlapped 구조체 주소
dwMilliseconds : 타임아웃 (INFINITE 값을 주면 I/O 완료까지 블록)
- 사용예
// 쓰레드 함수 내에서
Int read;
Int key;
OVERLAPPED *overlapped;
// IOCP 로 완료한 입출력이 있는지 큐에서 확인한다.
GetQueuedCompletionStatus(g_hIOCP, &read, &key, (LPOVERLAPPED*)&overlapped, INFINITE);
HANDLE hIOCP = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, NULL, NULL);
// read 를 읽어서 0이 아니라면 작업 수행
If (read == 0) continue;
// do something…