C++ 初識window下 IOCP網絡模式

window下 IOCP網絡模式

#define WIN32_LEAN_AND_MEAN

#include<windows.h>
#include<WinSock2.h>
#pragma comment(lib,"ws2_32.lib")

#include<mswsock.h>
#pragma comment(lib,"Mswsock.lib")

#include<stdio.h>

#define nClient 10
enum IO_TYPE
{
	ACCEPT = 10,
	RECV,
	SEND
};

//數據緩衝區空間大小
#define DATA_BUFF_SIZE 1024
struct IO_DATA_BASE
{
	//重疊體
	OVERLAPPED overlapped;
	//
	SOCKET sockfd;
	//數據緩衝區
	char buffer[DATA_BUFF_SIZE];
	//實際緩衝區數據長度
	int length;
	//操作類型
	IO_TYPE iotype;
};
//向IOCP投遞接受連接的任務
void postAccept(SOCKET sockServer, IO_DATA_BASE* pIO_DATA)
{
	pIO_DATA->iotype = IO_TYPE::ACCEPT;
	pIO_DATA->sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (FALSE == AcceptEx(sockServer
		, pIO_DATA->sockfd
		, pIO_DATA->buffer
		, 0
		, sizeof(sockaddr_in) + 16
		, sizeof(sockaddr_in) + 16
		, NULL
		, &pIO_DATA->overlapped
	))
	{
		int err = WSAGetLastError();
		if (ERROR_IO_PENDING != err)
		{
			printf("AcceptEx failed with error %d\n", err);
			return;
		}
	}
}
//向IOCP投遞接收數據的任務
void postRecv(IO_DATA_BASE* pIO_DATA)
{
	pIO_DATA->iotype = IO_TYPE::RECV;
	WSABUF wsBuff = {};
	wsBuff.buf = pIO_DATA->buffer;
	wsBuff.len = DATA_BUFF_SIZE;
	DWORD flags = 0;
	ZeroMemory(&pIO_DATA->overlapped, sizeof(OVERLAPPED));

	if (SOCKET_ERROR == WSARecv(pIO_DATA->sockfd, &wsBuff, 1, NULL, &flags, &pIO_DATA->overlapped, NULL))
	{
		int err = WSAGetLastError();
		if (ERROR_IO_PENDING != err)
		{
			printf("WSARecv failed with error %d\n", err);
			return;
		}
	}
}
//向IOCP投遞發送數據的任務
void postSend(IO_DATA_BASE* pIO_DATA)
{
	pIO_DATA->iotype = IO_TYPE::SEND;
	WSABUF wsBuff = {};
	wsBuff.buf = pIO_DATA->buffer;
	wsBuff.len = pIO_DATA->length;
	DWORD flags = 0;
	ZeroMemory(&pIO_DATA->overlapped, sizeof(OVERLAPPED));

	if (SOCKET_ERROR == WSASend(pIO_DATA->sockfd, &wsBuff, 1, NULL, flags, &pIO_DATA->overlapped, NULL))
	{
		int err = WSAGetLastError();
		if (ERROR_IO_PENDING != err)
		{
			printf("WSASend failed with error %d\n", err);
			return;
		}
	}
}
//-- 用Socket API建立簡易TCP服務端
//-- IOCP Server基礎流程
int main()
{
	//啓動Windows socket 2.x環境
	WORD ver = MAKEWORD(2, 2);
	WSADATA dat;
	WSAStartup(ver, &dat);
	//------------//
	// 1 建立一個socket
	// 當使用socket函數創建套接字時,會默認設置WSA_FLAG_OVERLAPPED標誌
	//////
	// 注意這裏也可以用 WSASocket函數創建socket
	// 最後一個參數需要設置爲重疊標誌(WSA_FLAG_OVERLAPPED)
	// SOCKET sockServer = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
	//////
	SOCKET sockServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	// 2.1 設置對外IP與端口信息 
	sockaddr_in _sin = {};
	_sin.sin_family = AF_INET;
	_sin.sin_port = htons(4567);//host to net unsigned short
	_sin.sin_addr.s_addr = INADDR_ANY;
	// 2.2 綁定sockaddr與ServerSocket
	if (SOCKET_ERROR == bind(sockServer, (sockaddr*)&_sin, sizeof(_sin)))
	{
		printf("錯誤,綁定網絡端口失敗...\n");
	}
	else {
		printf("綁定網絡端口成功...\n");
	}

	// 3 監聽ServerSocket
	if (SOCKET_ERROR == listen(sockServer, 64))
	{
		printf("錯誤,監聽網絡端口失敗...\n");
	}
	else {
		printf("監聽網絡端口成功...\n");
	}
	//-------IOCP Begin-------//

	//4 創建完成端口IOCP
	HANDLE _completionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
	if (NULL == _completionPort)
	{
		printf("CreateIoCompletionPort failed with error %d\n", GetLastError());
		return -1;
	}
	//5 關聯IOCP與ServerSocket
	//完成鍵
	auto ret = CreateIoCompletionPort((HANDLE)sockServer, _completionPort, (ULONG_PTR)sockServer, 0);
	if (!ret)
	{
		printf("關聯IOCP與ServerSocket失敗,錯誤碼=%d\n", GetLastError());
		return -1;
	}

	//6 向IOCP投遞接受連接的任務
	IO_DATA_BASE ioData[nClient] = {};
	for (int n = 0; n < nClient; n++)
	{
		postAccept(sockServer, &ioData[n]);
	}

	int msgCount = 0;
	while (true)
	{
		DWORD bytesTrans = 0;
		SOCKET sock = INVALID_SOCKET;
		IO_DATA_BASE* pIOData;
		//在網絡內存中查找可連接的的客戶端socket
		if (FALSE == GetQueuedCompletionStatus(_completionPort, &bytesTrans, (PULONG_PTR)&sock, (LPOVERLAPPED*)&pIOData, 1))
		{
			int err = GetLastError();
			if (WAIT_TIMEOUT == err)
			{
				continue;
			}
			if (ERROR_NETNAME_DELETED == err)
			{
				printf("關閉 sockfd=%d\n", pIOData->sockfd);
				closesocket(pIOData->sockfd);
				continue;
			}
			printf("GetQueuedCompletionStatus failed with error %d\n", err);
			break;
		}
		//7.1 接受鏈接 完成
		if (IO_TYPE::ACCEPT == pIOData->iotype)
		{
			printf("新客戶端加入 sockfd=%d\n", pIOData->sockfd);
			//7.2 關聯IOCP與ClientSocket
			auto ret = CreateIoCompletionPort((HANDLE)pIOData->sockfd, _completionPort, (ULONG_PTR)pIOData->sockfd, 0);
			if (!ret)
			{
				printf("關聯IOCP與ClientSocket=%d失敗\n", pIOData->sockfd);
				closesocket(pIOData->sockfd);
				continue;
			}
			//7.3 向IOCP投遞接收數據任務
			postRecv(pIOData);
		}
		//8.1 接收數據 完成 Completion
		else if (IO_TYPE::RECV == pIOData->iotype)
		{
			if (bytesTrans <= 0)
			{//客戶端斷開處理
				printf("關閉 sockfd=%d, RECV bytesTrans=%d\n", pIOData->sockfd, bytesTrans);
				closesocket(pIOData->sockfd);
				continue;
			}
			printf("收到數據: sockfd=%d, bytesTrans=%d msgCount=%d\n", pIOData->sockfd, bytesTrans, ++msgCount);
			pIOData->length = bytesTrans;
			//8.2 向IOCP投遞發送數據任務
			postSend(pIOData);
		}
		//9.1 發送數據 完成 Completion
		else if (IO_TYPE::SEND == pIOData->iotype)
		{
			if (bytesTrans <= 0)
			{//客戶端斷開處理
				printf("關閉 sockfd=%d, SEND bytesTrans=%d\n", pIOData->sockfd, bytesTrans);
				closesocket(pIOData->sockfd);
				continue;
			}
			printf("發送數據: sockfd=%d, bytesTrans=%d msgCount=%d\n", pIOData->sockfd, bytesTrans, msgCount);
			//9.2 向IOCP投遞接收數據任務
			postRecv(pIOData);
		}
		else {
			printf("未定義行爲 sockfd=%d", sock);
		}
	}

	//------------//
	//10.1 關閉ClientSocket
	for (int n = 0; n < nClient; n++)
	{
		closesocket(ioData[n].sockfd);
	}
	//10.2 關閉ServerSocket
	closesocket(sockServer);
	//10.3 關閉完成端口
	CloseHandle(_completionPort);
	//清除Windows socket環境
	WSACleanup();
	return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章