一. 重叠I/O的概念及使用
当调用ReadFile和WriteFile时,如果最后一个参数lpOverlapped设置为NULL,那么线程就阻塞在这里,直到读写完指定的数据后,它们才返回。这样在读写大文件的时候,很多时间都浪费在等待ReadFile和WriteFile的返回上面。如果ReadFile和WriteFile是往管道里读写数据,那么有可能阻塞得更久,导致程序性能下降。
为了解决这个问题,windows引进了重叠I/O的概念,它能够同时以多个线程处理多个I/O。其实你自己开多个线程也可以处理多个I/O,但是系统内部对I/O的处理在性能上有很大的优化。它是Windows下实现异步I/O最常用的方式。
Windows为几乎全部类型的文件提供这个工具:磁盘文件、通信端口、命名管道和套接字。通常,使用ReadFile和WriteFile就可以很好地执行重叠I/O。
重叠模型的核心是一个重叠数据结构。若想以重叠方式使用文件,必须用 FILE_FLAG_OVERLAPPED 标志打开它,例如:
HANDLE hFile = CreateFile(lpFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
如果没有规定该标志,则针对这个文件(句柄),重叠I/O是不可用的。如果设置了该标志,当调用ReadFile和WriteFile操作这个文件(句柄)时,必须为最后一个参数提供OVERLAPPED结构:
// WINBASE.H
typedef struct _OVERLAPPED{
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
HANDLE hEvent; //关键的一个参数
}OVERLAPPED, *LPOVERLAPPED;
头两个32位的结构字Internal和InternalHigh由系统内部使用;其次两个32位结构字Offset和OffsetHigh使得可以设置 64位的偏移量,该偏移量是要文件中读或写的地方。
因为I/O异步发生,就不能确定操作是否按顺序完成。因此,这里没有当前位置的概念。对于文件的操作,总是规定该偏移量。在数据流下(如COM端口或socket),没有寻找精确偏移量的方法,所以在这些情况中,系统忽略偏移量。这四个字段不应由应用程序直接进行处理或使用,OVERLAPPED结构的最后一个参数是可选的事件句柄,当I/O完成时,该事件对象受信(signaled)。程序通过等待该对事件对象受信来做善后处理。
设置了OVERLAPPED参数后,ReadFile/WriteFile的调用会立即返回,这时候你可以去做其他的事(所谓异步),系统会自动替你完成ReadFile/WriteFile相关的I/O操作。你也可以同时发出几个ReadFile/WriteFile的调用(所谓重叠)。当系统完成I/O操作时,会将OVERLAPPED.hEvent置信,我们可以通过调用WaitForSingleObject/WaitForMultipleObjects来等待这个I/O完成通知,在得到通知信号后,就可以调用GetOverlappedResult来查询I/O操作的结果,并进行相关处理。由此可以看出,OVERLAPPED结构在一个重叠I/O请求的初始化及其后续的完成之间,提供了一种沟通或通信机制。
以Win32重叠I/O机制为基础,自Winsock 2发布开始,重叠I/O便已集成到新的Winsock函数中,比如WSARecv/WSASend。这样一来,重叠I/O模型便能适用于安装了Winsock 2的所有Windows平台。可以一次投递一个或多个Winsock I/O请求。针对那些提交的请求,在它们完成之后,应用程序可为它们提供服务(对I/O的数据进行处理)。
相应的,要想在一个套接字上使用重叠I/O模型来处理网络数据通信,首先必须使用 WSA_FLAG_OVERLAPPED这个标志来创建一个套接字。如下所示:
SOCKET s = WSASocket(AF_INET, SOCK_STEAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
创建套接字的时候,假如使用的是socket函数,而非WSASocket函数,那么会默认设置WSA_FLAG_OVERLAPPED标志。成功创建好了一个套接字,将其与一个本地接口绑定到一起后,便可开始进行这个套接字上的重叠I/O操作,方法是调用下述的Winsock 2函数,同时为它们指定一个WSAOVERLAPPED结构参数(#define WSAOVERLAPPED OVERLAPPED// WINSOCK2.H):
n WSASend
n WSASendTo
n WSARecv
n WSARecvFrom
n WSAIoctl
n AcceptEx
n TransmitFile
若随一个WSAOVERLAPPED结构一起调用这些函数,函数会立即返回,无论套接字是否设为锁定模式。它们依赖于WSAOVERLAPPED结构来返回一个I/O请求操作的结果。
比起阻塞、select、WSAAsyncSelect以及WSAEventSelect等模型,Winsock的重叠I/O(Overlapped I/O)模型使应用程序能达到更佳的系统性能。因为它和这4种模型不同的是,使用重叠模型的应用程序通知缓冲区收发系统直接使用数据。也就是说,如果应用程序投递了一个10KB大小的缓冲区来接收数据,且数据已经到达套接字,则该数据将直接被拷贝到投递的缓冲区。而这4种模型中,数据到达并拷贝到单套接字接收缓冲区中,此时应用程序会被系统通知可以读入的字节数。当应用程序调用接收函数之后,数据才从单套接字缓冲区拷贝到应用程序的缓冲区。这样就减少了一次从I/O缓冲区到应用程序缓冲区的拷贝,差别就在于此。
在Windows NT和Windows 2000中,重叠I/O模型也允许应用程序以一种重叠方式实现对套接字连接的处理。具体的做法是在监听套接字上调用AcceptEx函数。AcceptEx是一个特殊的Winsock 1.1扩展函数,位于Mswsock.h头文件以及Mswsock.lib库文件内。该函数最初的设计宗旨是在Windows NT与Windows 2000操作系统上使用Win 32的重叠I/O机制。但事实上,它也适用于Winsock 2中的重叠I/O。AcceptEx的定义如下:
// MSWSOCK.H
AcceptEx(
IN SOCKET sListenSocket,
IN SOCKET sAcceptSocket,
IN PVOID lpOutputBuffer,
IN DWORD dwReceiveDataLength,
IN DWORD dwLocalAddressLength,
IN DWORD dwRemoteAddressLength,
OUT LPDWORD lpdwBytesReceived,
IN LPOVERLAPPED lpOverlapped);
参数一sListenSocket参数指定的是一个监听套接字。
参数二sAcceptSocket参数指定的是另一个套接字,负责对进入连接请求的“接受”。AcceptEx函数和accept函数的区别在于,我们必须提供接受的套接字,而不是让函数自动为我们创建。正是由于要提供套接字,所以要求我们事先调用socket或WSASocket函数,创建一个套接字,以便通过sAcceptSocket参数,将其传递给AcceptEx。
参数三lpOutputBuffer指定的是一个特殊的缓冲区,因为它要负责三种数据的接收:服务器的本地地址,客户机的远程地址,以及在新建连接上接收的第一个数据块。存储顺序是:接收到的数据块à本地地址à远程地址。
参数四dwReceiveDataLength以字节为单位,指定了在lpOutputBuffer缓冲区开头保留多大的空间,用于数据的接收。如这个参数设为0,那么只接受连接,不伴随接收数据。
参数五dwLocalAddressLength和参数六dwRemoteAddressLength也是以字节为单位,指定在lpOutputBuffer缓冲区中,保留多大的空间,在一个套接字被接受的时候,用于本地和远程地址信息的保存。要注意的是,和当前采用的传送协议允许的最大地址长度比较起来,这里指定的缓冲区大小至少应多出16字节。举个例子来说,假定正在使用的是TCP/IP协议,那么这里的大小应设为“SOCKADDR_IN结构的长度+16字节”。参数七lpdwBytesReceived参数用于返回接收到的实际数据量,以字节为单位。只有在操作以同步方式完成的前提下,才会设置这个参数。假如AcceptEx函数返回ERROR_IO_PENDING,那么这个参数永远都不会设置,我们必须利用完成事件通知机制,获知实际读取的字节量。
最后一个参数是lpOverlapped,它对应的是一个OVERLAPPED结构,允许AcceptEx以一种异步方式工作。如我们早先所述,只有在一个重叠I/O应用中,该函数才需要使用事件对象通知机制(hEvent字段),这是由于此时没有一个完成例程参数可供使用。
下一篇:《重叠I/O模型(2)》
参考:
《Windows 2000 Systems Programming Black Book》 Al Williams
《Network Programming for Microsoft Windows》 Anthony Jones,Jim Ohlund
评论