上一篇文章总结了WSA消息模型的学习,总体来说就是将网络事件与自定义的消息绑定起来,在消息循环中像处理平常的消息事件一样去处理网络事件。这一篇总结下WSA事件模型的学习。整体来说WSA事件模型和WSA消息模型非常相似。首先,他们都是异步的,其次,他们都是将网络事件与某一对象绑定起来,当有网络事件发生时,通过这一对象通知程序进行处理。在WSA消息中,这个对象是自定义的消息,而在WSA事件模型中,这个对象是事件。这也是把这种模型称之为WSA事件模型的原因。
在WSA事件模型中,要使用WSAEventSelect()函数创建一个事件,并把创建的事件和相应的套接字进行网络事件注册。注册完后,当有连接到来或是操作系统已经把数据准备好时,就以事件的形式通知应用程序。应用程序接收到这个事件后,根据事件的类型,进行不同的处理。
在这种模型中需要用到的主要函数是 WSAEventSelect(),WSACreateEvent(),WSAResetEvent(),WSAWaitForMultipleEvents(),WSAEnumNetworkEvents。相较之前几种模型的API数量,这次的有点多,也略显麻烦。
下面看下WSAEventSelect()函数的定义。
1
2
3
4
5
|
int WSAEventSelect (
SOCKET s, //套接字
WSAEVENT hEventObject, //事件对象句柄
long lNetworkEvents //应用程序感兴趣的网络事件集合
);
|
这个函数调用后会自动设置套接字为非阻塞状态,要是想把套接字重新设置成阻塞状态,需要将lNetworkEvents参数设为0,并重新调用 WSAEventSelect()函数。
对于第三个参数的类型,在上一篇博客中已经写了,这里复制过来。
对于参数三可选的值有:(来自MSDN)
1
2
3
4
5
6
7
8
9
10
11
|
Value Meaning
FD_READ 想要接收可读的通知
FD_WRITE 想要接收可写的通知
FD_OOB 想要接收带外数据到来的通知
FD_ACCEPT 想要接收有到来连接的通知
FD_CONNECT 想要接收一次连接完成或多点jion操作完成的通知
FD_CLOSE 想要接收套接字关闭的通知
FD_QOS 想要接收套接字服务质量发生变化的通知
FD_GROUP_QOS 想要接收套接字组服务质量发生变化的通知
FD_ROUTING_INTERFACE_CHANGE 想要在指定方向上,与路由接口发生变化的通知
FD_ADDRESS_LIST_CHANGE 想要接收针对套接字的协议家族,本地地址列表发生变化的通知
|
对于上述函数的第二参数事件句柄需要由函数 WSACreateEvent() 创建。该函数的定义为:
1
|
WSAEVENT WSACreateEvent(void);
|
该函数调用成功后就会返回相应的事件句柄,如果错误就会返回WSA_INVALID_EVENT。
对于WSA事件,它有两种工作状态和两种工作模式。两种工作状态分别是“有信号的”和“无信号的”,两种工作模式分别是“人工重置”和“自动重置”。这个函数创建的事件的初始属性是,人工重置,而且是无信号的。
在使用WSAEventSelect()函数绑定事件和套接字后,当有希望接收的网络事件到来的时候,绑定的事件就会由无信号状态转变成有信号状态。这时候应用程序就可以根据这点来处理相应的网络事件,当处理完相应的网络事件后,因为默认是人工重置,所以需要手动将该事件设置成无信号状态。
设置事件从有信号状态到有信号状态的函数是WSAResetEvent()函数。定义如下:
1
2
3
|
BOOL WSAResetEvent(
WSAEVENT hEvent
);
|
函数调用成功后返回真,调用失败返回假。
当程序要关闭时,需要关闭事件对象,以释放相应的资源。关闭事件的函数是WSACloseEvent(),定义如下:
1
2
3
|
BOOL WSACloseEvent(
WSAEVENT hEvent
);
|
调用成功后返回真,失败返回假。
对于上述的当事件从无信号状态转变为有信号状态时,应用程序会得知这一变化,并进行相应的处理。这里应用程序是通过函数 WSAWaitForMultipleEvents() 函数得知的。该函数定义如下:
1
2
3
4
5
6
7
|
DWORD WSAWaitForMultipleEvents(
DWORD cEvents, //等待事件句柄的数量,最少为1个,最多是64个,这也是这种模型的限制。
const WSAEVENT FAR *lphEvents, //指向等待事件集合的指针,该参数和上一个参数构成了事件数组。
BOOL fWaitAll, //是否等待所有事件都为有信号状态时才返回
DWORD dwTimeOUT, //设置超时时间,时间单位是毫秒。如果设置为WSA_INFINITE,就表示无限等待,直到满足fWaitAll的条件
BOOL fAlertable //这个参数说明当完成例程在系统队列中排队等待执行时,该函数是否返回。这个参数主要用于重叠IO模型,这里设置为FALSE
);
|
一般情况下,在这么多事件中,我们是当一个事件从无信号变为有信号状态时,就让该函数返回,然后我们就需要知道是哪一个事件从无信号状态变为有信号状态了。这一点可以通过函数的返回值来判断,具体方法是 返回值减去WSA_WAIT_EVENT_0所得的值就是有信号事件在数组中对应的下标。而WSA_WAIT_EVENT_0这个宏的代表的值是0。
而如果函数超时了,就会返回WSA_WAIT_TIMEOUT
而如果函数调用失败,就会返回 WSA_WAIT_FAILED
到现在,API介绍的差不多了,仔细想一下,会发现还有一点不对,就是当事件从无信号状态变为有信号状态的时候,我们知道是某个连接发生了相应网络事件,但是我们从哪里得知发生的具体网络事件是什么呢?
这里就需要 WSAEnumNetworkEvents() 函数。定义如下:
1
2
3
4
5
|
int WSAEnumNetworkEvents (
SOCKET s, //发生网络事件的套接字句柄
WSAEVENT hEventObject, //被重置的事件对象(可选),若该参数有值,则会把相应的事件对象从有信号状态设置成无信号状态
LPWSANETWORKEVENTS lpNetworkEvents //一个指向WSANETWORKEVENTS结构体的指针,这个结构体包含了具体的网络事件和相关的错误代码。
);
|
调用成功返回0,失败返回 SOCKETS_ERROR
下面是 WSANETWORKEVENTS 结构体的定义:
1
2
3
4
|
typedef struct _WSANETWORKEVENTS {
long lNetworkEvents; //发生的具体网络事件,可能是多个网络事件的组合
int iErrorCode[FD_MAX_EVENTS]; //包含网络事件错误代码的数组
} WSANETWORKEVENTS, FAR * LPWSANETWORKEVENTS;
|
若需要检查相应的网络事件是否发生错误,需要用网络事件错误标识符对iErrorCode数组进行索引。标识符的名称为响应的网络事件后加上"_BIT",比如FD_READ网络事件的错误标识符就是FD_READ_BIT。
上述讲解了该模型需要的API,下面看代码实例。(win7,VC6.0)
客户端代码同文章一
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
|
#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib,"ws2_32.lib")
int main(){
//初始化套接字
WSADATA wsaData;
WSAStartup(MAKEWORD(2,2),&wsaData);
SOCKET sListen=socket(AF_INET,SOCK_STREAM,0);
SOCKADDR_IN cAddr;
cAddr.sin_addr.S_un.S_addr=INADDR_ANY;
cAddr.sin_port=htons(5000);
cAddr.sin_family=AF_INET;
//绑定监听
bind(sListen,(SOCKADDR*)&cAddr,sizeof(SOCKADDR));
listen(sListen,5);
//创建事件对象
WSAEVENT Event=WSACreateEvent();
//为监听套接字注册网络事件,同时将事件对象与网络事件关联起来
if(SOCKET_ERROR==WSAEventSelect(sListen,Event,FD_ACCEPT | FD_CLOSE)){
printf("WSAEventSelect error with code is:%d\n",WSAGetLastError());
return 0;
}
//事件的总共数量
int EventSum=0;
//创建SOCKET和EVENT数组,这里WSA_MAXIMUM_WAIT_EVENTS宏代表64,所以这种模型每个线程最多只能管理64个套接字
WSAEVENT arrayEvent[WSA_MAXIMUM_WAIT_EVENTS];
SOCKET arraySocket[WSA_MAXIMUM_WAIT_EVENTS];
//将创建的事件对象和与之绑定的套接字分别添加进相应的数组
arrayEvent[EventSum]=Event;
arraySocket[EventSum]=sListen;
EventSum++;
while(1){
//该函数等待网络事件的发生
int dwIndex=WSAWaitForMultipleEvents(EventSum,arrayEvent,false,WSA_INFINITE,FALSE);
//判断是否发生错误
if(dwIndex==WSA_WAIT_FAILED){
printf("WSAWaitForMultipleEvents failed\n");
continue;
}
//根据函数返回的值获取相关的SOCKET和EVENT
WSAEVENT nowEvent=arrayEvent[dwIndex-WSA_WAIT_EVENT_0];
SOCKET nowSocket=arraySocket[dwIndex-WSA_WAIT_EVENT_0];
//创建WSANETWORKEVENTS结构
WSANETWORKEVENTS networkEvents;
//调用WSAEnumNetworkEvents函数获取发生在该套接字上的具体网络事件,
//并且设置第二个参数为当前事件对象,该函数会自动将事件对象重置为无信号状态
if(0==WSAEnumNetworkEvents(nowSocket,nowEvent,&networkEvents)){
//FD_ACCEPT事件,将lNetworkEvents变量与FD_ACCEPT进行位与操作,如果真,则表示发生了FD_ACCEPT网络事件
if(networkEvents.lNetworkEvents & FD_ACCEPT){
//创建相关变量获取客户端套接字
SOCKADDR_IN acceptAddr;
int accept_len=sizeof(SOCKADDR);
SOCKET sAccept;
//接收客户端套接字
sAccept=accept(nowSocket,(SOCKADDR*)&acceptAddr,&accept_len);
//将该套接字注册,并设置网络事件为可读与关闭以及可写
arraySocket[EventSum]=sAccept;
arrayEvent[EventSum]=WSACreateEvent();
WSAEventSelect(sAccept,arrayEvent[EventSum],FD_READ | FD_CLOSE | FD_WRITE);
//将总体事件对象个数加一
EventSum++;
}
//FD_READ网络事件,判断方法与FD_ACCEPT网络事件相同
else if(networkEvents.lNetworkEvents & FD_READ){
char recvBuf[1024];
char sendBuf[]="Get Message Success";
recv(arraySocket[dwIndex-WSA_WAIT_EVENT_0],recvBuf,sizeof(recvBuf),0);
printf("%s\n",recvBuf);
send(arraySocket[dwIndex-WSA_WAIT_EVENT_0],sendBuf,strlen(sendBuf)+1,0);
}
//FD_CLOSE网络事件,判断方法和上述相同
else if(networkEvents.lNetworkEvents & FD_CLOSE){
printf("A socket is closing\n");
//关闭相关套接字
closesocket(arraySocket[dwIndex-WSA_WAIT_EVENT_0]);
//关闭相关的事件对象
WSACloseEvent(arrayEvent[dwIndex-WSA_WAIT_EVENT_0]);
//将被关闭的套接字和被关闭的事件对象从数组中移除
for(int i=dwIndex-WSA_WAIT_EVENT_0;i<EventSum-1;i++){
arraySocket[i]=arraySocket[i+1];
arrayEvent[i]=arrayEvent[i+1];
}
//事件总数量减一
EventSum--;
}
//FD_WRITE网络事件
else if(networkEvents.lNetworkEvents & FD_WRITE){
printf("FD_WRITE event has happened!\n");
}
}
}
closesocket(sListen);
WSACleanup();
return 0;
}
|
运行截图如下: