반응형

 

소켓은 기본적으로 봉쇄/동기로 만들어진다. 

이 모델은 데이터 입출력 부분에서 봉쇄(blocking)된다는 문제점을 가진다.

 

이런 모델로는 하나의 쓰레드에서 두 개 이상의 소켓을 다루기가 힘들다. 그래서 모델을 바꾸지 않고 두 개 이상의 소켓을 처리 하려면 멀티 쓰레드를 사용하는 수 밖에 없다.

 

하지만 윈도우 운영체제는 멀티 태스킹을 지원한다. 이는 입출력 모델과는 상관없이, 커널은 여러 소켓으로 부터의 입력을 처리 한다는 의미이다. 단지 이 기능을 봉쇄(blocking)/동기 입출력 모델의 한계로 쓰지 못하는 것일 뿐이다.

 

이 문제는 입출력 모델을 비동기/봉쇄(blocking) 혹은 비동기/비봉쇄(non-blocking)를 쓰는 것으로 해결할 수 있다.

 

비동기/봉쇄(blocking) 모델을 사용하는 기술이 select 함수를 이용한 입출력 다중화이다.

 

이번에 정리할 Overlapped I/O 모델은 비동기/비봉쇄(non-blocking)의 응용 모델이다.
즉, Overlapped는 non-blocking+(비동기적) 완료 통보 라고 보면된다.


I/O(입출력)의 중첩

  • I/O의 중첩이라는 것은 쓰레드의 관점에서 동시에 둘 이상의 데이터 전송을 중첩시키는 것.
  • 데이터 전송을 중첩시키기 위해서는 데이터의 입출력 함수가 non-blocking 모드로 동작

데이터 전송의 대상이 소켓 BCD로 구분 -> IO 중첩은 하나의 소켓을 대상으로 진행

Overlapped I/O

  • Overlapped IO가 아니더라도 입출력을 중첩시킬 수 있다.
  • Overlapped IO의 중점은 입출력의 완료 확인방법에 있다.
  • select가 아닌 정확한 비동기는 입출력에 대한 명령을 커널로 전송 후 끝났을 때 signal을 자동으로 받아 특정 동작을 수행하는 것 => 이를 Overlapped I/O를 사용.
  • User와 Kernel 사이에서 계속해서 확인
  • WSASend 와 WSARecv 함수를 이용한다.(기존의 send와 recv 함수의 기능을 모두 다 수행 하면서, 아래의 두가지 항목을 추가적으로 지원한다.
    • 중복된 전송 연산을 수행하도록 중복(Overlapped)소켓을 가지고 작업할 수 있는 기능
    • 여러개의 전송 버퍼를 두어 전송 할 수 있는 기능

 

Completion Routine

    • Completion Routine의 이해와 등록
      • 등록과 호출로 이루어짐.
      • 입출력이 완료되었을때 호출되는 함수를 가리켜 Completion Routine이라 한다.
      • 입출력이 안료되면, 미리 등록된 Completion Routine이 운영체제에 의해서 자동으로 호출
      • Completion Routine이 호출되기 위해서는 해당 쓰레드가 alert wait상태에 놓여야 한다.
      • Alert wait은 운영체제가 전달하는 메시지의 수신이 가능한 상태를 말한다.
    • Alertable wait 상태로 진입에 사용되는 함수들
      • WaitForSingleObjectEX
      • WaitForMultipleObjectsEx
      •  WSAWaitForrMultipleEvents
      •  SleepEX
    • Completion Routine 기반의 입출력 완료 확인은 함수의 등록과 등록된 함수의 호출을 통해서 이뤄진다. 단, 등록된 함수가 호출될 수 있도록 쓰레드는 Alert wait 상태가 되어야 한다.
    • Completion Routine의 예
/*
## 소켓 서버 : 1 v n - overlapped callback
1. socket()            : 소켓생성
2. bind()            : 소켓설정
3. listen()            : 수신대기열생성
4. accept()            : 연결대기
5. read()&write()
	WIN recv()&send    : 데이터 읽고쓰기
6. close()
	WIN closesocket    : 소켓종료
*/

#include "stdafx.h"

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

#define MAX_BUFFER 1024	
#define SERVER_PORT 3598

struct SOCKETINFO
{
	WSAOVERLAPPED overlapped;
	WSABUF dataBuffer;
	SOCKET socket;
	char messageBuffer[MAX_BUFFER];
	int receiveBytes;
	int sendBytes;
};

void CALLBACK callback(DWORD Error, DWORD dataBytes, LPWSAOVERLAPPED overlapped, DWORD Inflags);

int _tmain(int argc, _TCHAR* argv[])
{
	//Winsock Start - winsock.dll 로드
	WSADATA WSAData;

	if (WSAStartup(MAKEWORD(2, 2), &WSAData) != 0)
	{
		printf("Error - Can not load 'winsock.dll' file\n");
		return 1;
	}

	//1.소켓 생성
	SOCKET listenSocket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);

	if (listenSocket == INVALID_SOCKET)
	{
		printf("Error - Invalid socket\n");
		return 1;
	}

	//서버정보 객체 설정
	SOCKADDR_IN serverAddr;
	memset(&serverAddr, 0, sizeof(SOCKADDR_IN));
	serverAddr.sin_family = PF_INET;
	serverAddr.sin_port = htons(SERVER_PORT);
	serverAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);

	//2. 소켓 설정
	if (bind(listenSocket, (struct sockaddr*)&serverAddr, sizeof(SOCKADDR_IN)) == SOCKET_ERROR)
	{
		printf("Error - Fail bind\n");
		// 6. 소켓종료
		closesocket(listenSocket);
		// Winsock End
		WSACleanup();
		return 1;
	}

	//3. 수신대기열 생성
	if (listen(listenSocket, 5) == SOCKET_ERROR)
	{
		printf("Error - Fail listen\n");
		// 6. 소켓종료
		closesocket(listenSocket);
		// Winsock End
		WSACleanup();
		return 1;
	}

	SOCKADDR_IN clientAddr;
	int addrLen = sizeof(SOCKADDR_IN);
	memset(&clientAddr, 0, addrLen);
	SOCKET clientSocket;
	SOCKETINFO* socketInfo;
	DWORD receiveBytes;
	DWORD flags;

	while (1)
	{
		clientSocket = accept(listenSocket, (struct sockaddr*)&clientAddr, &addrLen);
		if (clientSocket == INVALID_SOCKET)
		{
			printf("Error - Accept Failure\n");
			return 1;
		}

		socketInfo = (struct SOCKETINFO*)malloc(sizeof(struct SOCKETINFO));
		memset((void*)socketInfo, 0x00, sizeof(struct SOCKETINFO));
		socketInfo->socket = clientSocket;
		socketInfo->dataBuffer.len = MAX_BUFFER;
		socketInfo->dataBuffer.buf = socketInfo->messageBuffer;
		flags = 0;

		// 중첩 소캣을 지정하고 완료시 실행될 함수를 넘겨준다.
		//callback -> Completion Routine 등록
		//&(socketInfo->overlapped) -> Overlapped IO를 위한 인자의 전달
		if (WSARecv(socketInfo->socket, &socketInfo->dataBuffer, 1, &receiveBytes, &flags, &(socketInfo->overlapped), callback))
		{
			if (WSAGetLastError() != WSA_IO_PENDING)
			{
				printf("Error - IO pending Failure\n");
				return 1;
			}
		}
	}

	// 6-2. 리슨 소켓종료
	closesocket(listenSocket);

	// Winsock End
	WSACleanup();
}


//운영체제가 사용자에게 알리는 Callback  함수
//Callback 함수를 통해 recv 호출이 완료되면 호출되도록 구현
void CALLBACK callback(DWORD Error, DWORD dataBytes, LPWSAOVERLAPPED overlapped, DWORD lnFlags)
{
	struct SOCKETINFO* socketInfo;
	DWORD sendBytes = 0;
	DWORD receiveBytes = 0;
	DWORD flags = 0;

	socketInfo = (struct SOCKETINFO*)overlapped;
	memset(&(socketInfo->overlapped), 0x00, sizeof(WSAOVERLAPPED));

	if (dataBytes == 0)
	{
		closesocket(socketInfo->socket);
		free(socketInfo);
		return;
	}

	if (socketInfo->receiveBytes == 0)
	{
		// WSARecv(최초 대기에 대한)의 콜백일 경우
		socketInfo->receiveBytes = dataBytes;
		socketInfo->sendBytes = 0;
		socketInfo->dataBuffer.buf = socketInfo->messageBuffer;
		socketInfo->dataBuffer.len = socketInfo->receiveBytes;

		printf("TRACE - Receive message : %s (%d bytes)\n", socketInfo->messageBuffer, dataBytes);

		if (WSASend(socketInfo->socket, &(socketInfo->dataBuffer), 1, &sendBytes, 0, &(socketInfo->overlapped), callback) == SOCKET_ERROR)
		{
			if (WSAGetLastError() != WSA_IO_PENDING)
			{
				printf("Error - Fail WSASend(error_code : %d)\n", WSAGetLastError());
			}
		}
	}
	else
	{
		// WSASend(응답에 대한)의 콜백일 경우
		socketInfo->sendBytes += dataBytes;
		socketInfo->receiveBytes = 0;
		socketInfo->dataBuffer.len = MAX_BUFFER;
		socketInfo->dataBuffer.buf = socketInfo->messageBuffer;

		printf("TRACE - Send message : %s (%d bytes)\n", socketInfo->messageBuffer, dataBytes);

		if (WSARecv(socketInfo->socket, &socketInfo->dataBuffer, 1, &receiveBytes, &flags, &(socketInfo->overlapped), callback) == SOCKET_ERROR)
		{
			if (WSAGetLastError() != WSA_IO_PENDING)
			{
				printf("Error - Fail WSARecv(error_code : %d)\n", WSAGetLastError());
			}
		}
	}
}

 

 

 

반응형