본문 바로가기
programing/Networks

IOCP(Input Output Completion Port)

by RedWiz 2015. 6. 2.

 

IOCP.zip

 

 

 

- Completion Port

> IOCP에서는 IO 완료 정보가 Completion Port 오브젝트라는 커널 오브젝트에 등록

> 소켓과 Completion Port 오브젝트와의 연결 요청

-> 소켓을 기반으로 진행되는 IO 완료 상황을 Completion Port 오브젝트에 등록


> 주의 사항 : I/O 작업에 사용할 버퍼가 I/O 작업을 신청한 시점부터 IOCP로부터 완료했다는 메시지를 받기 전까지 변경이 되면 안 된다는 것이다. 따라서, I/O 작업에 사용하는 버퍼는 전역 변수이거나 동적으로 할당된 메모리 공간이어야 한다. 특히 WSASend()나 WriteFile() 작업을 할 때, 함수를 호출하는 시점에 사용했던 공간을 함수가 끝나자 마자 지워버리거나(지역 변수), 다른 값으로 변경하게 되면, 나중에 IOCP가 시간이 생겨서 이 작업을 할 때, 엉뚱한 공간을 참조하거나, 원래 쓰려고 했던 데이터가 아닌 다른 데이터를 쓰게 되므로 조심해야 한다.

 

- Completion Port 생성

#include <windows.h>

HANDLE CreateIoCompletionPort(

HANDLE FileHandle,                         // 오브젝트 생성시에는 INVALiD_HANDLE_VALUE

HANDLE ExistingCompletioinPort,       // 오브젝트 생성시에는 NULL

ULONG_PTR CompletionKey,             // 오브젝트 생성시에는 0

DWORD NumberOfConcurrentThreads // 오브젝트에 할당되어 완료된 IO를 처리할 쓰레드 수를 전달,

    ex) 2를 전달하면 오브젝트에 할당되어 동시 실행 가능

);    // 성공 : Completion Port 오브젝트의 핸들, 실패 : NULL

> Completion Port 오브젝트 생성 목적으로 호출할 때에는 마지막 매개변수만 의미를 갖음

 

ex) [IO를 처리할 쓰레드 수를 2개로 지정할 경우]

HANDLE hCpObject = CreateIocompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 2);

 

- Completion Port 오브젝트와 소켓 연결

#include <windows.h> 

HANDLE CreateIoCompletionPort(

HANDLE FileHandle,                         // 오브젝트에 연결할 소켓의 핸들

HANDLE ExistingCompletioinPort,       // 소켓과 연결할 Completion Port 오브젝트 핸들

ULONG_PTR CompletionKey,             // 완료된 IO 관련 정보 전달을 위한 매개변수

   GetQueuedCompletionStatus()와 함께 이해

   같이 전달 될 유저 정의 값

DWORD NumberOfConcurrentThreads // 할당할 쓰레드 수

ExistingCompletioinPort가 NULL이 아니면 무시됨

);    // 성공 : Completion Port 오브젝트의 핸들, 실패 : NULL

> CreateIocompletionPort()가 호출된 이후 해당 소켓을 대상으로 진행된 IO가 완료되면 이에 대한 정보가 핸들에 해당하는 Completion Port 오브젝트에 등록된다.

 

ex) [CP오브젝트와 소켓 연결]

HANDLE hCpObject;

SOCKET hSock;

CreateIoCompletionPort((HANDLE)hSock, hCpObject, (DWORD)ioInfo, 0);

 

- Completion Port의 완료된 IO확인과 쓰레드의 IO처리

#include <windows.h>

BOOL GetQueuedCompletionStatus(

HANDLE CompletionPort,                // 완료된 IO 정보가 등록된 Completion Port 오브젝트 핸들

LPDWORD lpNumberOfBytes,          // 송수신 된 데이터의 크기 정보 저장할 변수의 주소

(버퍼 가리키는 포인터)

PULONG_PTR lpCompletionKey,      // CreateIoCompletioinPort()의

세번째 인자로 전달된 값을 저장할 변수 주소

전달 된 유저 정의 값

(버퍼 가리키는 포인터, 포인터를 이용하려면 당연히 더블 포인터 형태가 돼야 함)

LPOVERLAPPED* lpOverlapped,    // WSASend(), WSARecv() 호출시

OVERLAPPED 구조체 변수 주소를 저장할 변수의 주소

(더블 포인터)

DWORD dwMilliseconds                // 타임아웃, 시간 완료시 FALSE 반환하며 함수 빠져나옴

  // INFINITE : 완료 IO가 CP오브젝트에 등록될때까지 무한 대기

);    // 성공 : TRUE, 실패 : FALSE

 

> GetQueuedCompletionStatus()의 세번째 인자(lpCompletionKey)를 통해서 얻게 되는 것

: 소켓과 CP 오브젝트의 연결을 목적으로 CreateIoCompletionPort()의 세번째 인자 lpCompletionKey

 

> GetQueuedCompletionStatus()의 네번째 인자(lpOverlapped)로 얻게 되는 것

: WSASend(), WSARecv() 의 WSAOVERLAPPED 구조체 변수의 주소 값 lpOverlapped

 

cf.

BOOL PostQueuedCompletionStatus(

HANDLE CompletionPort,

DWORD dwNumberOfBytesTransferred,

ULONG_PTR dwCompletionKey,

LPOVERLAPPED lpOverlapped);

> 완료된 입/출력 정보를 IOCP Queue에 직접 전달(Post)하기 위해서 사용하는 API.

> 호출시 GetQueuedCompletionStatus에서 받게 됨

> 예전에는 IOCP응용 ThreadPool을 만들때 이 API를 이용함


cf. 소켓 통신외에 파일 입출력을 할 경우, Overlapped I/O와는 달리 비동기 함수를 이용하면 안된다. 따라서 WriteFileEX(), ReadFileEx()는 사용할 수 없고 WriteFile(), ReadFile()을 이용해야 한다.


* GetQueuedCompletionStatus()는 CreateIoCompletionPort()와 WSASend(), WSARecv()에서 정보를 가져올 수 있음

 

* 구조체의 주소는 구조체 가장 처음 변수의 주소와 같음

-> lpOverlapped 변수를 전달할때 가장 처음 변수로 OVERLAPPED를 포함하는 구조체의 주소를 전달 하면 OVERLAPPED 변수를 이용하는 작업은  첫 주소를 이용하여 진행 되기때문에 이외의 변수에는 영향이 없음 => 주소를 받고 형변환 해서 OVERLAPPED가 포함된 구조체를 이용할 수 가 있음

(업 캐스팅 다운 캐스팅 과 비슷)

 

 

 

- IOCP의 완료된 IO처리를 담당한는 쓰레드가 GetQueuedCompletionStatus() 호출

 

- GetQueuedCompletionStatus()는 어떠한 쓰레드라도 호출 가능하지만, 실제 IO 완료에 대한 응답을 받는 쓰레드의 수는 CreateIoCompletionPort() 호출시 지정한 최대 쓰레드의 수를 넘지 않음

 

- IOCP가 성능이 좀더 나오는 이유

> Non-blocking 방식으로 IO가 진행 -> IO 작업으로 인한 시간의 지연이 발생 안함

> IO가 완료된 핸들을 찾기 위해 반복문을 구성할 필요 없음

> IO의 진행대상인 소켓의 핸들을 배열에 저장해 놓고 관리할 필요 없음

> IO 처리를 위한 쓰레드의 수를 조절 가능. -> 실험적 결과를 토대로 적절한 쓰레드의 수를 지정 가능