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.3.事件选择模型 -> 正文阅读

[网络协议]UDP.3.事件选择模型


https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-socket
基于UDP的网络编程还有5种模型:
SELECT模型
事件选择模型
异步选择模型
重叠IO模型
完成端口模型
这节讲基于UDP的事件选择模型。
关于基础知识,前面有写过,这里贴过来:

基础知识

windows处理用户行为有两种方式:

消息机制

其核心是消息队列,就是将要处理的操作放到队列(FIFO)中进行处理。
其特点是消息队列由操作系统维护,处理过程遵循队列特点,处理过程中,操作可以同时进行入队。
消息机制的处理是有序的(按队列的顺序),全面的(无论事件是否注册,都会进入到队列中,我们可以选择我们想要处理的事件来进行处理)
基于这个消息机制的异步选择模型下一篇讲。

事件机制

其核心是事件集合,同上面一样也是操作,但是这里没有先后顺序,是一个集合,处理的顺序由程序员决定。
根据需求,我们为用户的特定操作绑定一个事件,事件由我们自己调用API创建,需要多少创建多少。
当有对应的操作发生,例如单击鼠标左键,那么事件就会出发信号,程序员可以获取到这个信号,然后对信号进行处理。
其特点是所有事件都是自定义的,系统只管检测是否有信号。由于事件集合的无序性,当事件定义过多,会挤兑一些事件的执行效率(有人插队,轮不到)。
事件机制是无序的(集合的特点),不全面的(只有绑定的事件才会获取信号,没有绑定的事件就忽略)
本节来学习事件选择模型。

事件选择模型步骤

第一步:使用WSACreateEvent创建一个事件对象(变量)
第二步:使用WSAEventSelect为每一个事件对象绑定个SOCKET句柄,以及操作recvfrom,sendto等,并投递给系统(两个事情:绑定,投递)
第三步:循环使用WSAWaitForMultipleEvents查看事件是否有信号(这里要注意,该函数在等待过程中线程是处于挂起状态,不占用CPU时间片,这也是和SELECT模型的根本区别)
第四步:有信号的话就使用WSAEnumNetworkEvents分类处理

事件选择模型逻辑

同样的,这个模型的逻辑和基本模型是一样的

  1. 打开网络库

  2. 校验版本

  3. 创建SOCKET

  4. 绑定地址与端口

5. 开始监听 这个UDP没有

5、事件选择

6、有序处理

7、增加事件数量

8、释放

事件选择

创建事件对象

https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsacreateevent

WSAEVENT WSAAPI WSACreateEvent();

成功返回事件对象句柄
不成功则返回WSA_INVALID_EVENT

由于事件对象是一个句柄(内核对象),因此有相关的配套操作函数:
销毁事件对象句柄:

BOOL WSAAPI WSACloseEvent(
  WSAEVENT hEvent
);

重置事件对象句柄,将本来产生信号的事件重置为无信号状态:

BOOL WSAAPI WSAResetEvent(
  WSAEVENT hEvent
);

同样的有:

BOOL WSAAPI WSASetEvent(
  WSAEVENT hEvent
);

这个是将本来无信号的事件重置为有信号状态(但不能指定具体信号状态)。

具体代码:

//6.1创建事件对象
	WSAEVENT wse = WSACreateEvent();
	if(WSA_INVALID_EVENT == wse)
	{
		int err = WSAGetLastError();//取错误码
		printf("创建事件对象失败错误码为:%d\n",err);
		closesocket(socketServer);//与4、创建SOCKET对应,如果有创建客户端SOCKET句柄,也要关闭
		//清理网络库,不关闭句柄
		WSACleanup();
		return 0;
	}

绑定并投递

https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsaeventselect

int WSAAPI WSAEventSelect(
  SOCKET   s,
  WSAEVENT hEventObject,
  long     lNetworkEvents
);

给事件绑上SOCKET句柄与操作码,并投递给操作系统。
参数1:要绑定的SOCKET句柄
参数2:事件对象
参数3:具体要绑定的操作事件,根据MSDN,常见的事件有(删除线代表TCP才有):

操作码(信号)发生原因绑定操作
FD_READ有客户端消息绑定客户端SOCKET句柄
FD_WRITE可以可客户端发送消息绑定客户端SOCKET句柄,FD_ACCEPT成功后会自动产生这个信号
FD_OOB有带外数据一般不使用
FD_ACCEPT有客户端连接请求绑定客户端SOCKET句柄
FD_CONNECT在客户端编写,绑定服务器端SOCKET句柄
FD_CLOSE客户端下线(正常、强制均可)绑定客户端SOCKET句柄
FD_QOS套接字服务质量状态发生变化网络发生拥堵时发生该事件,获取服务质量状态可用WSAloctl
FD_GROUP_QOS保留操作码
FD_ROUTING_ INTERFACE_CHANGE路由接口改变(动态路由?)重叠I/O模型专用,要先WSAloctl注册才能生效
FD_ADDRESS_ LIST_CHANGE地址列表改变同上
0取消操作码绑定

当多个事件码同时绑定可以用【|】来连接多个事件码。
返回值:
成功:0
失败:SOCKET_ERROR

等待事件信号

https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsawaitformultipleevents

DWORD WSAAPI WSAWaitForMultipleEvents(
  DWORD          cEvents,
  const WSAEVENT *lphEvents,
  BOOL           fWaitAll,
  DWORD          dwTimeout,
  BOOL           fAlertable
);

参数1:通过转定义可以看到:

typedef unsigned long       DWORD;

DWORD是无符号长整型,代表当前绑定事件数量(最大是64,这里指已经绑定的数量,不是最大数量)
对于UDP而言,就一个事件,就填1。
参数2:多个事件对象数组的指针入口;
参数3:TRUE代表要等多个事件对象都产生信号后才返回,然后将事件对象数组按数组索引依次进行处理,这种方式不常用,会产生由于等待造成较大的延时;
FALSE代表只要多个事件对象中有一个产生信号后才返回,返回后用返回值减去宏WSA_WAIT_EVENT_0得到事件对象数组中有信号的事件对象的数组索引(下标),由于事件数组和SOCKET数组下标是一一对应关系(下面有讲),这个时候也获得了SOCKET数组下标。
需要注意的是,如果同时有多个事件对象产生信号,那么这个时候经过宏运算后得到是事件数组中下标最小的那个。
对于UDP而言,只有一个事件,所以这里用TRUE和FALSE没有什么区别。
参数4:等待时长,当查询完毕后,系统等待的时间长度,单位是毫秒。如果在等待过程中有事件信号产生则立刻返回。当超过设置的等待时长则返回WSA_WAIT_TIMEOUT,此时应该继续循环(continue;),相当于每次查询后会停顿一下,再根据if对WSA_WAIT_TIMEOUT的判断进行相应的处理;
当等待时长设置为0时,表示程序查询完时间状态后不等待,直接返回,进行下一轮查询;
当等待时长设置为WSA_INFINITE时,表示查询查询完会一直等待,直到有事件信号产生才返回,反正没信号也没事干,等着也行。
对于UDP而言,如果这里设置为WSA_INFINITE,那么和参数3设置为TRUE的效果是一样的。
参数5:TRUE,在重叠I/O模型中使用;
FALSE,在事件选择模型中使用。

返回值:
有信号的数组下标:这里分两种情况:参数3如果是TRUE,那么是整个数组,如果参数3是FALSE那么只返回一个值;
当参数5为TRUE的时候,返回值为:WSA_WAIT_IO_COMPLETION;
当参数4设置了等待时长,超过这个设置的时长没有信号就会返回WSA_WAIT_TIMEOUT,接continue即可。
失败:WSA_WAIT_FAILED
对于UDP而言,这里只有一个事件,不需要计算有信号的数组下标。

//6.3等待事件信号
		DWORD dwRes = WSAWaitForMultipleEvents(1,&wse,FALSE,WSA_INFINITE,FALSE);
		if(dwRes == WSA_WAIT_FAILED)//6.3等待事件信号失败
		{
			int err = WSAGetLastError();//取错误码
			printf("获取事件失败错误码为:%d\n",err);
			break;
		}

列举事件

https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsaenumnetworkevents

int WSAAPI WSAEnumNetworkEvents(
  SOCKET             s,
  WSAEVENT           hEventObject,
  LPWSANETWORKEVENTS lpNetworkEvents
);

参数1:SOCKET句柄
参数2:事件句柄
参数3:通过这个结构体指针(lp开头)将事件类型返回回来(传址调用),定义代码如下:

typedef struct _WSANETWORKEVENTS {
  long lNetworkEvents;
  int  iErrorCode[FD_MAX_EVENTS];
} WSANETWORKEVENTS, *LPWSANETWORKEVENTS;

一个事件对应多个操作码,那么参数1:lNetworkEvents也就包含多个操作码,但是会按位排列。
参数2:iErrorCode是一个错误码数组。当有多个操作码则要按操作码FD_XXX_BIT对应具体数组的下标,对应关系:

/*
 * WinSock 2 extension -- bit values and indices for FD_XXX network events
 */
#define FD_READ_BIT      0
#define FD_READ          (1 << FD_READ_BIT)

#define FD_WRITE_BIT     1
#define FD_WRITE         (1 << FD_WRITE_BIT)

#define FD_OOB_BIT       2
#define FD_OOB           (1 << FD_OOB_BIT)

#define FD_ACCEPT_BIT    3
#define FD_ACCEPT        (1 << FD_ACCEPT_BIT)

#define FD_CONNECT_BIT   4
#define FD_CONNECT       (1 << FD_CONNECT_BIT)

#define FD_CLOSE_BIT     5
#define FD_CLOSE         (1 << FD_CLOSE_BIT)

#define FD_QOS_BIT       6
#define FD_QOS           (1 << FD_QOS_BIT)

#define FD_GROUP_QOS_BIT 7
#define FD_GROUP_QOS     (1 << FD_GROUP_QOS_BIT)

如果某个操作码没有错误,那么它对应的数组下标里面的存储数值为0,例如FD_READ没有问题,那么在数组中第0位是0;FD_ACCEPT没有问题,那么在数组中第3位是0。

返回值:
成功:0
失败:SOCKET_ERROR

具体代码如下:

//6.3等待事件信号成功进入6.4列举事件
		WSANETWORKEVENTS wsne;
		dwRes = WSAEnumNetworkEvents(socketServer,wseSever,&wsne);
		if(dwRes == SOCKET_ERROR)//6.3等待事件信号失败
		{
			int err = WSAGetLastError();//取错误码
			printf("6.4列举事件失败错误码为:%d\n",err);
			continue;
		}

事件分类处理

事件分类处理逻辑

要用并列的if来进行逐个事件的判断,否则会有bug。
先用按位与来与某个事件进行判断,如果是该事件,那么判断是否有错误码,如果没有,就进行相应操作,以ACCEPT事件为例,固定写法大概如下所示:

if(pNetworkEvents->INetworkEvents&FD_ACCEPT)//先判断是不是这个事件
{
	if(lpNetworkEvents->iErrorCode[FD_ACCEPT_BIT]==0)//判断是不是有错误码
	{
		//接受链接
		//创建事件
		//投放事件
		//元素增加
	}
}

FD_READ

//6.5事件分类处理之FD_READ
		if(wsne.lNetworkEvents & FD_READ)//判断是否是READ事件
		{
			if(wsne.iErrorCode[FD_READ_BIT]==0)//没有错误
			{
				//把基本模型里面的recvfrom代码弄过来
				//收
				struct sockaddr sa;
				int iSaLen = sizeof(sa);
				char strRecvBuff[548]={0};

				if(recvfrom(socketServer,strRecvBuff,548,0,&sa,&iSaLen)==SOCKET_ERROR)
				{
					int err = WSAGetLastError();//取错误码
					printf("服务器recvfrom失败错误码为:%d\n",err);
					continue;
				}
				printf("服务器recvfrom消息是:%s\n",strRecvBuff);

				//发
				if(sendto(socketServer,"This is a message from server~!",sizeof("This is a message from server~!"),0,&sa,sizeof(sa))==SOCKET_ERROR)
				{
					int err = WSAGetLastError();//取错误码
					printf("服务器sendto失败错误码为:%d\n",err);
					continue;
				}

			}
		}

FD_WRITE

这个事件在TCP协议下:只会产生一次,且是自动产生,且在客户端被ACCEPT后,产生RECV后产生。
但是UDP协议下,客户端没有连接操作,也就是没有ACCEPT,因此FD_WRITE事件在服务器端运行后自动产生,也只会产生一次。

//6.5事件分类处理之FD_WRITE
		if(wsne.lNetworkEvents & FD_WRITE)//判断是否是FD_WRITE事件
		{
			if(wsne.iErrorCode[FD_WRITE_BIT]==0)//没有错误
			{
				printf("FD_WRITE事件执行完成!\n");
			}
		}

有序处理

TCP中由于socket很多,我们要进行相关的有序处理;
UDP就一个socket,就不用考虑顺序问题了。

增加事件数量

TCP中由于socket很多,每个socket要绑定一个事件,所以我们要多事件数量很多的情况进行逻辑处理
UDP就一个,不需要增加事件数量

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2021-08-01 14:49:27  更:2021-08-01 14:50:06 
 
开发: 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年5日历 -2024/5/3 8:22:27-

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