IOCP (Input Output Completion Port)

개요

TCP/IP 공부를 하면 크게는 동기 통신 이후 비동기 통신을 배우는 그 다음이 윈도우는 IOCP, 리눅스는 epoll이다. 간단히 말해 통신자체는 비동기로 수행을 하고(Non-Blocked), 결과확인을 IO쓰레드에서 전담한다는 것이다.

이론

Completion Port의 생성

    
 #include <windows.h>
 HANDLE CreateIoCompletionPort( HANDLE FileHandle, HANDLE ExistingCompletionPort, UNLONG_PTR CompltionKey, DWORD NumberOfConcurrentThreads );
//-> 성공 시 CP 오브젝트의 핸들, 실패 시 NULL 반환
    
  • FileHandle : CP 오브젝트 생성시에는 INVALID_HANDLE_VALUE를 전달
  • ExistingCompletionPort : CP 오브젝트 생성시에는 NULL 전달
  • CompltionKey : CP 오브젝트 생성시에는 0 전달
  • NumberOfConcurrentThreads : CP 오브젝트에 할당되어 완료된 IO를 처리할 쓰레드의 수를 전달, 예를 들어 2가 전달되면 CP 오브젝트에 할당되어 동시실행 가능한 쓰레드의 수는 최대 2개로 제한된다. 그리고 이인자에 0이 전달되면 시스템의 CPU개수가 동시 실행 가능한 쓰레드의 최대수로 지정된다.

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

    
 #include <windows.h>
 HANDLE CreateIoCompletionPort( HANDLE FileHandle, HANDLE ExistingCompletionPort, UNLONG_PTR CompltionKey, DWORD NumberOfConcurrentThreads );
//-> 성공 시 CP 오브젝트의 핸들, 실패 시 NULL 반환
    
  • FileHandle : CP 오브젝트에 연결할 소켓의 핸들 전달.
  • ExistingCompletionPort : 소켓과 연결할 CP오브젝트의 핸들 전달.
  • CompltionKey : 완료된 IO 관련 정보의 전달을 위한 매개변수, 이는 잠시후에 소개하는 GetQueuedCompletionStatus 함수와 함께 이해해야한다.
  • NumberOfConcurrentThreads : 어떠한 값을 전달하건, 이 함수의 두번째 매개변수가 NULL이 아니면 그냥 무시된다.

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

    
 #include <windows.h>
 BOOL GetQueuedCompletionStatus( HANDLE CompletionPort, LPDWORD lpNumberOfBytes, PULONG_PTR lpCompletionKey, LPOVERLAPPED* lpOverlapped, DWORD dwMilliseconds);
//-> 성공 시 TRUE, 실패 시 FALSE
   
  • CompletionPort : 완료된 IO 정보가 등록되어 있는 CP 오브젝트의 핸들 전달
  • lpNumberOfBytes : 입출력 과정에서 송수신 된 데이터의 크기정보를 저장할 변수의 주소 값 전달.
  • lpCompletionKey : CreateIoCompletionPort 함수의 세 번째 인자로 전달된 값의 저장을 위한 변수의 주소 값 전달.
  • lpOverlapped : WSASend, WSARecv 함수호출 시 전달하는 OVERLAPPED 구조체 변수의 주소 값이 저장될, 변수의 주소 값 전달.
  • dwMilliseconds : 타임아웃 정보전달, 여기서 지정한 시간이 완료되면 FALSE를 반환하면서 함수를 빠져나가며, INFINITE를 전달하면 완료된 IO가 CP오브젝트에 등록될 때까지 블로킹 상태에 있게 된다.

소스

    
// - server
#include <WinSock2.h>
#include <stdio.h>
#include <stdlib.h>
#include <process.h>
#include <windows.h>

#define  BUF_SIZE 100
#define READ 3
#define WRITE 5

typedef struct
{
	SOCKET hClntSock;
	SOCKADDR_IN clntAdr;
} PER_HANDLE_DATA, *LPPER_HANDLE_DATA;

typedef struct 
{
	OVERLAPPED overlapped;
	WSABUF wsaBuf;
	char bufffer[BUF_SIZE];
	int rwMode;
} PER_IO_DATA, *LPPER_IO_DATA;

unsigned int WINAPI EchoThreadMain(LPVOID CompletionPortIO);
void ErrorHandling(char *message);
int main(int argc, char* argv[])
{
	WSADATA wsaData;
	HANDLE hComPort;
	SYSTEM_INFO sysInfo;
	LPPER_IO_DATA ioInfo;
	LPPER_HANDLE_DATA handleInfo;

	SOCKET hServSock;
	SOCKADDR_IN servAdr;
	int recvBytes, i, flags = 0;
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		ErrorHandling("WSAStartup() error!");
	hComPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
	GetSystemInfo(&sysInfo);
	for (i = 0; i < sysInfo.dwNumberOfProcessors; i++)
	{
		_beginthreadex(NULL, 0, EchoThreadMain, (LPVOID)hComPort, 0, NULL);
	}
	hServSock = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
	memset(&servAdr, 0, sizeof(servAdr));
	servAdr.sin_family = AF_INET;
	servAdr.sin_addr.s_addr = htonl(atoi(argv[1]));

	bind(hServSock, (SOCKADDR*)&servAdr, sizeof(servAdr));
	listen(hServSock, 5);

	while (1)
	{
		SOCKET hClntSock;
		SOCKADDR_IN clntAdr;
		int addrLen = sizeof(clntAdr);
		hClntSock = accept(hServSock, (SOCKADDR*)&clntAdr, &addrLen);
		handleInfo = (LPPER_HANDLE_DATA)malloc(sizeof(PER_HANDLE_DATA));
		handleInfo->hClntSock = hClntSock;
		memcpy(&(handleInfo->clntAdr), &clntAdr, addrLen);

		CreateIoCompletionPort((HANDLE)hClntSock, hComPort, (DWORD)handleInfo, 0);

		ioInfo = (LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA));
		memset(&(ioInfo->overlapped), 0, sizeof(OVERLAPPED));
		ioInfo->wsaBuf.len = BUF_SIZE;
		ioInfo->wsaBuf.buf = ioInfo->bufffer;
		ioInfo->rwMode = READ;
		WSARecv(handleInfo->hClntSock, &(ioInfo->wsaBuf), 1, (LPDWORD)&recvBytes, (LPDWORD)&flags, &(ioInfo->overlapped), NULL);
	}
	return 0;
}

unsigned int WINAPI EchoThreadMain(LPVOID pComPort)
{
	HANDLE hComPort = (HANDLE)pComPort;
	SOCKET sock;
	DWORD bytesTrans;
	LPPER_HANDLE_DATA handleInfo;
	LPPER_IO_DATA ioInfo;
	DWORD flags = 0;

	while (1)
	{
		GetQueuedCompletionStatus(hComPort, &bytesTrans, (LPDWORD)&handleInfo, (LPOVERLAPPED*)&ioInfo, INFINITE);
		sock = handleInfo->hClntSock;

		if (ioInfo->rwMode = READ)
		{
			puts("message received!");
			if (bytesTrans == 0)
			{
				closesocket(sock);
				free(handleInfo);
				free(ioInfo);
				continue;
			}

			memset(&(ioInfo->overlapped), 0, sizeof(OVERLAPPED));
			ioInfo->wsaBuf.len = bytesTrans;
			ioInfo->rwMode = WRITE;
			WSASend(sock, &(ioInfo->wsaBuf), 1, NULL, 0, &(ioInfo->overlapped), NULL);
			ioInfo = (LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA));
			memset(&(ioInfo->overlapped), 0, sizeof(OVERLAPPED));
			ioInfo->wsaBuf.len = BUF_SIZE;
			ioInfo->wsaBuf.buf = ioInfo->bufffer;
			ioInfo->rwMode = READ;
			WSARecv(sock, &(ioInfo->wsaBuf), 1, NULL, &flags, &(ioInfo->overlapped), NULL);
		}
		else
		{
			puts("message sent!");
			free(ioInfo);
		}
	}
	return 0;
}

void ErrorHandling(char* message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}
   

출처

출처 - 열혈 TCP/IP 소켓 프로그래밍