重叠模型的基本原理
简单来说,重叠模型就是让应用程序使用重叠数据结构WSAOVERLAPPED,次投递一个或多个Winsock I/O请求。针对这些提交的请求,在它们完成之后,应用程序会收到通知,于是就可以通过自己另外的代码来处理这些数据了。
需要注意的是,有两个方法可以用来管理重叠IO请求的完成情况(就是说接到重叠操作完成的通知):
1. 事件对象通知(event object notification)
2. 完成例程(completion routines) ,注意,这里并不是完成端口
本篇文章主要讲一下:基于事件通知模型的重叠模型。
完成例程可以看下一章:
C++ Windows Socket五种I/O模型之重叠IO模型(二)
WSAOVERLAPPED结构
typedef struct _WSAOVERLAPPED {
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
WSAEVENT hEvent;// 唯一需要关注的参数,用来关联WSAEvent对象
} WSAOVERLAPPED, *LPWSAOVERLAPPED;
参数:
- Internal: 预留给操作系统使用。它指定一个独立于系统的状态,当GetOverlappedResult函数返回时没有设置扩展错误信息ERROR_IO_PENDING时有效。
- InternalHigh: 预留给操作系统使用。它指定长度的数据转移,当GetOverlappedResult函数返回TRUE时有效。
- Offset: 该文件的位置是从文件起始处的字节偏移量。调用进程设置这个成员之前调用ReadFile或WriteFile函数。当读取或写入命名管道和通信设备时这个成员被忽略设为零。
- OffsetHigh: 指定文件传送的 字节 偏移量的高位字。当读取或写入命名管道和通信设备时这个成员被忽略设为零。
- hEvent: 在转移完成时处理一个事件设置为有信号状态。调用进程集这个成员在调用ReadFile、 WriteFile、TransactNamedPipe、 ConnectNamedPipe函数之前。
WSARecv
在重叠模型中,接收数据就要靠它了,它的参数也比recv要多,因为要用到重叠结构嘛,它是这样定义的:
int WSARecv(
SOCKET s,// 当然是投递这个操作的套接字
LPWSABUF lpBuffers, //接收缓冲区,与Recv函数不同,需要一个由WSABUF结构构成的数组
DWORD dwBufferCount, // 数组中WSABUF结构的数量
LPDWORD lpNumberOfBytesRecvd,// 如果接收操作立即完成,这里会返回函数调用接收到的字节数
LPDWORD lpFlags, // 具体内容比较多,具体可以看MSDN,可以对接收数据进行筛选,我们这里设置为0即可
LPWSAOVERLAPPED lpOverlapped,// “绑定”的重叠结构
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine // 完成例程中将会用到的参数,基于事件通知就设置为 NULL
);
返回值:
如果事件成功并且立即完成的情况下,返回 0,否则就返回SOCKET_ERROR ,可以调用WSAGetLastError 函数来获取错误码,如果错误码为:WSA_IO_PENDING 表示重叠操作已经成功,稍后将完成重叠操作,其他情况下标明重叠操作不成功。
WSAWaitForMultipleEvents
等待事件触发函数,函数原型如下
DWORD WSAWaitForMultipleEvents(
DWORD cEvents,//等候事件的总数量
const WSAEVENT* lphEvents,//事件数组的指针
BOOL fWaitAll,//设置为 TRUE,则事件数组中所有事件被传信的时候函数才会返回,FALSE则任何一个事件被传信函数都要返回,我们这里设置为FALSE
DWORD dwTimeout,//超时时间,如果超时,函数会返回 WSA_WAIT_TIMEOUT,如果设置为0,函数会立即返回
// 如果设置为WSA_INFINITE只有在某一个事件被传信后才会返回,否则一直处于阻塞状态
BOOL fAlertable //在完成例程中会用到这个参数,这里我们先设置为FALSE
);
返回值:
WSA_WAIT_TIMEOUT :最常见的返回值,我们需要做的就是继续Wait
WSA_WAIT_FAILED : 出现了错误,请检查cEvents和lphEvents两个参数是否有效
如果事件数组中有某一个事件被传信了,函数会返回这个事件的索引值,但是这个索引值需要减去预定义值 WSA_WAIT_EVENT_0才是这个事件在事件数组中的位置。
注意:
- WSAWaitForMultipleEvents函数只能支持由WSA_MAXIMUM_WAIT_EVENTS对象定义的一个最大值,是 64,就是说WSAWaitForMultipleEvents只能等待64个事件,如果想同时等待多于64个事件,就要 创建额外的工作者线程,就不得不去管理一个线程池,这一点就不如下一篇要讲到的完成例程模型了。
- dwTimeout 参数在本demo 中不能设置为 WSA_INFINITE,因为如果当一个套接字被投递出去,线程一直在等待回信阻塞状态,当另外一个客户端连接进来,由于线程处于阻塞状态,将出现无法响应的情况出现(这个问题查阅了好多资料才发现问题所在)
WSAGetOverlappedResult
BOOL WSAGetOverlappedResult(
SOCKET s,//套接字句柄
LPWSAOVERLAPPED lpOverlapped,//这里是我们想要查询结果的那个重叠结构的指针
LPDWORD lpcbTransfer,//本次重叠操作的实际接收(或发送)的字节数
BOOL fWait,//设置为TRUE,除非重叠操作完成,否则函数不会返回
//设置FALSE,而且操作仍处于挂起状态,那么函数就会返回FALSE
//错误为WSA_IO_INCOMPLETE
//不过因为我们是等待事件传信来通知我们操作完成,所以我们这里设置成什么都没有作用…
LPDWORD lpdwFlags //指向DWORD的指针,负责接收结果标志
);
如果第三个参数 返回值 lpcbTransfer=0,标明连接已经中断,需要一些数组的清理,下面示例会具体的讲解。
简单的介绍了一下用到的知识点,下面是重叠io的服务端代码:
编译环境 :vs2019