一、 异步IO
说到重叠模型首先还是提一下异步IO比较好,因为从本质上讲,重叠模型也是一种异步IO模型。
我们知道,相对于计算机执行的其他操作而言,设备IO(文件、管道、套接字等)是比较慢的。于是在多线程结构中就考虑到采用异步的方式进行设备读写操作,即我们告诉系统对设备的读写数据,而同时应用程序的其他代码继续执行,直到获取设备操作完毕的系统通知。
在进行异步IO时,我们先向系统发出IO请求,操作系统队列化各种IO请求,并在内部完成操作,当系统在处理IO请求时,我们的线程可以返回继续执行,当操作系统处理完IO请求之后,通知我们数据操作(发送、接收、出错)完毕。
Windows提供了四种异步IO技术,机制几乎时相同的,区别在于通知结果的方式不同:
1、 使一个设备内核对象变为有信号
Windows将设备句柄看作可同步的对象,即它可以处于有信号或处于无信号状态,当创建设备句柄、以异步的方式发送IO请求时,该句柄处于无信号状态,当异步IO完成之后,该句柄受信,通过WaitForSingleobject或WatiForMultipleObjects函数可以判断设备操作合适完成。该技术只能用于一个设备只发送一个IO请求,否则,若一个设备对应多个操作,当句柄受信时无法判断是该设备的那个操作完成。
2、 使一个事件内核对象变为有信号
针对每个I/O操作绑定一个内核事件对象,并将等待事件等待函数等待该事件的受信,当I/O操作完成后系统使得与该操作绑定的事件受信,从而判断那个操作完成。该技术解决了使一个设备内核对象变为有信号技术中一个设备只能对应一个操作的不足。
3、 警告I/O
在该技术中,当发出设备IO请求时,同时要求我们传递一个被称为完成例程的回调函数,当IO请求完成时调用该回调函数完成我们需要处理的工作。该技术允许单个设备同时进行多个I/O请求。
4、 完成端口
完成端口技术多用于处理大规模的请求,通过内在的进程池技术可以达到很高的性能,此时暂不做深入讨论,若预知后事如何,请自己看,或等下回完成端口部分分解。
二、 网络编程中的重叠IO模型理论
在编程中可以有两种方法管理I/O请求:
1、 事件对象通知(event object notification) —对应上面的2
2、 完成例程(completion routines)——对应上面的3
这里只讨论第一种情况,具体思路就是正对每个套接字的每个操作绑定一个事件,然后将所有的事件组成事件数据,运用事件等待函数等待事件的发生,并根据事件与操作的对应关系对与之对应的操作进行处理。
下面说一下实际编程中所用到的数据结构及函数。
1、WSAOVERLAPPED结构
这个结构自然是重叠模型里的核心,用于绑定套接字、操作以及操作对应的事件。
typedef struct _WSAOVERLAPPED {
DWORD Internal;
DWORD InternalHigh;
DWORD Offset; //文件操作中想要开始的低偏移量,网络中不用
DWORD OffsetHigh; //文件操作中想要开始的高偏移量,网络中不用
WSAEVENT hEvent; // 网络编程中唯一需要关注的参数,用来关联WSAEvent对象
} WSAOVERLAPPED, *LPWSAOVERLAPPED;
2. WSARecv系列函数
在重叠模型中,发送接收等函数均由WSASend、WSARecv、WSASendTo、WSARecvFrom等函数代替,通过该函数将I/O操作与 WSAOVERLAPPED结构绑定起来,也既是与一个特定的事件绑定在一起,当系统完成操作之后与该操作绑定的事件受信。
int WSARecv(
SOCKET s, // 当然是投递这个操作的套接字
LPWSABUF lpBuffers, // 接收缓冲区,是WSABUF结构构成的数组
DWORD dwBufferCount, // 数组中WSABUF结构的数量
LPDWORD lpNumberOfBytesRecvd, // 如果接收操作立即完成,返回函数调用所接收到的字节数
LPDWORD lpFlags, // 说来话长了,我们这里设置为0 即可
LPWSAOVERLAPPED lpOverlapped, // “绑定”的重叠结构
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine); // 完成例程中将会用到的参数,我们这里设置为 NULL
返回值:
WSA_IO_PENDING : 最常见的返回值,这是说明我们的WSARecv操作成功了,但是I/O操作还没有完成,所以我们就需要绑定一个事件来通知我们操作何时完成
3、WSAWaitForMultipleEvents函数
该函数类似于线程中常用的WaitForMultipleObjects函数,都是在等待事件的触发。我们将WSARecv等操作上绑定的LPWSAOVERLAPPED数据结构中的事件组称事件数据,在该函数上等待。
DWORD WSAWaitForMultipleEvents(
DWORD cEvents, // 等候事件的总数量
const WSAEVENT* lphEvents, // 事件数组的指针
BOOL fWaitAll, //当设置为 TRUE,事件数组中所有事件被传信的时候函数才会返回
// FALSE则任何一个事件被传信函数都要返回,此时设为FALSE
DWORD dwTimeout, // 超时时间,如果超时,函数会返回 WSA_WAIT_TIMEOUT
// 如果设置为0,函数会立即返回
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个事件,就要 创建额外的工作者线程,就需要通过线程池管理了。
4、WSAGetOverlappedResult函数
我们利用该函数来查询重叠操作的结果,定义如下:
BOOL WSAGetOverlappedResult(
SOCKET s, // SOCKET,需要操作的套接字
LPWSAOVERLAPPED lpOverlapped, //我们想要查询结果的那个重叠结构的指针
LPDWORD lpcbTransfer, // 本次重叠操作的实际接收(或发送)的字节数
BOOL fWait, // 设置为TRUE,除非重叠操作完成,否则函数不会返回
// 设置FALSE,而且操作仍处于挂起状态,那么函数就会返回FALSE
// 此处采用的是设备内核对象受信方式,等待套接字受信。
LPDWORD lpdwFlags); // 指向DWORD的指针,负责接收结果标志
注:如果WSAGetOverlappedResult完成以后,第三个参数返回是 0 ,则说明通信对方已经关闭连接,我们这边的SOCKET, Event之类的也就可以关闭了。