8.2.4 重叠模型
在Wi n s o c k中,相比我们迄今为止解释过的其他所有I / O模型,重叠I / O(Overlapped I/O)
模型使应用程序能达到更佳的系统性能。重叠模型的基本设计原理便是让应用程序使用一个重叠的数据结构,一次投递一个或多个Winsock I/O请求。针对那些提交的请求,在它们完成之后,应用程序可为它们提供服务。该模型适用于除Windows CE之外的各种Wi n d o w s平台。
模型的总体设计以Wi n 3 2重叠I / O机制为基础。那个机制可通过R e a d F i l e和Wr i t e F i l e两个函数,针对设备执行I / O操作。
最开始的时候,Wi n s o c k重叠I / O模型只能应用于Windows NT操作系统上运行的Wi n s o c k1 . 1应用程序。作为应用程序,它可针对一个套接字句柄,调用R e a d F i l e以及Wr i t e F i l e,同时指定一个重叠式结构(稍后详述),从而利用这个模型。自Winsock 2发布开始,重叠I / O便已集成到新的Wi n s o c k函数中,比如W S A S e n d和W S A R e c v。这样一来,重叠I / O模型便能适用于安装了Winsock 2的所有Wi n d o w s平台。

注意在Winsock 2发布之后,重叠I/O仍可在Windows NT和Windows 2000这两个操作系统中,随R e a d F i l e和Wr i t e F i l e两个函数使用。但是,Windows 95和Windows 98均不具备这一功能。考虑到应用程序的跨平台兼容问题,同时考虑到性能方面的因素,应尽量避免使用Wi n 3 2的R e a d F i l e和Wr i t e F i l e函数,分别换以W S A R e c v和W S A S e n d函数。本节只打算讲述通过新的Winsock 2函数,来使用重叠I/O模型。
要想在一个套接字上使用重叠I / O模型,首先必须使用W S A _ F L A G _ O V E R L A P P E D这个标志,创建一个套接字。如下所示:

  s = WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERLAPPED);
  创建套接字的时候,假如使用的是s o c k e t函数,而非W S A S o c k e t函数,那么会默认设置W S A _ F L A G _ O V E R L A P P E D标志。成功建好一个套接字,同时将其与一个本地接口绑定到一起后,便可开始进行重叠I / O 操作,方法是调用下述的Wi n s o c k 函数,同时指定一个
W S A O V E R L A P P E D结构(可选):
■ W S A S e n d
■ W S A S e n d To
■ W S A R e c v
■ W S A R e c v F r o m
■ W S A I o c t l
■ A c c e p t E x
■ Tr n a s m i t F i l e
大家现在或许已经知道,其中每个函数都与一个套接字上数据的发送、数据接收以及连接的接受有关。因此,这些活动可能会花极少的时间才能完成。这正是每个函数都可接受一个W S A O V E R L A P P E D结构作为参数的原因。若随一个W S A O V E R L A P P E D结构一起调用这些函数,函数会立即完成并返回,无论套接字是否设为锁定模式(本章开头已详细讲过了)。它
们依赖于W S A O V E R L A P P E D结构来返回一个I / O请求的返回。
主要有两个方法可用来管理一个重叠I / O请求的完成:我们的应用程序可等待“事件对象通知”,亦可通过“完成例程”,对已经完成的请求加以处理。上面列出的函数( A c c e p t E x除外)还有另一个常用的参数:
l p C o m p l e t i o n R O U T I N E。该参数指定的是一个可选的指针,指向一个完成例程函数,在重叠请求完成后调用。接下去,我们将探讨事件通知方法。在本章稍后,还会介绍如何使用可选的完成例程,代替事件,对完成的重叠请求加以处理。
1. 事件通知
重叠I / O的事件通知方法要求将Wi n 3 2事件对象与W S A O V E R L A P P E D结构关联在一起。若使用一个W S A O V E R L A P P E D结构,发出像W S A S e n d和W S A R e c v这样的I / O调用,它们会立即返回。
通常,大家会发现这些I / O 调用会以失败告终,返回S O C K E T _ E R R O R 。使用W S A G e t L a s t E r r o r函数,便可获得与错误状态有关的一个报告。这个错误状态意味着I / O操作正在进行。稍后的某个时间,我们的应用程序需要等候与W S A O V E R L A P P E D结构对应的事件对象,了解一个重叠I / O请示何时完成。W S A O V E R L A P P E D结构在一个重叠I / O请求的初始化,及其后续的完成之间,提供了一种沟通或通信机制。下面是这个结构的定义:

 typedef struct WSAOVERLAPPED
 {
  DWORD Internal;
  DWORD InternalHigh;
  DWORD Offset;
  DWORD OffsetHigh;
  WSAEVENT hEvent;
 }WSAOVERLAPPED,FAR *LPWSAOVERLPPED;
 
其中,I n t e r n a l、I n t e r n a l H i g h、O ff s e t和O ff s e t H i g h字段均由系统在内部使用,不应由应用程序直接进行处理或使用。而另一方面, h E v e n t字段有点儿特殊,它允许应用程序将一个事件对象句柄同一个套接字关联起来。大家可能会觉得奇怪,如何将一个事件对象句柄分配给该字段呢?正如我们早先在W S A E v e n t S e l e c t模型中讲述的那样,可用W S A C r e a t e E v e n t函数来创建一个事件对象句柄。一旦创建好一个事件句柄,简单地将重叠结构的h E v e n t字段分配给事件句柄,再使用重叠结构,调用一个Wi n s o c k函数即可,比如W S A S e n d或W S A R e c v。
一个重叠I / O请求最终完成后,我们的应用程序要负责取回重叠I / O操作的结果。一个重叠请求操作最终完成之后,在事件通知方法中, Wi n s o c k会更改与一个W S A O V E R L A P P E D结构对应的一个事件对象的事件传信状态,将其从“未传信”变成“已传信”。由于一个事件对象已分配给W S A O V E R L A P P E D结构,所以只需简单地调用W S AWa i t F o r M u l t i p l e E v e n t s函数,从而判断出一个重叠I / O调用在什么时候完成。该函数已在我们前面讲述WSAEventSelect I/O模型时介绍过了。W S AWa i t F o r M u l t i p l e E v e n t s函数会等候一段规定的时间,等待一个或多个事件对象进入“已传信”状态。在此再次提醒大家注意: W S AWa i t F o r M u l t i p l e E v e n t s函数一次
最多只能等待6 4个事件对象。发现一次重叠请求完成之后,接着需要调用W S A G e t O v e r l a p p e dR e s u l t(取得重叠结构)函数,判断那个重叠调用到底是成功,还是失败。该函数的定义如下:
 BOOL WSAGetOverlappedResult(
                SOCKET s,
                LPWSAOVERLAPPED lpOverlapped,
                LPWORD lpcbTransfer,
                BOOL  fWait,
                LPWORD lpdwFlags
               );
其中, s参数用于指定在重叠操作开始的时候,与之对应的那个套接字。l p O v e r l a p p e d参数是一个指针,对应于在重叠操作开始时,指定的那个W S A O V E R L A P P E D 结构。
l p c b Tr a n s f e r参数也是一个指针,对应一个D W O R D(双字)变量,负责接收一次重叠发送或接收操作实际传输的字节数。f Wa i t参数用于决定函数是否应该等待一次待决(未决)的重叠操作完成。若将f Wa i t设为T R U E,那么除非操作完成,否则函数不会返回;若设为FA L S E,而且操作仍然处于“待决”状态,那么W S A G e t O v e r l a p p e d R e s u l t函数会返回FA L S E值,同时返回一个W S A _ I O _ I N C O M P L E T E(I / O操作未完成)错误。但就我们目前的情况来说,由于需要等候重叠操作的一个已传信事件完成,所以该参数无论采用什么设置,都没有任何效果。
最后一个参数是l p d w F l a g s,它对应于一个指针,指向一个D W O R D(双字),负责接收结果标志(假如原先的重叠调用是用W S A R e c v或W S A R e c v F r o m函数发出的)。
如W S A G e t O v e r l a p p e d R e s u l t函数调用成功,返回值就是T R U E。这意味着我们的重叠I / O操作已成功完成,而且由l p c b Tr a n s f e r参数指向的值已进行了更新。若返回值是FA L S E,那么可能是由下述任何一种原因造成的:
■ 重叠I / O操作仍处在“待决”状态。
■ 重叠操作已经完成,但含有错误。
■ 重叠操作的完成状态不可判决,因为在提供给W S A G e t O v e r l a p p e d R e s u l t函数的一个或
多个参数中,存在着错误。

失败后,由l p c b Tr a n s f e r参数指向的值不会进行更新,而且我们的应用程序应调用W S A G e t L a s t E r r o r函数,调查到底是何种原因造成了调用失败。
在程序清单8 - 7中,我们向大家阐述了如何编制一个简单的服务器应用,令其在一个套接字上对重叠I / O操作进行管理,程序完全利用了前述的事件通知机制。对该程序采用的编程步骤总结如下:
1) 创建一个套接字,开始在指定的端口上监听连接请求。
2) 接受一个进入的连接请求。
3) 为接受的套接字新建一个W S A O V E R L A P P E D结构,并为该结构分配一个事件对象句柄。
也将事件对象句柄分配给一个事件数组,以便稍后由W S AWa i t F o r M u l t i p l e E v e n t s函数使用。
4) 在套接字上投递一个异步W S A R e c v请求,指定参数为W S A O V E R L A P P E D结构。
注意函数通常会以失败告终,返回S O C K E T _ E R R O R错误状态W S A _ I O _ P E N D I N G(I/O操作尚未完成)。
5) 使用步骤3 )的事件数组,调用W S AWa i t F o r M u l t i p l e E v e n t s函数,并等待与重叠调用关联在一起的事件进入“已传信”状态(换言之,等待那个事件的“触发”)。
6) WSAWa i t F o r M u l t i p l e E v e n t s函数完成后,针对事件数组,调用W S A R e s e t E v e n t(重设事件)函数,从而重设事件对象,并对完成的重叠请求进行处理。
7) 使用W S A G e t O v e r l a p p e d R e s u l t函数,判断重叠调用的返回状态是什么。
8) 在套接字上投递另一个重叠W S A R e c v请求。
9) 重复步骤5 ) ~ 8 )。
这个例子极易扩展,提供对多个套接字的支持。方法是将代码的重叠I / O处理部分移至一个独立的线程内,让主应用程序线程为附加的连接请求提供服务。
程序清单8-7 采用事件机制的简单重叠I / O处理示例

Posted on 2006-09-15 22:05 艾凡赫 阅读(1106) 评论(0)  编辑 收藏 引用 所属分类: win32 sdk 编程网络编程

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理