본문 바로가기

ETC/Server

IOCP

IOCP(I/O Completion Port)


멀티스레드 윈속서버 프로그램의 경우 user한명당 thread가 1개씩 할당이 된다.

사용자수가 많아지면 thread의 동적생성과 thread간의 잦은 context switching으로 인한 overhead가 크게 증가한다.

이러한 점을 극복하기 위해 IOCP가 만들어졌다.


IOCP는 하나의 thread가 하나 이상의 user request를 처리할 수 있도록 해준다.

즉 thread의 개수를 줄여 context switching의 횟수를 줄여 비용을 절감하는 개념이다.


Multi thread programming에서 유용하게 사용할 수 있으며 그중에서도

socket이나 file, mail slot, Pipe와 같은 입출력 관련 프로그램에서 유용하게 사용된다.


Asynchronous I/O는 server programming 필수이다.

작업의 시작만 async로 하고 작업이 끝나는부분, 즉 I/O결과를 받는 부분은 sync가 되어야 한다.


IOCP객체를 만들어 여러개의 thread, I/O객체와 연결한다.



서버 예제 코드



    

#include 
#include 
#include 
#include 

static const int BUFSIZE = 1024;

typedef struct
{
	SOCKET hClntSock;
	SOCKADDR_IN clntAddr; // socket에 주소와 port 를 할당하기 위해 sockaddr_in 구조체를 이용합니다
} PER_HANDLE_DATA, *LPPER_HANDLE_DATA;

typedef struct
{
	OVERLAPPED overlapped;
	char buffer[BUFSIZE];
	WSABUF wsaBuf;
} PER_IO_DATA, *LPPER_IO_DATA;

unsigned int __stdcall CompletionThread(LPVOID pComPort);
void ErrorHandling(char *message);

#pragma comment(lib, "ws2_32.lib")

int main(int argc, char** argv)
{
	WSADATA wsaData;//윈속 초기화 정보 구조체
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) //윈속 초기화
		ErrorHandling("WSAStartup() error");

	HANDLE hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);

	SYSTEM_INFO SystemInfo;
	GetSystemInfo(&SystemInfo);//시스템정보를 가져온다
	for (int i = 0; i < SystemInfo.dwNumberOfProcessors; ++i)	//프로세서만큼 스레드를 생성
		_beginthreadex(NULL, 0, CompletionThread, (LPVOID)hCompletionPort, 0, NULL);

	SOCKET hServSock = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
	//서버소켓 생성

	SOCKADDR_IN servAddr;
	servAddr.sin_family = AF_INET;
	servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
	//servAddr.sin_port = htons(atoi("2738"));
	servAddr.sin_port = htons(2738);

	bind(hServSock, (SOCKADDR*)&servAddr, sizeof(servAddr));
	//bind() 함수를 이용하여 socket, server socket 에 필요한 정보를 할당하고 커널에 등록
	listen(hServSock, 5);
	//서버리스닝

	LPPER_IO_DATA PerIoData;
	LPPER_HANDLE_DATA PerHandleData;

	int RecvBytes;
	int i, Flags;

	while (TRUE)
	{
		SOCKADDR_IN clntAddr;
		int addrLen = sizeof(clntAddr);

		SOCKET hClntSock = accept(hServSock, (SOCKADDR*)&clntAddr, &addrLen);
		//accept() 클라이언트의 연결요청을 수락하는 함수, 연결요청이 오지않으면 무작정 대기


		PerHandleData = (LPPER_HANDLE_DATA)malloc(sizeof(PER_HANDLE_DATA)); //핸들 동적할당
		PerHandleData->hClntSock = hClntSock;
		memcpy(&(PerHandleData->clntAddr), &clntAddr, addrLen);

		CreateIoCompletionPort((HANDLE)hClntSock, hCompletionPort, (DWORD)PerHandleData, 0);
		//IOCP생성, 맨마지막인자(동시 실행 가능한 스레드의 수)를 0으로넣으면 
                //CPU개수만큼 IOCP객체가 스레드를 관리할 수 있음

		PerIoData = (LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA));
		memset(&(PerIoData->overlapped), 0, sizeof(OVERLAPPED));
		PerIoData->wsaBuf.len = BUFSIZE;
		PerIoData->wsaBuf.buf = PerIoData->buffer;
		Flags = 0;

		WSARecv(PerHandleData->hClntSock, &(PerIoData->wsaBuf), 1, (LPDWORD)&RecvBytes, 
			(LPDWORD)&Flags, &(PerIoData->overlapped), NULL);
		/*
		WSARecv() 접속된 상대방 소켓으로부터 날라온 데이터를 얻어내는 함수
		http://bbs.nicklib.com/library/winsock/WSARecv.html 인자설명
		*/

	
	}

	return 0;
}


unsigned int __stdcall CompletionThread(LPVOID pComPort)
{
	HANDLE hCompletionPort = (HANDLE)pComPort;
	DWORD BytesTransferred;
	LPPER_HANDLE_DATA PerHandleData;
	LPPER_IO_DATA PerIoData;
	DWORD flags;

	while (1)
	{
		GetQueuedCompletionStatus(hCompletionPort, &BytesTransferred, (LPDWORD)&PerHandleData, 
                (LPOVERLAPPED*)&PerIoData, INFINITE);
		//IOCP의 입출력 완료 대기열로부터 입출력 완료를 기다린다, 대기열에 완료가 없으면 완료가 있을때까지 대기
		//https://www.joinc.co.kr/w/man/4200/GetQueuedCompletionStatus 인자정보

		//전송된 데이터크기(BytesTransferred)가 0일경우 
		if (BytesTransferred == 0)
		{
			closesocket(PerHandleData->hClntSock);
			free(PerHandleData);
			free(PerIoData);
			continue;
		}

		PerIoData->wsaBuf.buf[BytesTransferred] = '\0';
		printf("Recv[%s]\n", PerIoData->wsaBuf.buf);

		PerIoData->wsaBuf.len = BytesTransferred;
		WSASend(PerHandleData->hClntSock, &(PerIoData->wsaBuf), 1, NULL, 0, NULL, NULL);
		//접속된 상대방 소켓으로 데이터를 전송
		//http://bbs.nicklib.com/library/winsock/WSASend.html  인자설명
		//PerHandleData->hClntSock 은 접속된 소켓을 가리키는 소켓 기술자이다.

		memset(&(PerIoData->overlapped), 0, sizeof(OVERLAPPED));
		PerIoData->wsaBuf.len = BUFSIZ;
		PerIoData->wsaBuf.buf = PerIoData->buffer;

		flags = 0;
		WSARecv(PerHandleData->hClntSock, &(PerIoData->wsaBuf), 1, NULL, &flags, &(PerIoData->overlapped), NULL);
	}
	return 0;
}

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




클라 예제 코드



    
#define _CRT_SECURE_NO_WARNINGS
#include 
#include 
#include 

void ErrorHandling(char *message);

#pragma warning(disable:4996)
#pragma comment(lib, "ws2_32.lib")

int main()
{
	WSADATA wsaData;
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		ErrorHandling("WSAStartUp() Error!");

	SOCKET hSocket = WSASocket(PF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
	//Overlapped소켓 생성
	if (hSocket == INVALID_SOCKET)
		ErrorHandling("socket() error");

	SOCKADDR_IN recvAddr;
	memset(&recvAddr, 0, sizeof(recvAddr));
	recvAddr.sin_family = AF_INET;
	recvAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
	recvAddr.sin_port = htons(2738);
	//htons함수 = short형 호스트 바이트 순서의 데이터를 네트워크 바이트 순서로 변경한다 

	if (connect(hSocket, (SOCKADDR*)&recvAddr, sizeof(recvAddr)) == SOCKET_ERROR)
		ErrorHandling("connect() error!");
	//connect함수 = 생성한 소켓을 통해 서버로 접속을 요청

	WSAEVENT event = WSACreateEvent();
	//네트워크 이벤트 개체를 생성

	WSAOVERLAPPED overlapped; 
	//이 구조체는 비동기 입출력을 위한 정보를 운영체제에 전달하거나 
	//운영체제가 비동기 입출력 결과를 어플리케이션에 전달할떄 사용
	memset(&overlapped, 0, sizeof(overlapped));

	WSABUF dataBuf;
	char message[1024] = { 0, };
	int sendBytes = 0;
	int recvBytes = 0;
	int flags = 0;

	while (true)
	{
		flags = 0;
		printf("전송할데이터\n");
		scanf("%s", message);

		if (!strcmp(message, "exit"))
			break;

		dataBuf.len = strlen(message);
		dataBuf.buf = message;

		if (WSASend(hSocket, &dataBuf, 1, (LPDWORD)&sendBytes, 0, &overlapped, NULL) == SOCKET_ERROR)
		{
			if (WSAGetLastError() != WSA_IO_PENDING)
				ErrorHandling("WSASend() error");
		}
		WSAWaitForMultipleEvents(1, &event, TRUE, WSA_INFINITE, FALSE);
		//이벤트 객체의 신호 상태 감지하기

		WSAGetOverlappedResult(hSocket, &overlapped, (LPDWORD)&sendBytes, FALSE, NULL);
		//입출력 결과를확인

		printf("전송된 바이트수 : %d \n", sendBytes);

		if (WSARecv(hSocket, &dataBuf, 1, (LPDWORD)&recvBytes, (LPDWORD)&flags, &overlapped, NULL) == SOCKET_ERROR)
		{
			if (WSAGetLastError() != WSA_IO_PENDING)
				ErrorHandling("WSASend() error");
		}
		printf("Recv[%s] \n", dataBuf.buf);
	}
	closesocket(hSocket);
	WSACleanup();
	return 0;
}

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



'ETC > Server' 카테고리의 다른 글

HTTP / FTP / Socket  (0) 2019.03.11
Multi play game의 server 형태와 그 특징  (0) 2019.03.07
Dedicated Server / P2P Server / Listen Server  (0) 2019.03.07
Network Transport / RPC  (0) 2019.03.05
Get방식과 Post방식  (0) 2019.02.26