- 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 처리를 위한 쓰레드의 수를 조절 가능. -> 실험적 결과를 토대로 적절한 쓰레드의 수를 지정 가능
'programing > Networks' 카테고리의 다른 글
[펌] Windows Registered I/O (RIO) vs IOCP (0) | 2015.06.03 |
---|---|
[펌] 게임 서버의 구조 (0) | 2015.06.02 |
Overlapped IO 모델 (0) | 2015.06.01 |
Asynchronous Notification IO 모델 (0) | 2015.05.22 |
표준 입출력 함수, 입출력 스트림, select()의 문제, 쓰레드 (0) | 2015.05.21 |