본문 바로가기
프로그래밍/네트워크

IOCP 입출력 완료 포트 생성 및 연결 그리고 입출력 과정

by bantomak 2024. 8. 7.

CreateIoCompletionPort()

CreateIoCompletionPort() 메서드는 두 가지 역할을 한다. 하나는 입출력 완료 포트를 새로 생성하는 일이고, 다른 하나는 소켓과 입출력 완료 포트를 연결하는 일이다. 소켓과 입출력 완료 포트를 연결해 두면 해당 소켓에 대한 비동기 입출력 결과가 입출력 완료 포트에 저장된다.

 

HANDLE WINAPI CreateIoCompletionPort(
  _In_     HANDLE    FileHandle, // IOCP와 연결할 파일 핸들이나 소켓, INVALID_HANDLE_VALUE값 전달시 신규 생성
  _In_opt_ HANDLE    ExistingCompletionPort, // 연결할 IOCP 핸들, NULL이면 새 IOCP 생성
  _In_     ULONG_PTR CompletionKey, // 입출력 완료 패킷(비동기 입출력 작업 완료시 생성되어 IOCP에 저장됨)
  _In_     DWORD     NumberOfConcurrentThreads // 동시에 실행할 수 있는 스레드 수, 0 입력시 CPU 수만큼 스레드 수를 맞춤
); // 성공 : 입출력 완료 포트 핸들, 실패 : NULL

 

입출력 완료 포트 생성 코드 예제

//IOCP 생성
g_hIocp = ::CreateIoCompletionPort(
    INVALID_HANDLE_VALUE,	//연결된 파일 없음.
    NULL,			//기존 핸들 없음.
    0,				//식별자(Key) 해당되지 않음.
    0);				//스레드 개수는 OS에 맡김.
if (g_hIocp == NULL)
{
    puts("ERORR: IOCP를 생성할 수 없습니다.");
    return 0;
}

 

기존 소켓과 입출력 완료 포트를 연결하는 코드 예제

파일 혹은 소켓 핸들과 같이 ::CreateIoCompletionPort() 메서드를 호출하면 해당 소켓을 Iocp에 등록하고 감시하게 된다.

//(연결된) 클라이언트 소켓 핸들을 IOCP에 연결.
::CreateIoCompletionPort((HANDLE)hClient,
    g_hIocp,
    (ULONG_PTR)pNewUser, //KEY!!!
    0);

 

비동기 입출력 결과 확인하기

작업자 스레드는 GetQueuedCompletionStatus() 메서드를 호출함으로써 입출력 완료 포트에 입출력 완료 패킷이 들어올 때까지 대기한다. 입출력 완료 패킷이 입출력 완료 포트에 들어오면 OS는 실행 중인 작업자 스레드의 개수를 체크한다. 이 값은 CreateIoCompletionPort() 메서드의 네 번째 인자로 설정한 값보다 작다면, 대기 상태인 작업자 스레드를 깨워 입출력 완료 패킷을 처리하게 된다.

 

BOOL GetQueuedCompletionStatus(
  [in]  HANDLE       CompletionPort,             // IOCP 핸들
        LPDWORD      lpNumberOfBytesTransferred, // OUT 비동기 입출력 작업으로 전송된 byte수
  [out] PULONG_PTR   lpCompletionKey,            // OUT CreateCompletionPort() 함수 호출시 사용햇던 3번째 인자
  [out] LPOVERLAPPED *lpOverlapped,              // OUT 비동기 입출력 함수 호출 시 전달한 OVERLAPPED 구조체 주소
  [in]  DWORD        dwMilliseconds              // 작업자 스레드가 대기할 시간 (ms)
);

 

비동기 입출력 결과받기 코드 예제

DWORD			dwTransferredSize = 0;
DWORD			dwFlag = 0;
USERSESSION		*pSession = NULL;
LPWSAOVERLAPPED	pWol = NULL;
BOOL			bResult;

while (1)
{
    bResult = ::GetQueuedCompletionStatus(
        g_hIocp,				//Dequeue할 IOCP 핸들.
        &dwTransferredSize,		//수신한 데이터 크기.
        (PULONG_PTR)&pSession,	//수신된 데이터가 저장된 메모리
        &pWol,					//OVERLAPPED 구조체.
        INFINITE);				//이벤트를 무한정 대기.

    if (bResult == TRUE)
    {
        // ........ 성공적으로 전송 완료
    }

}

 

함수 실패 시 *lpOverlapped의 값이 NULL로 변한다. 하지만, 실패 시엔 Transferred, Key 인자의 값이 변하지 않기 때문에 재활용 시 예전의 값을 갖고 있을 수 있다. 따라서 초기화가 필수적이다.

 

응용 프로그램이 작업자 스레드에 특별한 사실을 알리기 위해 직접 입출력 완료 패킷을 생성할 수도 있다. 이때 사용하는 함수는 PostQueuedCompletionStatus()이다.

댓글