728x90

Overlapped IO 기반 에코 서버의 구조

- accept 함수를 통해서 client 연결 요청을 받음

- 연결 요청이 없으면 aceept가 blocking 상태가 되니 리스닝 소켓을 non-blocking 소켓으로 변경

- 연결 요청이 없는 상태에서 accept 시 INVALID_SOCKET 에러가 반환되는데 accept에 에러가 없으면 WSAGetLastError 호출 시 WSAEWOULDBLOCK가 반환

- 새로운 클라이언트 연결 요청이 들어오면 WSAOverlapped 구조체를 생성해서 Recv 호출

- Overlapped 구조체는 recv, send시 completion routine에 전달되기때문에 그 안에 event 오브젝트에 정보를 담음

typedef struct
{
	SOCKET hClntSock;
	char buf[BUF_SIZE];
	WSABUF wsaBuf;
} PER_IO_DATA, *LPPER_IO_DATA;

- WSARecv에 수신이 완료됐을 때 호출될 CompletionRoutine을 등록

{	
    int mode=1;
	hLisnSock=WSASocket(PF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
	ioctlsocket(hLisnSock, FIONBIO, &mode);   // 소켓을 non-blocking 모드로 변경
    
    while(1)
	{
		SleepEx(100, TRUE);    // 쓰레드를 주기적으로 alertable wait 상태로 변경
		hRecvSock=accept(hLisnSock, (SOCKADDR*)&recvAdr,&recvAdrSz);
		if(hRecvSock==INVALID_SOCKET)
		{
			if(WSAGetLastError()==WSAEWOULDBLOCK)
				continue;
			else
				printf("accept() error");
		}
        
		printf("Client connected.....");

		lpOvLp=(LPWSAOVERLAPPED)malloc(sizeof(WSAOVERLAPPED));
		memset(lpOvLp, 0, sizeof(WSAOVERLAPPED));

		hbInfo=(LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA));
		hbInfo->hClntSock=(DWORD)hRecvSock;
		(hbInfo->wsaBuf).buf=hbInfo->buf;
		(hbInfo->wsaBuf).len=BUF_SIZE;

		lpOvLp->hEvent=(HANDLE)hbInfo;
		WSARecv(hRecvSock, &(hbInfo->wsaBuf), 
			1, &recvBytes, &flagInfo, lpOvLp, ReadCompRoutine);
	}
}

- 수신용 Completion Routine에서는 데이터를 전송한 소켓에 대한 정보와 데이터를 확인해서 연결을 끊거나 다시 그 소켓에 WSASend

- WSASend에도 전송이 완료됐을 때 호출될 CompletionRoutine을 등록

void CALLBACK ReadCompRoutine(
	DWORD dwError, DWORD szRecvBytes, LPWSAOVERLAPPED lpOverlapped, DWORD flags)
{
	LPPER_IO_DATA hbInfo=(LPPER_IO_DATA)(lpOverlapped->hEvent);
    // 구조체 주소를 형변환하여 정보로 활용
	SOCKET hSock=hbInfo->hClntSock;
    // 수신 완료된 소켓
	LPWSABUF bufInfo=&(hbInfo->wsaBuf);
    // 수신받은 데이터 정보
	DWORD sentBytes;

	if(szRecvBytes==0)
	{
		closesocket(hSock);
		free(lpOverlapped->hEvent); free(lpOverlapped);
		printf("Client disconnected.....");
	} // EOF 전달 시 close
	else 
	{
		bufInfo->len=szRecvBytes;
		WSASend(hSock, bufInfo, 1, &sentBytes, 0, lpOverlapped, WriteCompRoutine);
	} // 수신 정보로 다시 Send
}

- 전송용 Completion Routine에서는 다시 WSARecv를 호출해서 수신을 대기

void CALLBACK WriteCompRoutine(
	DWORD dwError, DWORD szSendBytes, LPWSAOVERLAPPED lpOverlapped, DWORD flags)
{
	LPPER_IO_DATA hbInfo=(LPPER_IO_DATA)(lpOverlapped->hEvent);
	SOCKET hSock=hbInfo->hClntSock;
	LPWSABUF bufInfo=&(hbInfo->wsaBuf);
	DWORD recvBytes;
	int flagInfo=0;
	WSARecv(hSock, bufInfo, 1, &recvBytes, &flagInfo, lpOverlapped, ReadCompRoutine);
}

- 클라이언트에서는 send 후 recv 시 보낸 send 크기만큼 recv를 해줘야 함

{
	while(1) 
	{
		strLen=strlen(message);
		send(hSocket, message, strLen, 0);

		readLen=0;
		while(1)
		{
			readLen+=recv(hSocket, &message[readLen], BUF_SIZE-1, 0);
			if(readLen>=strLen)
				break;
		}
		message[strLen]=0;
		printf("Message from server: %s", message);
	}
}

 

IOCP 모델

- Overlapped IO 모델은 넌 블로킹 모드의 accpet 함수와 반복된 SleepEx 함수 호출은 성능에 영향을 미칠 수 있음

- 그 문제를 해결하기 위해 accept 호출을 main에서 하고 별도의 쓰레드로 클라이언트와 입출력을 수행

- 위와 같은 구조의 모델이 IOCP 모델

728x90

+ Recent posts