2009. 7. 24. 14:19

[Programming][Network] IOCP(I/O Completion Port)


IOCP IOCP(I/O Completion Port)는 어떤 I/O 핸들에 대해서, 블록 되지 않게 함으로 프로그램 대기시간을 줄이는 목적으로 사용된다.
우선 Overlapped IO(비동기 IO작업)의 개념이 기반이 된다. IOCP는 이런 Overlapped IO가 실행이되고 알려주는 방법에 대한 것이다.
그리고 IOCP는 커널객체이다. 뮤텍스나 스레드와 같이 커널에서 제공을 한다. (커널 객체는 일반적으로 전역적으로 사용되는 개념인거 같다.;;)
사용 이점 사용자가 설정한 버퍼만을 사용하기 때문에 더 효율적으로 작동한다. (기존에는 OS버퍼, 사용자 버퍼로 따로 분리되는 개념이었다.)

IO요청에 대해서 효율적으로 접근한다. 디스크IO의 경우 디스크에 접근을 똑똑하게 한다-_-;(순서대로가 아닌 효율적인 순서에 따라 접근하기도 한다.는 뜻;;)

Raid나 여러개의 랜카드같은 Deviced-level Parallelism을 지원한다. 역시나 효율적으로 할 수 있다.

사용 순서 1. IOCP핸들을 생성한다.
2. 핸들의 키와 쌍으로 IOCP핸들에 등록한다.
3. 핸들에 I/O 작업을 한다. (I/O작업을 신청한다는 의미가 더 어울릴 것 같다.)
4. 어떤 핸들에 대한 I/O작업이 완료되면 IOCP가 프로그램에게 알려준다.
5. 완료된 작업에 대해 해야 할 일을 한다.


윈속API는 버전 1.1에서는 OverlappedIO를 지원하지 않는다. 반드시 2.2를 사용할것.

소켓 생성(WSASocket)을 할때 마지막에 반드시 WSA_FLAG_OVERLAPPED를 넣어줄것. 반드시란다. 버그가 있기때문이란다;;
WSASend, WSARecv사용할때 Overlapped구조체를 넣어줄것. LPWSAOVERLAPPED나 LPOVERLAPPED는 그놈이 그놈임-_-;
LPWSAOVERLAPPED_COMPLETION_ROUTINE는 콜백루틴을 넣는것인데 버그이기때문에 사용 금지다-_-;
 
사용법 1. IOCP핸들을 생성한다.
HANDLE CreateIoCompletionPort (
     HANDLE FileHandle,              // 목적하는 핸들.
     HANDLE ExistingCompletionPort,  // 연결할 IOCP의 핸들. 새로만들려면 NULL로 설정. 
     ULONG_PTR CompletionKey,        // 핸들을 통한 작업이 끝났을때 IOCP는 이 컴플리션 키값을 넘겨준다.
     DWORD NumberOfConcurrentThreads // 입출력 작업시 얼마나 많은 스레드를 사용할건지 설정. 0으로하면 시스템에서 알아서 잡아줌.
);
성공시 IOCP핸들을 반환. 실패시 0.
 
처음 생성시 인자로 INVALID_HANDLE, NULL, NULL, NULL를 넣어줌.

2. 핸들의 키와 쌍으로 IOCP핸들에 등록한다.
HANDLE CreateIoCompletionPort (
     HANDLE FileHandle,              // 목적하는 핸들.
     HANDLE ExistingCompletionPort,  // 연결할 IOCP의 핸들. 새로만들려면 NULL로 설정. 
     ULONG_PTR CompletionKey,        // 핸들을 통한 작업이 끝났을때 IOCP는 이 컴플리션 키값을 넘겨준다.
     DWORD NumberOfConcurrentThreads // 입출력 작업시 얼마나 많은 스레드를 사용할건지 설정. 0으로하면 시스템에서 알아서 잡아줌.
);
같은 함수를 사용해서 ExistingCompletionPort에 위의 생성한 IOCP핸들을 넣어준다. 연결된 IOCP핸들이 반환된다. 

3. 핸들에 I/O 작업을 한다. (I/O작업을 신청한다는 의미가 더 어울릴 것 같다.)
IOCP는 중첩된(Overlapped)된 I/O방식을 사용한다. 
따라서, WSASend(), WSARecv(), ReadFile(), WriteFile()를 사용할 것을 권한다.
보통의 IO함수랑 다른 차이점은 Overlapped구조체를 요구한다. 대부분 필요에 따라 구조체를 변형시켜서(상속, 포함)사용한다.
그리고 여기서 설정된 Overlapped구조체나 데이터들은 작업이 완료될때까지 변경되어서는 안된다. 
비동기로 요청을 하였으므로 바로 반환된다. 그러므로 SOCKET_ERROR을 반환하지만 에러는 아니다.
반드시 GetLastError()로 조사를 하고 ERROR_IO_PENDING이 나오면 정상적인 비동기 작업이다.

4. 어떤 핸들에 대한 I/O작업이 완료되면 IOCP가 프로그램에게 알려준다.
BOOL GetQueuedCompletionStatus(
     HANDLE CompletionPort,       // IOCP 핸들
     LPDWORD lpNumberOfBytes,     // 읽거나 쓰여진 바이트의 수
     PULONG_PTR lpCompletionKey,  // IOCP를 생성시 등록된 CompletionKey값을 반환
     LPOVERLAPPED *lpOverlapped,  // Overlapped 구조체
     DWORD dwMilliseconds         // 기다리는 시간. INFINITE로 무한대로 기다릴수 있음.
);
반환값은 FALSE라고해서 실패는 아니고. 반환되는 lpOverlapped구조체가 NULL이고 GetLastError()로 조사시 64의 값이 아닐때 에러란다-_-;
 
BOOL PostQueuedCompletionStatus (
     HANDLE CompletionPort,            // IOCP 핸들
     DWORD dwNumberOfBytesTransferred, // 읽거나 쓰여질 바이트의 수
     ULONG_PTR dwCompletionKey,        // 등록할 CompletionKey값
     LPOVERLAPPED lpOverlapped         // Overlapped 구조체
);
IOCP가 반드시 파일이나, 네트웍의 전송으로만 사용되어야 할 필요는 없다.
스레드간의 통신에서도 이렇게 CompletionKey값을 주고받는것으로 대체 할 수도 있다.

5. 완료된 작업에 대해 해야 할 일을 한다.
수행된 내용의 다음 내용을 처리한다.


출처 : http://cafe.naver.com/cyberzone/219