三.Windows I/O模型之事件选择(WSAEventSelect )模型

1.事件选择模型:和异步选择模型类似的是,它也允许应用程序在一个或多个套接字上,接收以事件为基础的网络事件通知。对于异步选择模型采用的网络事件来说,它们均可原封不动地移植到事件选择模型。事件选择模型和异步选择模型最主要的差别在于网络事件会投递至一个事件对象句柄,而非投递至一个窗口例程。
2.创建事件对象:事件选择模型要求应用程序针对打算使用事件选择模型的每一个套接字,首先创建一个事件对象。创建方法是调用WSACreateEvent函数,它的定义如下:WSAEVENT WSACreateEvent(void);
3.绑定事件对象与套接字:
int WSAEventSelect(
    SOCKET s,
    WSAEvent hEventObject,
    long lNetworkEvents//网络事件,就是异步选择中的网络事件,用法完全相同
    );
 对于事件来说,他有两种类型,自动事件和人工事件。有两种状态,未触发状态和触发状态。使用WSACreateEvent函数创建的事件默认为人工事件且处于未触发状态。随着网络事件触发了与一个套接字关联在一起的事件对象,工作状态便会从未触发状态转变成触发状态。由于事件对象是在一种人工重设模式中创建的,所以在完成了一个I/O请求的处理之后,我们的应用程序需要负责将工作
状态触发状态更改未触发状态。
4.重置事件为未触发状态:
BOOL WSAResetEvent(WSAEVENT hEvent);
该函数的功能就是把时间从触发状态重置为未触发状态
5.关闭事件对象,释放其所占用的内核资源:
BOOL WSACloseEvent(WSAEVENT hEvent);
6.监视事件对象的状态:
DWORD WSAWaitForMultipleEvents(
    DWORD cEvents;//事件对象数组中事件的数目
    const WSAEVENT FAR* lphEvents,//事件对象数组
    BOOL fWaitAll,//该参数指明了是否要等到所有事件对象变为触发状态函数才返回
    DWORD dwTimeout,//超时,毫秒为单位超过规定的时间,函数就会立即返回,即使由fWaitAll参数规定的条件尚未满足也如此
    BOOL fAlertable//忽略,置为FALSE
    );
参数:
    要注意的是,WSAWaitForMultipleEvents只能支持由WSA_MAXIMUM_WAIT_EVENTS对象规定的一个最大值,在此定义成6 4个。因此,针对发出WSAWaitForMultipleEvents调用的每个线程,该I/O模型一次最多都只能支持6 4个套接字。假如想让这个模型同时管理不止64个套接字,必须创建额外的工作者线程,以便等待更多的事件对象。fWaitAll 参数指定了指明了是否要等到所有事件对象变为触发状态函数才返回。若设为TRUE,那么只有等lphEvents数组内包含的所有事件对象都已进入触发状态,函数才会返回;但若设为FALSE,任何一个事件对象进入触发状态,函数就会返回。就后一种情况来说,返回值指出了到底是哪个事件对象造成了函数的返回。通常,应用程序应将该参数设为FALSE,通常,dwTimeout被置为0.
一次只为一个套接字事件提供服务    
函数解释:
一个套接字同一个事件对象句柄关联在一起后,应用程序便可开始I/O处理;方法是等待网络事件触发事件对象句柄的工作状态。WSAWaitForMultipleEvents函数的设计宗旨便是用来等待一个或多个事件对象句柄,并在事先指定的一个或所有事件对象进入触发状态后,
或在超过了一个规定的时间周期后,立即返回。
7.确定网络事件发生的套接字:若WSAWaitForMultipleEvents收到一个事件对象的网络事件通知,便会返回一个值,指出造成函数返回的事件对象。这样一来,我们的应用程序便可引用事件数组中已传信的事件,并检索与那个事件对应的套接字,判断到底是在哪个套接字上,发生了什么网络事件类型。对事件数组中的事件进行引用时,应该用WSAWaitForMultipleEvents的返回值,减去预定义值WSA_WAIT_EVENT_0,得到具体的引用值(即索引位置)。
Index=WSAWaitForMultipleEvents(...);
myEvent=EventArray[Index-WSA_WAIT_EVENT_0];
7.调查发生的网络事件类型:
int WSAEnumNetworkEvents(
    SOCKET s,
    WSAEVENT hEventObjects,//参数可选,对应于打算重设的事件对象,即设置事件为未触发状态。和WSAtResetEvent函数功能相同
    LPWSANETWORKEVENTS lpNetworkEvents//用来接受发生的网络事件类型以及可能出现的任何错误代码
    );
该函数中的第四个参数用来接收发生的网络事件类型
8.WSANETWORKEVENTS结构:
tydef struct _WSANETWORKEVENTS
{
    long lNetworkEvents;//网络事件类型
    long iErrorCode[FD_MAX_EVENTS];//错误代码
}WSANETWORKEVENTS,FAR* LPWSANETWORKEVENTS;
iErrorCode参数指定的是一个错误代码数组,同lNetworkEvents中的事件关联在一起。iErrorCode针对每个网络事件类型,都存在着一个特殊的事件索引,名字与事件类型的名字类似,只是要在事件名字后面添加一个“ _BIT”后缀字串即可.

示例代码:

 1 SOCKET Socket[WSA_MAXIMUM_WAIT_EVENTS];//64
 2 WSAEVENT event[WSA_MAXIMUM_WAIT_EVENTS];
 3 SOCKET accept,listen;
 4 DWORD eventTotal=0;
 5 DWORD index;
 6 
 7 //创建套接字
 8 listen=socket(...);
 9 
10 //绑定本地地址
11 bind(...);
12 
13 //创建事件对象
14 WSAEVENT newEvent;
15 newEvent=WSACreateEvent();
16 
17 //注册网络事件
18 WSAEventSelect(listen,newEvent,FD_ACCEPT|FD_CLOSE);
19 
20 Socket[eventTotal]=listen;
21 event[eventTotal]=newEventl;
22 eventTotal++;
23 
24 
25 while(1)
26 {
27     //等待事件触发状态
28     index=WSAWaitForMultipleEvents(eventTotal,event,FALSE,WSA_INFINITE,FALSE);
29 
30     //查看发生的网络事件类型,确定发生网络事件的套接字
31     WSANETWORKEVENTS networkEvents;
32     WSAEnumNetworkEvents(Socket[index-WSA_WAIT_EVENT_0],event[index-WSA_WAIT_EVENT_0],&networkEvents);
33 
34     //确定发生的网络事件类型
35     if(networkEvents.lNetworkEvents&FD_ACCEPT)
36     {
37         if(networkEvents.iErrorCode[FD_ACCEPT_BIT]!=0)
38         {
39             printf("FD_ACCEPT failed with error %d
",networkEvents.iErrorCode[FD_ACCEPT_BIT]);
40             break;
41         }
42         
43         //FD_ACCEPT事件发生后,则进行后续处理
44         accept=accept(Socket[index_WSA_WAIT_EVENT_0],NULL,NULL);
45         
46         //查看事件对象的数目
47         if(eventTotal>WSA_MAXIMUM_WAIT_EVENTS)
48         {
49             printf("too many connections
");
50             closesocket(accept);
51             break;
52         }
53         
54         //再次创建事件,再次进行上述操作,进行循环
55         newEvent=WSACreateEvent();
56         
57         WSAEventSelect(listen, newEvent, FD_READ|FD_WRITE | FD_CLOSE);
58         
59         event[eventTotal]=newEvent;
60         Socket[eventTotal]=accept;
61         eventTotal++;
62         printf("socket %d connected
",accept);
63     }
64 
65     //FD_READ事件的处理
66     //雷同于FD_ACCEPT网络事件的处理
67     if(networkEvents.lNetworkEvents&FD_READ)
68     {
69         if(networkEvents.iErrorCode[FD_READ_BIT]!=0)
70         {
71             printf("FD_READ failed with error %d
",networkEvents.iErrorCode[FD_READ_BIT]);
72             break;
73         }
74         //读取数据
75         recv(Socket[index-WSA_WAIT_EVENT_0],buf,sizeof(buf),0);
76     }
77 
78     //接下来其他网络事件的处理同上,但是需要注意一点,事件选择模型是基于窗口程序的,并且需要消息发送,只是这部分代码为给出而已
79     
80 }
81 
82 
83 
84     
View Code