IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> UDP.07.完成端口模型 -> 正文阅读

[网络协议]UDP.07.完成端口模型

https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-socket
基于UDP的网络编程有5种模型:
SELECT模型
事件选择模型
异步选择模型
重叠IO模型
完成端口模型

这次讲最后一种:完成端口模型。
基本理论知识:核与线程的关系、线程数量的优化等看下TCP篇即可,这里不赘述

完成端口模型逻辑

1.将重叠套接字(UDP只有一个服务器SOCKET)与一个完成端口(完成端口是某一个类型的变量)绑定在一起;
2.使用WSARecvFrom、WSASendTo投递请求(和重叠IO模型一样的代码,但是立即完成部分可以不要,因为完成端口会处理请求);
3.当系统异步完成请求,就会把通知存进一个队列,我们就叫它通知队列,该队列由操作系统系统创建,维护;
4.完成端口可以理解为这个队列的头,可通过GetQueuedCompletionStatus从队列头往外取请求,一个一个处理。

完成端口模型代码

这里也是分步骤将代码按步骤分解。

1-5通用部分

1.加载网络头文件网络库
2.打开网络库
3.校验版本
4.创建SOCKET
5.绑定地址与端口

	WORD wVersionRequested = MAKEWORD(2, 2);//版本
	WSADATA wsaDATA;

	//2、打开网络库
	int iret = WSAStartup(wVersionRequested, &wsaDATA);
	if (iret != 0)
	{
		//有错
		switch (iret)
		{
		case WSASYSNOTREADY:
			printf("解决方案:重启。。。");
			break;
		case WSAVERNOTSUPPORTED:
			printf("解决方案:更新网络库");
			break;
		case WSAEINPROGRESS:
			printf("解决方案:重启。。。");
			break;
		case WSAEPROCLIM:
			printf("解决方案:网络连接达到上限或阻塞,关闭不必要软件");
			break;
		case WSAEFAULT:
			printf("解决方案:程序有误");
			break;
		}
		getchar();
		return 0;
	}
	printf("WSAStartup成功\n");

	//3、校验版本,只要有一个不是2,说明系统不支持我们要的2.2版本	
	if (2 != HIBYTE(wsaDATA.wVersion) || 2 != LOBYTE(wsaDATA.wVersion))
	{
		printf("版本有问题!");
		WSACleanup();//关闭网络库
		return 0;
	}
	printf("校验版本成功\n");
	// 4、创建SOCKET
	socketServer = WSASocket(AF_INET, SOCK_DGRAM, IPPROTO_UDP, NULL, 0, WSA_FLAG_OVERLAPPED);
	if (INVALID_SOCKET == socketServer)
	{
		//清理网络库,不关闭句柄
		WSACleanup();
		return 0;
	}
	printf("创建SOCKET成功\n");

	//5、绑定地址与端口
	struct sockaddr_in lsi;
	lsi.sin_family = AF_INET;//这里要和创建SOCKET句柄的参数1类型一样
	lsi.sin_port = htons(9527);//用htons宏将整型转为端口号的无符号整型
	lsi.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");

	if (bind(socketServer, (struct sockaddr*)&lsi, sizeof(lsi)) == SOCKET_ERROR)
	{
		int err = WSAGetLastError();//取错误码
		printf("服务器bind失败错误码为:%d\n", err);
		closesocket(socketServer);//释放
		WSACleanup();//清理网络库
	}
	printf("绑定SOCKET成功\n");

6.创建/绑定完成端口

完成这两个功能只用一个函数,但是传的参数不一样。
https://docs.microsoft.com/en-us/windows/win32/fileio/createiocompletionport

HANDLE WINAPI CreateIoCompletionPort(
  _In_     HANDLE    FileHandle,
  _In_opt_ HANDLE    ExistingCompletionPort,
  _In_     ULONG_PTR CompletionKey,
  _In_     DWORD     NumberOfConcurrentThreads
);
创建/绑定完成端口绑定完成端口
参数1INVALID_HANDLE_VALUE服务器SOCKET句柄,前面要加(HANDLE)进行类型转换
参数2NULL完成端口变量
参数30再次传递服务器SOCKET句柄,也可以传递一个下标(用句柄数组下标)做编号,便于客户端绑定指定完成端口(后面要用这个编号)但是对于UDP来说,没有必要用这个参数,因为只涉及到一个SOCKET句柄
参数4允许此端口上最多同时运行的线程数量,可以用GetSystemInfo获取CPU核数,也可以用0表示默认CPU的核数0
返回值成功:返回一个新的完成端口;失败:用GetLastError()获取错误码成功:返回一个与服务器SOCKET句柄绑定后的完成端口变量,实际上也就是原来的完成端口;失败:用GetLastError()获取错误码

6.1 创建完成端口
先定义完成端口句柄/内核对象的全局变量,因为要在点×关闭事件关闭该句柄

HANDLE hPort//全局完成端口句柄
//6.1创建完成端口
	hPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);

	if (hPort == 0)//出错
	{
		int err = WSAGetLastError();//取错误码
		printf("CreateIoCompletionPort失败错误码为:%d\n", err);
		closesocket(socketServer);//释放
		WSACleanup();//清理网络库
	}

CreateIoCompletionPort最后一个参数用0表示用CPU的核数来创建线程,也可以用函数来获取CPU核数。

6.2 绑定完成端口
将完成端口与服务器SOCKET句柄绑定

//6.2绑定完成端口	
	hPort = CreateIoCompletionPort((HANDLE)socketServer, hPort, socketServer, 0);

	if (hPort == 0)//出错
	{
		int err = WSAGetLastError();//取错误码
		printf("6.2绑定完成端口失败错误码为:%d\n", err);
		CloseHandle(hPort);
		closesocket(socketServer);//释放
		WSACleanup();//清理网络库
		return 0;
	}

7.创建线程

这里要用到CPU核数。
https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsysteminfo
信息是放在SYSTEM_INFO结构里面。

void GetSystemInfo(
  LPSYSTEM_INFO lpSystemInfo
);

具体代码:

//获取CPU核数
	SYSTEM_INFO systemProcessorsCount;
	GetSystemInfo(&systemProcessorsCount);
	int nProcessorsCount = systemProcessorsCount.dwNumberOfProcessors;

创建线程函数介绍看:https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createthread
例子看:https://docs.microsoft.com/en-us/windows/win32/procthread/creating-threads

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

具体参数看TCP部分,这里不复述。

//7.创建线程
	//获取CPU核数
	SYSTEM_INFO si;
	GetSystemInfo(&si);
	int nProcessorsCount = si.dwNumberOfProcessors;

	//按核数创建线程
	for (int i = 0; i < nProcessorsCount; i++)
	{
		DWORD dwThreadId;
		if (NULL == CreateThread(NULL, 0, ThreadProc, hPort, 0, &dwThreadId))
		{
			//创建线程失败
			int err = WSAGetLastError();//取错误码
			printf("7创建线程失败失败错误码为:%d\n", err);
			i--;//创建失败就不算,重新来过
		}
	}

8.阻塞主线程

在写线程函数之前要在主函数里面将主线程设置为阻塞,不然主函数不断的运行到后面就return了。
在创建好线程后,投递f1,然后加入以下代码:

//设置主线程进入阻塞,子线程进入工作状态	
	while(1)
	{
		//Sleep是线程挂起状态,不占用CPU时间片
		Sleep(1000);
	}

9.线程绑定函数/操作通知队列

/// <summary>
/// 9.线程绑定函数/操作通知队列,相当于回调函数
/// </summary>
/// <param name="lpParam">创建线程CreateThread函数中参数4传进来的参数</param>
/// <returns></returns>
DWORD __stdcall ThreadProc(LPVOID lpParam)
{
	while (1)
	{
		DWORD  NumberOfBytesTransferred;//接收或发送数据的长度
		ULONG_PTR CompletionKey;//创建完成端口CreateIoCompletionPort的参数3
		WSAOVERLAPPED* lpOverlapped;
		//9.1取队头信号
		if (FALSE == GetQueuedCompletionStatus(hPort, &NumberOfBytesTransferred, &CompletionKey, &lpOverlapped, INFINITE))
		{
			continue;//取队头信号失败,跳过,下次再取
		}

		//9.2信号置空
		WSAResetEvent(lpOverlapped->hEvent);

		//9.3成功取到信号,分类处理
		if (0 == recvBuff[0])
		{
			printf("成功取到信号,缓冲区无数据,SEND可执行\n");
		}
		else
		{
			printf("成功取到信号,缓冲区有数据,RECV可执行\n");
			printf("%s\n", recvBuff);
			PostSendTo(&gsi);//f2.根据需求对客户端套接字投递WSASend
			//memset(recvBuff,0,548);//mark法1:清空接收缓存,逐位设置为0
			recvBuff[0] = 0;//mark法2:为第一位设置特殊值
			PostRecvFrom();//f1.投递PostRecvFrom
		}
	}
	return 0;
}

其他

f1.投递PostRecvFrom
f2.根据需求对客户端套接字投递WSASend
这两个函数可以把立即执行去掉,因为在线程函数中会有相应的处理。

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2021-08-06 10:11:58  更:2021-08-06 10:12:31 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/27 13:11:25-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计