Windows+网络编程技术(八)Windows_网络编程技术(八)_.pdf
-
资源ID:70009356
资源大小:1.18MB
全文页数:35页
- 资源格式: PDF
下载积分:15金币
快捷下载
会员登录下载
微信登录下载
三方登录下载:
微信扫一扫登录
友情提示
2、PDF文件下载后,可能会被浏览器默认打开,此种情况可以点击浏览器菜单,保存网页到桌面,就可以正常下载了。
3、本站不支持迅雷下载,请使用电脑自带的IE浏览器,或者360浏览器、谷歌浏览器下载即可。
4、本站资源下载后的文档和图纸-无水印,预览文档经过压缩,下载后原文更清晰。
5、试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。
|
Windows+网络编程技术(八)Windows_网络编程技术(八)_.pdf
下载第8章 Winsock I/O方法本章重点是如何在 Wi n d o w s套接字应用程序中对 I/O(输入输出)操作进行管理。Wi n s o c k分别提供了“套接字模式”和“套接字 I/O模型”,可对一个套接字上的 I/O行为加以控制。其中,套接字模式用于决定在随一个套接字调用时,那些 Wi n s o c k函数的行为。而另一方面,套接字模型描述了一个应用程序如何对套接字上进行的 I/O进行管理及处理。要注意的是,“套接字I/O模型”与“套接字模式”是无关的。套接字模型的出现,正是为了解决套接字模式存在的某些限制。Wi n s o c k提供了两种套接字模式:锁定和非锁定。本章第一部分将详细介绍这两种模式,并阐释一个应用程序如何通过它们管理I/O。如大家在本章的后面部分所见,Wi n s o c k提供了一些有趣的I/O模型,有助于应用程序通过一种“异步”方式,一次对一个或多个套接字上进行的通信加以管理。这些模型包括s e l e c t(选择)、W S A A s y n c S e l e c t(异步选择)、W S A E v e n t S e l e c t(事件选择)、Overlapped I/O(重叠式I/O)以及Completion port(完成端口)等等。到本章结束时,我们打算对各种套接字模式以及 I/O模型的优缺点进行总结。同时,帮助大家判断到底哪一种最适合自己应用程序的要求。所有Wi n d o w s平台都支持套接字以锁定或非锁定方式工作。然而,并非每种平台都支持每一种 I/O模型。如表 8-1所示,在当前版本的 Windows CE中,仅提供了一个 I/O模型。Windows 98和Windows 95(取决于安装的是Winsock 1还是Winsock 2)则支持大多数I/O模型,唯一的例外便是I/O完成端口。而到了Windows NT和最新发布的Windows 2000中,每种I/O模型都是支持的。表8-1 操作系统对套接字I/O模型的支持情况平台s e l e c tWSAAsync SelectWSAEvent SelectO v e r l a p p e dCompletion PortWindows CE支持不支持不支持不支持不支持Windows 95(Winsock 1)支持支持不支持不支持不支持Windows 95(Winsock 2)支持支持支持支持不支持Windows 98支持支持支持支持不支持Windows NT支持支持支持支持支持Windows 2000支持支持支持支持支持8.1 套接字模式就像我们前面提到的那样,Wi n d o w s套接字在两种模式下执行 I/O操作:锁定和非锁定。在锁定模式下,在I/O操作完成前,执行操作的Wi n s o c k函数(比如s e n d和r e c v)会一直等候下去,不会立即返回程序(将控制权交还给程序)。而在非锁定模式下,Wi n s o c k函数无论如何都会立即返回。在Windows CE和Windows 95(安装Winsock 1)平台上运行的应用程序仅支持极少的I/O模型,所以我们必须采取一些适当的步骤,让锁定和非锁定套接字能够满足各种场合的要求。8.1.1 锁定模式对于处在锁定模式的套接字,我们必须多加留意,因为在一个锁定套接字上调用任何一个Winsock API函数,都会产生相同的后果耗费或长或短的时间“等待”。大多数Wi n s o c k应用都是遵照一种“生产者消费者”模型来编制的。在这种模型中,应用程序需要读取(或写入)指定数量的字节,然后以它为基础执行一些计算。程序清单 8-1展示的代码片断便是一个典型的例子。程序清单8-1 简单的锁定模式示例这段代码的问题在于,假如没有数据处于“待决”状态,那么 r e c v函数可能永远都无法返回。这是由于从语句可以看出:只有从系统的输入缓冲区中读回点什么东西,才允许返回!有些程序员可能会在r e c v中使用M S G _ P E E K标志,或者调用i o c t l s o c k e t(设置F I O N R E A D选项),在系统的缓冲区中,事先“偷看”是否存在足够的字节数量。然而,在不实际读入数据的前提下,仅仅“偷看”数据(如实际读入数据,便会将其从系统缓冲区中将其删除),可不是一件光彩的事情。我们认为,这是一种非常不好的编程习惯,应尽全力避免。在“偷看”的时候,对系统造成的开销是极大的,因为仅仅为了检查有多少个字节可用,便发出一个或者更多的系统调用。以后,理所当然地,还需要牵涉到进行实际 r e c v调用,将数据从系统缓冲区内删除的开销。那么,如何避免这一情况呢?在此,我们的目标是防止由于数据的缺乏(这可能是网络出了故障,也可能是客户机出了问题),造成应用程序完全陷于“凝固”状态,同时不必连续性地检视系统网络缓冲!为达此目的,一个办法是将应用程序划分为一个读线程,以及一个计算线程。两个线程都共享同一个数据缓冲区。对这个缓冲区的访问需要受到一定的限制,这是用一个同步对象来实现的,比如一个事件或者 M u t e x(互斥体)。“读线程”的职责是从网络连续地读入数据,并将其置入共享缓冲区内。读线程将计算线程开始工作至少需要的数据量拿到手后,便会触发一个事件,通知计算线程:你老兄可以开始干活了!随后,计算线程从缓冲区取走(删除)一个数据块,然后进行要求的计算。第8章计Winsock I/O方法计计173下载在程序清单8-2中,我们分别提供了两个函数,采取的便是上述办法。在两个函数中,一个负责读取网络数据(R e a d T h r e a d),另一个则负责对数据执行计算(P r o c e s s T h r e a d)。程序清单8-2 多线程的锁定套接字示例174计计第二部分附Winsock API下载第8章计Winsock I/O方法计计175下载对锁定套接字来说,它的一个缺点在于:应用程序很难同时通过多个建好连接的套接字通信。使用前述的办法,我们可对应用程序进行修改,令其为连好的每个套接字都分配一个读线程,以及一个数据处理线程。尽管这仍然会增大一些开销,但的确是一种可行的方案。唯一的缺点便是扩展性极差,以后想同时处理大量套接字时,恐怕难以下手。8.1.2 非锁定模式除了锁定模式,我们还可考虑采用非锁定模式的套接字。尽管这种套接字在使用上存在着些许难度,但只要排除了这项困难,它在功能上还是非常强大的。除具备锁定套接字已有的各项优点之外,还进行了少许扩充,功能更强。程序清单 8-3向大家展示了如何创建一个套接字,并将其置为非锁定模式。程序清单8-3 设置一个非锁定套接字将一个套接字置为非锁定模式之后,Winsock API调用会立即返回。大多数情况下,这些调用都会“失败”,并返回一个W S A E W O U L D B L O C K错误。什么意思呢?它意味着请求的操作在调用期间没有时间完成。举个例子来说,假如在系统的输入缓冲区中,尚不存在“待决”的数据,那么r e c v(接收数据)调用就会返回 W S A E W O U L D B L O C K错误。通常,我们需要重复调用同一个函数,直至获得一个成功返回代码。在表 8-2中,我们对常见Wi n s o c k调用返回的W S A E W O U L D B L O C K错误的含义进行了总结。由于非锁定调用会频繁返回 W S A E W O U L D B L O C K错误,所以在任何时候,都应仔细检查所有返回代码,并作好“失败”的准备。许多程序员易犯的一个错误便是连续不停地调用一个函数,直到它返回成功的消息为止。例如,假定在一个紧凑的循环中不断地调用 r e c v,以读入2 0 0个字节的数据,那么与使用前述的 M S G _ P E E K标志来“轮询”一个锁定套接字相比,前一种做法根本没有任何优势可言。为此,Wi n s o c k的套接字I/O模型可帮助应用程序判断一个套接字何时可供读写。锁定和非锁定套接字模式都存在着优点和缺点。其中,从概念的角度说,锁定套接字更易使用。但在应付建立连接的多个套接字时,或在数据的收发量不均,时间不定时,却显得极难管理。而另一方面,假如需要编写更多的代码,以便在每个 Wi n s o c k调用中,对收到一个176计计第二部分附Winsock API下载W S A E W O U L D B L O C K错误的可能性加以应付,那么非锁定套接字便显得有些难于操作。在这些情况下,可考虑使用“套接字 I/O模型”,它有助于应用程序通过一种异步方式,同时对一个或多个套接字上进行的通信加以管理。表8-2 非锁定套接字上的W S A E W O U L D B L O C K错误函数名说明W S A A c c e p t和a c c e p t应用程序没有收到连接请求。再次调用,便可检查连接情况c l o s e s o c k e t大多数情况下,这个错误意味着已随S O _ L I N G E R选项一道,调用了s e t s o c k o p t,而且已设定了一个非零的超时值W S A C o n n e c t和c o n n e c t应用程序已初始化。再次调用,便可检查是否完成W S A R e c v、r e c v、W S A R e c v F r o m和r e c v f r o m没有收到数据。稍后再次检查W S A S e n d、s e n d、W S A S e n d To和s e n d t o外出数据无缓冲区可用。稍后再试8.2 套接字I/O模型共有五种类型的套接字I/O模型,可让Wi n s o c k应用程序对I/O进行管理,它们包括:s e l e c t(选择)、W S A A s y n c S e l e c t(异步选择)、W S A E v e n t S e l e c t(事件选择)、o v e r l a p p e d(重叠)以及completion port(完成端口)。在这一节里,我们打算向大家解释每种 I/O模型的特点,同时讲述如何利用这些模型,来开发自己的应用程序,以便同时管理一个或多个套接字请求。在本书的配套光盘上,针对每种 I/O模型,大家都可以找到一个或者更多的示范应用程序。这些程序阐述了如何利用每种模型的原理,开发一个简单的 T C P回应服务器。8.2.1 select模型s e l e c t(选择)模型是Wi n s o c k中最常见的I/O模型。之所以称其为“s e l e c t模型”,是由于它的“中心思想”便是利用s e l e c t函数,实现对I/O的管理!最初设计该模型时,主要面向的是某些使用U n i x操作系统的计算机,它们采用的是 B e r k e l e y套接字方案。s e l e c t模型已集成到Winsock 1.1中,它使那些想避免在套接字调用过程中被无辜“锁定”的应用程序,采取一种有序的方式,同时进行对多个套接字的管理。由于 Winsock 1.1向后兼容于B e r k e l e y套接字实施方案,所以假如有一个 B e r k e l e y套接字应用使用了 s e l e c t函数,那么从理论角度讲,毋需对其进行任何修改,便可正常运行。利用s e l e c t函数,我们判断套接字上是否存在数据,或者能否向一个套接字写入数据。之所以要设计这个函数,唯一的目的便是防止应用程序在套接字处于锁定模式中时,在一次 I/O绑定调用(如s e n d或r e c v)过程中,被迫进入“锁定”状态;同时防止在套接字处于非锁定模式中时,产生W S A E W O U L D B L O C K错误。除非满足事先用参数规定的条件,否则 s e l e c t函数会在进行I/O操作时锁定。s e l e c t的函数原型如下:其中,第一个参数 n f d s会被忽略。之所以仍然要提供这个参数,只是为了保持与早期的B e r k e l e y套接字应用程序的兼容。大家可注意到三个f d _ s e t参数:一个用于检查可读性(r e a d f d s),一个用于检查可写性(w r i t e f d s),另一个用于例外数据(e x c e p t f d s)。从根本上说,f d _ s e t数据类型代表着一系列特定套接字的集合。其中,r e a d f d s集合包括符合下述任何一个条件的套接字:有数据可以读入。连接已经关闭、重设或中止。假如已调用了l i s t e n,而且一个连接正在建立,那么a c c e p t函数调用会成功。w r i t e f d s集合包括符合下述任何一个条件的套接字:有数据可以发出。如果已完成了对一个非锁定连接调用的处理,连接就会成功。最后,e x c e p t f d s集合包括符合下述任何一个条件的套接字:假如已完成了对一个非锁定连接调用的处理,连接尝试就会失败。有带外(O u t-o f-b a n d,O O B)数据可供读取。例如,假定我们想测试一个套接字是否“可读”,必须将自己的套接字增添到r e a d f d s集合,再等待s e l e c t函数完成。s e l e c t完成之后,必须判断自己的套接字是否仍为 r e a d f d s集合的一部分。若答案是肯定的,便表明该套接字“可读”,可立即着手从它上面读取数据。在三个参数中(r e a d f d s、w r i t e f d s和e x c e p t f d s),任何两个都可以是空值(N U L L);但是,至少有一个不能为空值!在任何不为空的集合中,必须包含至少一个套接字句柄;否则,s e l e c t函数便没有任何东西可以等待。最后一个参数 t i m e o u t对应的是一个指针,它指向一个 t i m e v a l结构,用于决定s e l e c t最多等待I/O操作完成多久的时间。如 t i m e o u t是一个空指针,那么s e l e c t调用会无限期地“锁定”或停顿下去,直到至少有一个描述符符合指定的条件后结束。对 t i m e v a l结构的定义如下:其中,t v _ s e c字段以秒为单位指定等待时间;t v _ u s e c字段则以毫秒为单位指定等待时间。若将超时值设置为(0,0),表明s e l e c t会立即返回,允许应用程序对 s e l e c t操作进行“轮询”。出于对性能方面的考虑,应避免这样的设置。s e l e c t成功完成后,会在 f d _ s e t结构中,返回刚好有未完成的I/O操作的所有套接字句柄的总量。若超过 t i m e v a l设定的时间,便会返回 0。不管由于什么原因,假如s e l e c t调用失败,都会返回S O C K E T _ E R R O R。用s e l e c t对套接字进行监视之前,在自己的应用程序中,必须将套接字句柄分配给一个集合,设置好一个或全部读、写以及例外 f d _ s e t结构。将一个套接字分配给任何一个集合后,再来调用s e l e c t,便可知道一个套接字上是否正在发生上述的 I/O活动。Wi n s o c k提供了下列宏操作,可用来针对I/O活动,对f d _ s e t进行处理与检查:FD_CLR(s,*set):从s e t中删除套接字s。FD_ISSET(s,*set):检查s是否s e t集合的一名成员;如答案是肯定的是,则返回 T R U E。FD_SET(s,*set):将套接字s加入集合s e t。F D _ Z E R O(*s e t):将s e t初始化成空集合。第8章计Winsock I/O方法计计177下载例如,假定我们想知道是否可从一个套接字中安全地读取数据,同时不会陷于无休止的“锁定”状态,便可使用 F D _ S E T宏,将自己的套接字分配给 f d _ r e a d集合,再来调用s e l e c t。要想检测自己的套接字是否仍属 f d _ r e a d集合的一部分,可使用F D _ I S S E T宏。采用下述步骤,便可完成用s e l e c t操作一个或多个套接字句柄的全过程:1)使用F D _ Z E R O宏,初始化自己感兴趣的每一个f d _ s e t。2)使用F D _ S E T宏,将套接字句柄分配给自己感兴趣的每个 f d _ s e t。3)调用s e l e c t函数,然后等待在指定的f d _ s e t集合中,I/O活动设置好一个或多个套接字句柄。s e l e c t完成后,会返回在所有f d _ s e t集合中设置的套接字句柄总数,并对每个集合进行相应的更新。4)根据s e l e c t的返回值,我们的应用程序便可判断出哪些套接字存在着尚未完成(待决)的I/O操作具体的方法是使用F D _ I S S E T宏,对每个f d _ s e t集合进行检查。5)知道了每个集合中“待决”的I/O操作之后,对I/O进行处理,然后返回步骤1),继续进行s e l e c t处理。s e l e c t返回后,它会修改每个 f d _ s e t结构,删除那些不存在待决 I/O操作的套接字句柄。这正是我们在上述的步骤(4)中,为何要使用F D _ I S S E T宏来判断一个特定的套接字是否仍在集合中的原因。在程序清单8-4中,我们向大家阐述了为一个(只有一个)套接字设置 s e l e c t模型所需的一系列基本步骤。若想在这个应用程序中添加更多的套接字,只需为额外的套接字维护它们的一个列表,或维护它们的一个数组即可。程序清单8-4 用s e l e c t管理一个套接字上的I/O操作178计计第二部分附Winsock API下载第8章计Winsock I/O方法计计179下载8.2.2 WSAAsyncSelectWi n s o c k提供了一个有用的异步 I/O模型。利用这个模型,应用程序可在一个套接字上,接收以 Wi n d o w s消息为基础的网络事件通知。具体的做法是在建好一个套接字后,调用W S A A s y n c S e l e c t函数。该模型最早出现于 Wi n s o c k的1.1版本中,用于帮助应用程序开发者面向一些早期的1 6位Wi n d o w s平台(如Windows for Wo r k g r o u p s),适应其“落后”的多任务消息环境。应用程序仍可从这种模型中得到好处,特别是它们用一个标准的Wi n d o w s例程(常称为“w i n p r o c”),对窗口消息进行管理的时候。该模型亦得到了 Microsoft Foundation Class(微软基本类,M F C)对象C S o c k e t的采纳。消息通知要想使用W S A A s y n c S e l e c t模型,在应用程序中,首先必须用 C r e a t e Wi n d o w函数创建一个窗口,再为该窗口提供一个窗口例程支持函数(Wi n p r o c)。亦可使用一个对话框,为其提供一个对话例程,而非窗口例程,因为对话框本质也是“窗口”。考虑到我们的目的,我们打算用一个简单的窗口来演示这种模型,采用的是一个支持窗口例程。设置好窗口的框架后,便可开始创建套接字,并调用W S A A s y n c S e l e c t函数,打开窗口消息通知。该函数的定义如下:其中,s参数指定的是我们感兴趣的那个套接字。h W n d参数指定的是一个窗口句柄,它对应于网络事件发生之后,想要收到通知消息的那个窗口或对话框。w M s g参数指定在发生网络事件时,打算接收的消息。该消息会投递到由 h W n d窗口句柄指定的那个窗口。通常,应用程序需要将这个消息设为比 Wi n d o w s的W M _ U S E R大的一个值,避免网络窗口消息与预定义的标准窗口消息发生混淆与冲突。最后一个参数是 l E v e n t,它指定的是一个位掩码,对应于一系列网络事件的组合(请参考表8-3),应用程序感兴趣的便是这一系列事件。大多数应用程序通常感兴趣的网络事件类型包括:F D _ R E A D、F D _ W R I T E、F D _ A C C E P T、F D _ C O N N E C T和F D _ C L O S E。当然,到底使用F D _ A C C E P T,还是使用F D _ C O N N E C T类型,要取决于应用程序的身份到底是一个客户机呢,还是一个服务器。如应用程序同时对多个网络事件有兴趣,只需对各种类型执行一次简单的按位 O R(或)运算,然后将它们分配给 l E v e n t就可以了。举个例子来说:180计计第二部分附Winsock API下载这样一来,我们的应用程序以后便可在套接字 s上,接收到有关连接、发送、接收以及套接字关闭这一系列网络事件的通知。特别要注意的是,多个事件务必在套接字上一次注册!另外还要注意的是,一旦在某个套接字上允许了事件通知,那么以后除非明确调用c l o s e s o c k e t命令,或者由应用程序针对那个套接字调用了 W S A A s y n c S e l e c t,从而更改了注册的网络事件类型,否则的话,事件通知会永远有效!若将 l E v e n t参数设为0,效果相当于停止在套接字上进行的所有网络事件通知。若应用程序针对一个套接字调用了W S A A s y n c S e l e c t,那么套接字的模式会从“锁定”自动变成“非锁定”,我们在前面已提到过这一点。这样一来,假如调用了像W S A R e c v这样的Wi n s o c kI/O函数,但当时却并没有数据可用,那么必然会造成调用的失败,并返回W S A E W O U L D B L O C K错误。为防止这一点,应用程序应依赖于由W S A A s y n c S e l e c t的u M s g参数指定的用户自定义窗口消息,来判断网络事件类型何时在套接字上发生;而不应盲目地进行调用。表8-3 用于W S A A s y n c S e l e c t函数的网络事件类型事件类型含义F D _ R E A D应用程序想要接收有关是否可读的通知,以便读入数据F D _ W R I T E应用程序想要接收有关是否可写的通知,以便写入数据F D _ O O B应用程序想接收是否有带外(O O B)数据抵达的通知F D _ A C C E P T应用程序想接收与进入连接有关的通知F D _ C O N N E C T应用程序想接收与一次连接或者多点 j o i n操作完成的通知F D _ C L O S E应用程序想接收与套接字关闭有关的通知F D _ Q O S应用程序想接收套接字“服务质量”(Q o S)发生更改的通知F D _ G R O U P _ Q O S应用程序想接收套接字组“服务质量”发生更改的通知(现在没什么用处,为未来套接字组的使用保留)F D _ R O U T I N G _ I N T E R FA C E _ C H A N G E应用程序想接收在指定的方向上,与路由接口发生变化的通知F D _ A D D R E S S _ L I S T _ C H A N G E应用程序想接收针对套接字的协议家族,本地地址列表发生变化的通知应用程序在一个套接字上成功调用了W S A A s y n c S e l e c t之后,应用程序会在与h W n d窗口句柄参数对应的窗口例程中,以Windows消息的形式,接收网络事件通知。窗口例程通常定义如下:其中,h W n d参数指定一个窗口的句柄,对窗口例程的调用正是由那个窗口发出的。u M s g参数指定需要对哪些消息进行处理。就我们的情况来说,感兴趣的是 W S A A s y n c S e l e c t调用中定义的消息。w P a r a m参数指定在其上面发生了一个网络事件的套接字。假若同时为这个窗口例程分配了多个套接字,这个参数的重要性便显示出来了。在 l P a r a m参数中,包含了两方面重要的信息。其中,l P a r a m的低字(低位字)指定了已经发生的网络事件,而 l P a r a m的高字(高位字)包含了可能出现的任何错误代码。网络事件消息抵达一个窗口例程后,应用程序首先应检查 l P a r a m的高字位,以判断是否在套接字上发生了一个网络错误。这里有一个特殊的宏:W S A G E T S E L E C T E R R O R,可用它返回高字位包含的错误信息。若应用程序发现套接字上没有产生任何错误,接着便应调查到底是哪个网络事件类型,造成了这条Wi n d o w s消息的触发具体的做法便是读取l P a r a m之低字位的内容。此时可使用另一个特殊的宏:W S A G E T S E L E C T E V E N T,用它返回l P a r a m的低字部分。在程序清单8-5中,我们向大家演示了如何使用 W S A A s y n c S e l e c t这种I/O模型,来实现窗口消息的管理。在源程序中,我们着重强调的是开发一个基本服务器应用要涉及到的基本步骤,忽略了开发一个完整的Wi n d o w s应用需要涉及到的大量编程细节。程序清单8-5 WSAAsyncSelect服务器示范代码第8章计Winsock I/O方法计计181下载182计计第二部分附Winsock API下载第8章计Winsock I/O方法计计183下载最后一个特别有价值的问题是应用程序如何对 F D _ W R I T E事件通知进行处理。只有在三种条件下,才会发出F D _ W R I T E通知:使用c o n n e c t或W S A C o n n e c t,一个套接字首次建立了连接。使用a c c e p t或W S A A c c e p t,套接字被接受以后。若s e n d、W S A S e n d、s e n d t o或W S A S e n d To操作失败,返回了W S A E W O U L D B L O C K错误,而且缓冲区的空间变得可用因此,作为一个应用程序,自收到首条 F D _ W R I T E消息开始,便应认为自己必然能在一个套接字上发出数据,直至一个 s e n d、W S A S e n d、s e n d t o或W S A S e n d To返回套接字错误W S A E W O U L D B L O C K。经过了这样的失败以后,要再用另一条 F D _ W R I T E通知应用程序再次发送数据。8.2.3 WSAEventSelectWi 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函数,它的定义如下: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函数,对它的定义如下:其中,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函数,对它的定义如下:该函数唯一的参数便是一个事件句柄;基于调用是成功还是失败,会分别返回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函数的定义如下:该函数也要拿一个事件句柄作为自己唯一的参数,并会在成功后返回 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函数的定义:其中,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最多可184计计第二部分附Winsock API下载等待一个网络事件发生有多长时间,以毫秒为单位,这是一项“超时”设定。超过规定的时间,函数就会立即返回,即使由 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,得到具体的引用值(即索引位置)。如下例所示:知道了造成网络事件的套接字后,接下来可调用 W S A E n u m N e t w o r k E v e n t s函数,调查发生了什么类型的网络事件。该函数定义如下: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结构的定义: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事件类型来说,第8章计Winsock I/O方法计计185下载i E r r o r C o d e数组的索引标识符便是 F D _ R E A D _ B I T。下述代码片断对此进行了阐释(针对F D