(三)Windows网络模型之重叠IO模型(事件通知和完成例程)

重叠IO模型


重叠IO模型基于Windows提供的一种异步读写文件机制,注意socket的本质就是一个文件描述符;所以异步读写机制也适用于读写其他文件、串口等。

所谓重叠IO模型中的重叠指的是该机制在使用时所需要的一个结构体:WSAOVERLAPPED,在使用重叠IO模型时我们主要用了该结构体中的第5个成员

struct _WSAOVERLAPPED {
  DWORD    Internal;
  DWORD    InternalHigh;
  DWORD    Offset;
  DWORD    OffsetHigh;
  WSAEVENT hEvent;  //前四个成员用不到,主要用的是hEvent成员,也即事件对象
};

如何理解异步读写文件呢?

①与异步读写相对的就是同步读写:例如recv()函数要把协议缓冲区中的1000个字节读到程序中的buffer[]中。这个过程需要花费一定的时间,而在这段时间里,程序会阻塞在recv()函数位置,等待到recv()将1000字节数据全部读完并返回,这就是所谓的同步读同步写也是同样的道理。

异步读写则是将读写任务投递给操作系统完成,等到操作系统完成后以某种方式通知我们,我们再回来处理。因此程序代码不必阻塞在读写操作上,也就提高了程序的执行效率。

上边所说的某种方式,在重叠IO网络模型中就是事件event,通知方式就是将事件设置为有信号


重叠IO模型到底解决了什么问题呢?

基本的C/S模型,到处都是阻塞的,accept()函数傻等阻塞、recv()函数傻等阻塞、recv()send()函数均存在的执行阻塞

select模型通过FD_SET这样的socket集合,主动查询哪些socket是有信号、有动作的,它解决了accept()recv()傻等阻塞的问题,但是select()函数本身的执行阻塞也是一个大问题,如果socket非常多,访问量非常高,select模型就非常糟糕了

事件选择模型是基于事件机制的select模型的升级版,其通过将事件对象eventsocket实际的动作绑定在一起,然后投递给操作系统。并由操作系统监测事件的发生,程序员定时去检查有信号的事件,然后分类处理即可。这就解决了select模型的执行阻塞问题

异步选择模型是基于消息机制的select模型的升级版,其通过将消息messagesocket实际的动作绑定在一起,基于win窗口的消息队列,由操作系统监测消息并将消息装进消息队列,程序员只需挨个取出消息message并分类处理即可,这同样也解决了select模型的执行阻塞问题


但是无论上述怎么优化,都存在一个非常关键的问题:
recv()send()函数的执行阻塞问题一直都存在。我们又该怎么解决呢?

此时设想一下我们是怎么解决掉select()函数的执行阻塞问题呢?

答案就是:操作系统托管;靠将任务丢给操作系统;最终还是操作系统承担了一切!专业一点就是异步解决阻塞

这就类似于: 下班回家后,将脏衣服丢进全自动洗衣机里,然后去快乐的打王者;等衣服洗好了,洗衣机发出提示音(嘀嘀嘀~或音乐),我们就可以选择立马放下手机或者打完这局游戏,然后把衣服取出来,这就是一个异步的、由洗衣机托管了洗衣任务的类比!

同样,那思考一下:recv()send()函数可不可以由操作系统托管呢?实现收发数据由操作系统完成,程序不至于阻塞呢?

答案就是: 可以!重叠IO模型解决的就是recv()send()、以及accept()的阻塞问题;直接交给操作系统托管。操作系统完成之后用OVERLAPPED结构实现结果反馈;然后程序员再分类处理即可。

该模型使用AcceptEx()WSARecv()WSASend()函数实现接收连接、收发数据的异步性


重叠IO模型的使用逻辑(事件通知反馈结果)

  1. 首先将OVERLAPPED结构体与socket绑定
  2. 调用AcceptEx()WSARecv()WSASend()将接收连接任务、收发数据任务等投递给操作系统完成
  3. 调用WSAWaitForMultipleEvents()查询是否有事件被置位有信号的
  4. 调用WSAGetOverlappedResult()查询信号结果,根据传出参数以及返回值确定事件类型,然后分类处理。

除了事件通知反馈结果,还可以使用 回调函数反馈结果,即调用WSARecv()WSASend()将接收连接任务、收发数据任务等投递给操作系统完成,完成后直接自动调用回调函数处理


AcceptEx()函数的原型以及执行逻辑:

// 投递给操作系统异步接收连接请求函数
BOOL AcceptEx(
  SOCKET       sListenSocket,			// 服务器socket
  SOCKET       sAcceptSocket,			// 客户端通信socket,传进来的是一个未绑定任何信息的socket,传出的是一个绑定了客户端信息的socket
  PVOID        lpOutputBuffer,			// 缓冲区指针,接收新连接上发来的第一组数据,不能填NULL
  DWORD        dwReceiveDataLength,		// 设置0可取消参数3的功能;设置成参数3的长度,可接受数据;并且接收完数据之后OVERLAPPED才会有信号
  DWORD        dwLocalAddressLength,	// sizeof(struct sockaddr_in) + 16
  DWORD        dwRemoteAddressLength,	// sizeof(struct sockaddr_in) + 16
  LPDWORD      lpdwBytesReceived,		// 同步接收到数据时,该参数会装着接收到数据的字节数,其他时候没有用
  LPOVERLAPPED lpOverlapped				// 参数1 socket对应的重叠结构的地址
);

AcceptEx()函数调用有两种情况:

  1. 立即完成,也就是立即接收到了新的连接:那么就给客户端socket投递WSARecv()以及WSASend()
  2. 延迟完成,也即异步完成(无法立即完成),那么循环去等待信号即可

WSARecv()WSASend()函数的原型与执行逻辑:

// 返回值:0 表示立即完成;  	SOCKET_ERROR 表示出错(注意客户端强制退出也属于错误)
int WSAAPI WSARecv(
  SOCKET			s,						// 客户端通信socket
  LPWSABUF			lpBuffers,				// 接收到的数据存储位置
  DWORD				dwBufferCount,			// 参数2 WSABUF的个数
  LPDWORD			lpNumberOfBytesRecvd,	// 接收成功时,这里存储着接收到的字节数
  LPDWORD			lpFlags,				// 指向用于修改WSARecv()函数行为的标志的指针 可以填0
  LPWSAOVERLAPPED   lpOverlapped,			// 参数1socket 对应的重叠结构
  LPWSAOVERLAPPED_COMPLETION_ROUTINE  		
  					lpCompletionRoutine		// 回调函数:用于完成例程模型;事件通知时填NULL
);

// 返回值:0 表示立即完成;	 SOCKET_ERROR 表示出错(注意客户端强制退出也属于错误)
int WSAAPI WSASend(
  SOCKET			s,						// 客户端通信socket
  LPWSABUF			lpBuffers,				// 发送数据缓冲区
  DWORD				dwBufferCount,			// 参数2的WSABUF的个数
  LPDWORD			lpNumberOfBytesSent,	// 发送成功时,这里存储着发送成功的字节数
  DWORD				dwFlags,				// 指向用于修改WSASend()函数行为的标志的指针 可以填0
  LPWSAOVERLAPPED	lpOverlapped,			// 参数1 socket对应的重叠结构
  LPWSAOVERLAPPED_COMPLETION_ROUTINE   		
    				lpCompletionRoutine		// 回调函数
);

WSARecv()函数调用有两种情况:

  1. 立即完成,返回值等于0;此时需要去根据实际业务处理接收到数据,然后再次投递WSARecv()函数
  2. 延迟完成,异步完成(无法立即完成),继续循环等待信号即可

WSASend()函数调用有两种情况:

  1. 立即完成,返回值等于0;此时需要再次投递WSASend()函数
  2. 延迟完成,异步完成(无法立即完成),继续循环等待信号即可

WSAWaitForMultipleEvents()函数的原型与使用逻辑:

DWORD WSAAPI WSAWaitForMultipleEvents(
  DWORD          cEvents,		//  参数1:事件个数
  const WSAEVENT *lphEvents,	//  参数2:事件列表(集合数组)
  BOOL           fWaitAll,		//	参数3:true 等待所有事件均产生信号才返回; false 任意一个事件产生信号就返回
  DWORD          dwTimeout,		//	参数4:超时时间
  BOOL           fAlertable		//	参数5:在重叠IO模型的完成例程模型中填true, 其他模型false
)
// 返回值:事件集合数组中有信号事件的下表值
// 如果参数3为true,表示所有事件均有信号
// 如果参数3为false,表示有信号的事件中下表索引最小的那个事件的索引的运算值,实际的索引 == 返回值 - WSA_WAIT_EVENT_0
// 返回值为WSA_WAIT_IO_COMPLETION,表示在完成重叠IO模型中的完成例程(回调函数时)
// 返回值为WSA_WAIT_TIMEOUT,超时了

使用逻辑:

  1. 通过调用这个函数,我们可以知道事件是否有信号;
  2. 重叠IO模型中使用这句代码WSAWaitForMultipleEvents(1, &(g_allOlp[i].hEvent), FALSE, 0, FALSE);每次查看一个事件。

WSAGetOverlappedResult()函数的原型与使用逻辑:

BOOL WSAAPI WSAGetOverlappedResult(
  SOCKET          s,				// 参数1[in]:socket
  LPWSAOVERLAPPED lpOverlapped,		// 参数2[in]:重叠结构
  LPDWORD         lpcbTransfer,		// 参数3[out]:发送或接收到的实际字节个数;0表示客户端正常下线
  //(在调用该函数时,必然是事件有信号,因此应该先判断是否是有连接事件ACCEPT;如果确定是客户端事件,那么基本就是3种情况:发送数据、接收数据、ClLOSE也即正常下线)
  BOOL            fWait,			// 参数4[in]:仅当重叠IO模型中选择事件通知时,才填true,其余填写false
  LPDWORD         lpdwFlags			// 参数5[out]:装WSARecv的参数5 lpflags
);
// 返回值:true 表示执行成功  false 表示执行失败

通常可以根据参数3、socket类型以及接收缓冲区等,区分事件类型并分类处理。


上述所讲基于事件通知,利用 WSAWaitForMultipleEvents()函数WSAGetOverlappedResult()函数等待事件并解析,然后分类处理的情况是属于重叠IO的事件通知机制,此外还存在一种重叠IO的更高效的处理方式----完成例程


重叠IO模型之完成例程

所谓的完成例程,其实就是利用回调函数完成数据收发后的工作

WSARecv()WSASend()函数的最后一个参数,回调函数必须按照一定的规则声明并编写实现:

void CALLBACK funcName(				// CALLBACK 调用约定  funcName可以随意书写
    DWORD dwError, 					// 错误码,也即在调用WSARecv()与WSASend()函数时出现错误了,可以使用该参数判断客户端强制退出
    DWORD cbTransferred, 			// 发送或接收到的字节数
    LPWSAOVERLAPPED lpOverlapped, 	// 重叠结构
    DWORD dwFlags					// WSARecv()与WSASend()这个函数的执行方式,也即其参数5
);

AcceptEx()没有回调函数的参数,因此在投递服务器socket和重叠结构之后,还需要使用WSAWaitForMultipleEvents()函数去等待服务器socket上发生的事件,也即有客户端请求连接。


如何回答:讲讲你所理解的重叠IO网络模型?

重叠IO网络模型基于异步文件读写机制,socket本质也属于一种特殊文件。他解决的是例如recv函数,send()函数的执行阻塞问题

将socket与重叠结构绑定,并通过异步函数WSARecv()WSASend()AcceptEx()等函数将接收连接、收发数据等任务投递给操作系统,操作系统会帮我们完成相应的任务,之后再通过将重叠结构OVERLAPPED中的事件置成有信号的方式通知应用程序

  1. 对于事件通知模型,程序员只需要不断查看事件是否被置为有信号,然后查询信号结果之后分类处理即可,本质上是应用程序与操作系统的异步,是读写操作的异步。
  2. 而对于完成例程,程序员只需要查看服务器socket的事件是否为有信号,若有信号则表示与新客户端建立了连接,之后为新客户端socket投递WSARecv()WSASend();为WSARecv()WSASend()函数编写回调函数,用于告诉操作系统在收发完成数据之后要做什么工作。

实际上,事件通知时程序员要做的事情更多,且效率要低于完成例程。


异步选择模型与重叠IO模型的流程图

在这里插入图片描述


推荐阅读:(四)Windows网络模型之完成端口模型详解

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咖啡与乌龙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值