windows socket网络编程六:完成端口模型

分析

我们的重叠IO模型还有什么问题吗?
去掉我编写时候的逻辑bug,就网络模型来说主要问题还有:

  • 循环询问,延迟高,做了很多无用功
  • 采用多线程,数量可能太多,我们很难管理,切换线程消耗大量cpu资源/时间

那最优线程数量是多少?
网上的几种答案:

  • CPU核数
  • CPU核数*2
  • CPU核数*2+2

其中CPU核数*2+2 这个数量,是根据实际应用中的经验得来。线程函数中可能会调用Sleep(),WSAWaitForMultipleEvents()…这类函数,会使线程挂起(不占cpu时间片),从而使得CPU某个核空闲了,所以一般我们多建个两三根,以解决此类情况,让CPU不停歇,从而在整体上保证程序执行效率。

完成端口也是windows的一种机制,是在重叠IO模型基础上的优化:

  • 模仿消息队列,创造一个通知队列,系统创建(保证有序,不做无用功)
  • 创建最佳数量的线程(充分利用cpu的性能)

服务器

创建完成端口

HANDLE WINAPI CreateIoCompletionPort(
  _In_     HANDLE    FileHandle,
  _In_opt_ HANDLE    ExistingCompletionPort,
  _In_     ULONG_PTR CompletionKey,
  _In_     DWORD     NumberOfConcurrentThreads
);

功能:创建一个完成端口
参数:

  • FileHandle — INVALID_HANDLE_VALUE
  • ExistingCompletionPort — NULL
  • CompletionKey — 0
  • NumberOfConcurrentThreads — 允许此端口上最多同时运行的线程数量(填写0表示默认是cpu核数)

返回值:

  • 如果成功,返回一个新的完成端口。
  • 如果失败,返回NULL。

代码:

// 创建完成端口
hPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
if (NULL == hPort)
{
	printf("CreateIoCompletionPort 创建失败 error:%d\n", WSAGetLastError());
	closesocket(socketServer);
	WSACleanup();
	return -1;
}

绑定端口与SOCKET

HANDLE WINAPI CreateIoCompletionPort(
  _In_     HANDLE    FileHandle,
  _In_opt_ HANDLE    ExistingCompletionPort,
  _In_     ULONG_PTR CompletionKey,
  _In_     DWORD     NumberOfConcurrentThreads
);

功能:将完成端口与SOCKET绑定在一起
参数:

  • FileHandle — 服务器socket
  • ExistingCompletionPort — 完成端口变量
  • CompletionKey — 用户定义的完成密钥
  • NumberOfConcurrentThreads — 忽略此参数填0

返回值:

  • 如果成功,返回与socket绑定后的端口。
  • 如果失败,返回NULL。

代码:

// 绑定端口与SOCKET
HANDLE hPort1 = CreateIoCompletionPort((HANDLE)socketServer, hPort, 0, 0);
if (hPort1 != hPort)
{
	printf("CreateIoCompletionPort 绑定失败 error:%d\n", GetLastError());
	CloseHandle(hPort);
	closesocket(socketServer);
	WSACleanup();
	return -1;
}

创建线程

HANDLE CreateThread(
  LPSECURITY_ATTRIBUTES   lpThreadAttributes,
  SIZE_T                  dwStackSize,
  LPTHREAD_START_ROUTINE  lpStartAddress,
  __drv_aliasesMem LPVOID lpParameter,
  DWORD                   dwCreationFlags,
  LPDWORD                 lpThreadId
);

功能:创建一个线程
参数:

  • lpThreadAttributes — 确定线程句柄是否能被继承(默认NULL不继承)
  • dwStackSize — 线程栈大小,以字节为单位。(填0,系统使用默认大小1M)
  • lpStartAddress — 线程函数地址
  • lpParameter — 外部给线程传递的数据
  • dwCreationFlags — 控制线程创建的标志
    含义
    0线程在创建后立即运行
    CREATE_SUSPENDED该线程以挂起状态创建,并且直到调用ResumeThread函数后才运行 。
    STACK_SIZE_PARAM_IS_A_RESERVATION所述dwStackSize参数指定堆栈的初始保留大小。如果未指定此标志,则dwStackSize指定提交大小。
  • lpThreadId — 指向接收线程标识符的变量的指针。如果此参数为 NULL,则不返回线程标识符。

返回值:

  • 如果成功,返回新线程的句柄。
  • 如果失败,返回NULL。

代码:

// 获取要创建的线程数量
SYSTEM_INFO systemProcessorsCount;
GetSystemInfo(&systemProcessorsCount);
nProcessorsCount = systemProcessorsCount.dwNumberOfProcessors;

// 创建线程
pThread = (HANDLE*)malloc(sizeof(HANDLE)*nProcessorsCount);
for (int i = 0; i < nProcessorsCount; ++i)
{
	pThread[i] = CreateThread(NULL, 0, ThreadProc, hPort, 0, NULL);
	if (NULL == pThread[i])
	{
		printf("CreateThread 失败 error:%d\n", GetLastError());
		CloseHandle(hPort);
		closesocket(socketServer);
		WSACleanup();
		return -1;
	}
}

线程内部代码

BOOL GetQueuedCompletionStatus(
  HANDLE       CompletionPort,
  LPDWORD      lpNumberOfBytesTransferred,
  PULONG_PTR   lpCompletionKey,
  LPOVERLAPPED *lpOverlapped,
  DWORD        dwMilliseconds
);

功能:尝试从指定的I / O完成端口出列I / O完成数据包(无通知时,线程挂起,不占用CPU的时间)
参数:

  • CompletionPort — 完成端口
  • lpNumberOfBytesTransferred — 接收或者发送得字节数
  • lpCompletionKey — 接收与I / O操作已完成的文件句柄关联的完成键值
  • lpOverlapped — 重叠结构
  • dwMilliseconds — 等待时间,INFINITE为一直等

返回值:

  • 如果成功,返回TRUE。
  • 如果失败,返回FALSE。

代码:

DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
	HANDLE port = (HANDLE)lpParameter;
	DWORD NumberOfBytes;
	ULONG_PTR index;
	LPOVERLAPPED lpOverlapped;
	while (g_flag)
	{
		BOOL bFlag = GetQueuedCompletionStatus(port, &NumberOfBytes, &index, &lpOverlapped, INFINITE);
		if (FALSE == bFlag)
		{
			if (64 == GetLastError())
			{
				printf("客户端异常下线\n");
			}
			continue;
		}

		// 分类处理
		
		if (0 == index)	// accept
		{
			printf("accept\n");
			// 绑定到完成端口
			HANDLE hPort1 = CreateIoCompletionPort((HANDLE)g_allSock[g_count], hPort, g_count, 0);
			if (hPort1 != hPort)
			{
				printf("CreateIoCompletionPort 绑定失败 error:%d\n", GetLastError());
				closesocket(g_allSock[g_count]);
				continue;
			}
			PostRecv(g_count);	// 投递recv
			++g_count;			// 客户端数量++
			PostAccept();		// 投递accept
		}
		else
		{
			if (0 == NumberOfBytes)
			{
				printf("客户端正常下线\n");
				closesocket(g_allSock[index]);
				WSACloseEvent(g_allOlp[index].hEvent);
				// 从数组中删掉
				g_allSock[index] = 0;
				g_allOlp[index].hEvent = NULL;
			}
			else
			{
				if (0 != g_strRecv[index][0])	// recv
				{
					// 接收到客户端消息 
					printf("Client Data : %s \n", g_strRecv[index]);
					memset(g_strRecv[index], 0, sizeof(g_strRecv[index]));
					PostSend(index);	// 给客户回信
					PostRecv(index);	// 对自己投递接收
				}
			}
		}
	}
	
	return 0;
}

投递accept、send、recv

代码:

int PostAccept()
{
	g_allSock[g_count] = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
	g_allOlp[g_count].hEvent = WSACreateEvent();

	char str[1024] = { 0 };
	DWORD dwRecvcount;
	AcceptEx(g_allSock[0], g_allSock[g_count], str, 0, sizeof(struct sockaddr_in) + 16,
		sizeof(struct sockaddr_in) + 16, &dwRecvcount, &g_allOlp[0]);

	if (ERROR_IO_PENDING != WSAGetLastError())
	{
		return 1;	// 函数执行出错
	}
	return 0;
}

int PostRecv(int index)
{
	WSABUF wsabuf;
	wsabuf.buf = g_strRecv[index];
	wsabuf.len = MAX_RECV_COUNT;

	DWORD dwRecvCount;
	DWORD dwFlag = 0;
	WSARecv(g_allSock[index], &wsabuf, 1, &dwRecvCount, &dwFlag, &g_allOlp[index], NULL);

	if (ERROR_IO_PENDING != WSAGetLastError())
	{
		return 1;	// 函数执行出错
	}
	return 0;
}

int PostSend(int index)
{
	WSABUF wsabuf;
	wsabuf.buf = "ok";
	wsabuf.len = MAX_RECV_COUNT;

	DWORD dwSendCount;
	DWORD dwFlag = 0;
	WSASend(g_allSock[index], &wsabuf, 1, &dwSendCount, dwFlag, &g_allOlp[index], NULL);

	if (ERROR_IO_PENDING != WSAGetLastError())
	{
		return 1;	// 函数执行出错
	}
	return 0;
}

运行结果

在这里插入图片描述

模型流程图

在这里插入图片描述
也是解决了send、recv、accept的执行阻塞问题,是重叠io的优化。

源码链接

百度云链接:https://pan.baidu.com/s/1xBOiSADlAG2gO1TC6BBO_A
提取码:sxbd

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值