8.2.3 WSAEventSelect
Wi n s o c k提供了另一个有用的异步I / O模型。和W S A A s y n c S e l e c t模型类似的是,它也允许应用程序在一个或多个套接字上,接收以事件为基础的网络事件通知。对于表8 - 3总结的、由W S A A s y n c S e l e c t模型采用的网络事件来说,它们均可原封不动地移植到新模型。在用新模型开发的应用程序中,也能接收和处理所有那些事件。该模型最主要的差别在于网络事件会投递至一个事件对象句柄,而非投递至一个窗口例程。
事件通知
事件通知模型要求我们的应用程序针对打算使用的每一个套接字,首先创建一个事件对象。创建方法是调用W S A C r e a t e E v e n t函数,它的定义如下:

WSAEVENT WSACreateEvent(void);

W S A C r e a t e E v e n t函数的返回值很简单,就是一个创建好的事件对象句柄。事件对象句柄到手后,接下来必须将其与某个套接字关联在一起,同时注册自己感兴趣的网络事件类型,如表8 - 3所示。要做到这一点,方法是调用W S A E v e n t S e l e c t函数,对它的定义如下:

int WSAEventSelect(
          SOCKET s, 
          WSAEVENT hEventObject,
          long lNetwordEvents
         );
 其中, s参数代表自己感兴趣的套接字。h E v e n t O b j e c t参数指定要与套接字关联在一起的 事件对象—用W S A C r e a t e E v e n t取得的那一个。而最后一个参数l N e t w o r k E v e n t s,则对应一个“位掩码”,用于指定应用程序感兴趣的各种网络事件类型的一个组合(如表8 - 3所示)。要想获知对这些事件类型的详细说明,请参考早先讨论过的WSAAsyncSelect I/O模型。
为W S A E v e n t S e l e c t创建的事件拥有两种工作状态,以及两种工作模式。其中,两种工作状态分别是“已传信”(s i g n a l e d)和“未传信”(n o n s i g n a l e d)。工作模式则包括“人工重设”(manual reset)和“自动重设”(auto reset)。W S A C r e a t e E v e n t最开始在一种未传信的工作状态中,并用一种人工重设模式,来创建事件句柄。随着网络事件触发了与一个套接字关联在
一起的事件对象,工作状态便会从“未传信”转变成“已传信”。由于事件对象是在一种人工重设模式中创建的,所以在完成了一个I / O请求的处理之后,我们的应用程序需要负责将工作状态从已传信更改为未传信。要做到这一点,可调用W S A R e s e t E v e n t函数,对它的定义如下:

 BOOL  WSAResetEvent(WSAEVENT hEvent);
 
 该函数唯一的参数便是一个事件句柄;基于调用是成功还是失败,会分别返回T R U E或FA L S E。应用程序完成了对一个事件对象的处理后,便应调用W S A C l o s e E v e n t函数,释放由事件句柄使用的系统资源。对W S A C l o s e E v e n t函数的定义如下:

 BOOL WSACloseEvent(WSAEVENT hEvent);
 
 该函数也要拿一个事件句柄作为自己唯一的参数,并会在成功后返回T R U E,失败后返回FA L S E。
一个套接字同一个事件对象句柄关联在一起后,应用程序便可开始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函数的定义:

DWORD WSAWaitForMultipleEvents(
                DWORD cEvent,
                const WSAEVENT FAR * lphEvents,
                BOOL  fWaitAll,
                DWORD dwTimeout,
                BOOL  fAlertable
               );
               
 其中, c E v e n t s和l p h E v e n t s参数定义了由W S A E V E N T对象构成的一个数组。在这个数组中,c E v e n t s指定的是事件对象的数量,而l p h 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只能支持由W S A _ M A X I M U M _ WA I T _ E V E N T S对象规定的一个最大值,在此定义成6 4个。因此,针对发出W S AWa i t F o r M u l t i p l e E v e n t s调用的每个线程,该I / O模型一次最多都只能支持6 4个套接字。假如想让这个模型同时管理不止6 4个套
接字,必须创建额外的工作者线程,以便等待更多的事件对象。f Wa i t A l l 参数指定了W S AWa i t F o r M u l t i p l e E v e n t s如何等待在事件数组中的对象。若设为T R U E,那么只有等l p h E v e n t s数组内包含的所有事件对象都已进入“已传信”状态,函数才会返回;但若设为FA L S E,任何一个事件对象进入“已传信”状态,函数就会返回。就后一种情况来说,返回值指出了到底是哪个事件对象造成了函数的返回。通常,应用程序应将该参数设为FA L S E,
一次只为一个套接字事件提供服务。d w Ti m e o u t参数规定了W S AWa i t F o r M u l t i p l e E v e n t s最多可等待一个网络事件发生有多长时间,以毫秒为单位,这是一项“超时”设定。超过规定的时间,函数就会立即返回,即使由f Wa i t A l l参数规定的条件尚未满足也如此。如超时值为0,函数会检测指定的事件对象的状态,并立即返回。这样一来,应用程序实际便可实现对事件对
象的“轮询”。但考虑到它对性能造成的影响,还是应尽量避免将超时值设为0。假如没有等待处理的事件, W S AWa i t F o r M u l t i p l e E v e n t s便会返回W S A _ WA I T _ T I M E O U T。如d w s Ti m e o u t设为W S A _ I N F I N I T E(永远等待),那么只有在一个网络事件传信了一个事件对象后,函数才
会返回。最后一个参数是f A l e r t a b l e,在我们使用W S A E v e n t S e l e c t模型的时候,它是可以忽略的,且应设为FA L S E。该参数主要用于在重叠式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的返回值,减去预定义值W S A _ WA I T _ E V E N T _ 0,得到具体的引用值(即索引位置)。如下例所示:

Index = WSAWaitForMultipleEvents(...);
MyEvent = EventArray[Index - WSA_WAIT_EVENT_0];
知道了造成网络事件的套接字后,接下来可调用W S A E n u m N e t w o r k E v e n t s函数,调查发生了什么类型的网络事件。该函数定义如下:

 int WSAEnumNetworkEvents(
              SOCKET s,
              WSAEVENT hEventObject,
              LPWSANETWORKEVENTS lpNetworkEvents
             );
 s参数对应于造成了网络事件的套接字。h E v e n t O b j e c t参数则是可选的;它指定了一个事件句柄,对应于打算重设的那个事件对象。由于我们的事件对象处在一个“已传信”状态,所以可将它传入,令其自动成为“未传信”状态。如果不想用h E v e n t O b j e c t参数来重设事件,那么可使用W S A R e s e t E v e n t 函数, 该函数早先已经讨论过了。最后一个参数是l p N e t w o r k E v e n t s,代表一个指针,指向W S A N E T W O R K E V E N T S结构,用于接收套接字上发
生的网络事件类型以及可能出现的任何错误代码。下面是W S A N E T W O R K E V E N T S结构的定义:

 typedef struct _WSANETWORKEVENTS
 {
  long lNetworkEvents;
  int iErrorCode[FD_MAX_EVENTS];
 }WSANETWORKEVENTS,FAR * LPWSANETWORKEVENTS;
 
 l N e t w o r k E v e n t s参数指定了一个值,对应于套接字上发生的所有网络事件类型(参见表8 - 3)。
注意一个事件进入传信状态时,可能会同时发生多个网络事件类型。例如,一个繁忙
的服务器应用可能同时收到FD_READ和FD_WRITE通知。
i E r r o r C o d e参数指定的是一个错误代码数组,同l N e t w o r k E v e n t s中的事件关联在一起。针对每个网络事件类型,都存在着一个特殊的事件索引,名字与事件类型的名字类似,只是要在事件名字后面添加一个“ _ B I T”后缀字串即可。例如,对F D _ R E A D事件类型来说,i E r r o r C o d e数组的索引标识符便是F D _ R E A D _ B I T。下述代码片断对此进行了阐释(针对F D _ R E A D事件):

  if(NetWorkEvents.lNetworkEvents & FD_READ)
  {
   if(NetWorkEvents.iErrorCode[FD_READ_BIT] != 0)
   {
    printf("FD_READ FAILED with error %d\n",NetWorkEvents.iErrorCode[FD_READ_BIT]);
   }
  }

完成了对W S A N E T W O R K E V E N T S结构中的事件的处理之后,我们的应用程序应在所有可用的套接字上,继续等待更多的网络事件。在程序清单8 - 6中,我们阐释了如何使用W S A E v e n t S e l e c t这种I / O模型,来开发一个服务器应用,同时对事件对象进行管理。这个程序主要着眼于开发一个基本的服务器应用要涉及到的步骤,令其同时负责一个或多个套接字的管理。

完成了对W S A N E T W O R K E V E N T S结构中的事件的处理之后,我们的应用程序应在所有可用的套接字上,继续等待更多的网络事件。在程序清单8 - 6中,我们阐释了如何使用W S A E v e n t S e l e c t这种I / O模型,来开发一个服务器应用,同时对事件对象进行管理。这个程序主要着眼于开发一个基本的服务器应用要涉及到的步骤,令其同时负责一个或多个套接字的管理。
程序清单8-6 采用WSAEventSelect I/O模型的示范服务器源代码

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

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