事件选择模型

windows处理用户行为的两种方式

消息机制

核心:消息队列

处理过程:所有的用户操作,比如点鼠标,按键盘,对软件进行的各种操作……等等,所有操作均依次按顺序被记录,装进一个队列。不同的操作对应着不同的编号。

特点:消息队列由操作系统维护,用户进行操作,然后把消息读取出来,分类处理。有先后顺序。动态的。

异步选择模型就是基于这个消息的

事件机制

核心:事件集合

处理过程:根据需求我们为用户的特定操作绑定一个事件,事件由我们自己调用API创建,需要多少创建多少。

将时间投递给系统,系统就帮我们监视着,所以不能无限创建,太多了系统运行就卡了。

如果操作发生了,比如用户按鼠标了,那么对应的事件就会被置成有信号,也就是类似1变2,用个数来标记。

直接获取到有信号的时间进行处理。

特点:所有时间都是咱们自己定义的,系统只是帮咱们标记有无信号。无序

事件选择模型,就是应用这个。

事件选择

整体类似于select

  1. 创建一个事件对象(变量),WSACreateEvent

  2. 为每一个事件对象绑定socket以及操作accept,read,close…..

    投递给系统让其监管,WSAEventSelect

  3. 查看事件是否有信号,WSAWaitForMultipleEvents

  4. 有信号就分类处理,WSAEnumNetworkEvents

1
2
3
4
5
6
7
8
网路头文件 网络库
打开网络库
校验版本
创建SOCKET
绑定地址与端口
开始监听

事件选择

创建一个事件对象

1
WSAEVENT WSAAPI WSACreateEvent();

1
WSAEVENT eventServer = WSACreateEvent();
1
2
3
4
5
6
7
8
9
成功-返回一个事件
失败-返回WSA_INVALID_EVENT(无效的事件对象)
if (eventServer == WSA_INVALID_EVENT)
{
int a = WSAGetLastError();
closesocket(socketServer);
WSACleanup();
return 0;
}

就是定义了一个事件类型。

HANDLE(void *通用类型指针),句柄,句柄的本质是ID,内核对象,唯一的标识符。

内核对象内核对象是系统提供的用户模式下代码与内核模式下代码进行交互的基本接口(百度百科)。

当调用一个用于创建内核对象的函数时,该函数就返回一个用于标识该对象的**句柄**

1
2
3
4
5
6
7
8
9
10
11
内核对象
由系统在内核申请
由操作系统访问
我们不能定位其内容,也不能修改
void* 通用类型指针
对内核的保护,对规则的保护,从而使操作系统有序的平稳的,
有效的运行,而不会随便出问题
调用函数创建,调用函数释放
如果我们没有调用释放,那么他可能就一直存在于内核,
造成内核内存泄漏, 这种只能重启电脑
内核对象有哪些 socket Kernel Objects

关闭/释放事件句柄

不用就要释放

1
2
3
4
BOOL WSAAPI WSACloseEvent
(
WSAEVENT hEvent
);

1
WSACloseEvent(eventServer);

指定事件主动置成无信号的

1
2
3
4
BOOL WSAAPI WSAResetEvent
(
WSAEVENT hEvent
);

指定事件主动置成有信号的

1
2
3
4
BOOL WSAAPI WSASetEvent
(
WSAEVENT hEvent
);

绑定并投递

1
2
3
4
5
int WSAAPI WSAEventSelect(
SOCKET s,
WSAEVENT hEventObject,
long INetworkEvents
);

功能

给事件绑上socket与操作码,并投递给操作系统。

参数1

被绑定的socket,最终每个socket都会被绑定一个事件

参数2

事件对象,逻辑,就是将参数1和参数2绑定在一起

参数3

具体事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
FD_ACCEPT
有客户端链接,与服务器socket绑定
FD_READ
有客户端发来消息,与客户端socket绑定,可多个属性并列使用。
FD_CLOSE
客户端下线,与客户端绑定,包含强制下线
FD_WRITE
可以给客户端发信,与客户端socket绑定,会在accept之后立即主动产生该信号。可以说明,客户端连接成功。即可随时send
FD_CONNECT
用在客户端上,给服务器绑定这个。
0
取消事件绑定。
FD_OOB
带外数据,一般不使用。
FD_QOS
套接字服务质量状态发生变化。动态变化。
FD_GROUP_QOS
保留——还没有对其赋值具体意义。
1
2
3
4
重叠I/O模型中
FD_ROUTING_ INTERFACE_CHANGE

FD_ADDRESS_ LIST_CHANGE

返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
成功——返回0
失败——返回SOCKET_ERROR
if (SOCKET_ERROR == WSAEventSelect(socketServer, SetEvent, FD_ACCEPT);)
{
int a = WSAGetLastError();
//释放事件句柄
WSACloseEvent(eventServer);
//释放所有socket
closesocket(socketServer);
//关闭网络库
WSACleanup();
return 0;
}

询问事件

1
2
3
4
5
6
7
DWORD WSAAPI WSAWaitForMultipleEvents(
DWORD cEvents,
const WSAEVENT *lphEvents,
BOOL fWaitAll,
DWORD dwTimeout,
BOOL fAlertable
);

功能

获取发生信号的事件。

参数1

事件个数,定义事件列表(数组)个数。

1
最大64  WSA_MAXIMUM_WAIT_EVENTS

可以变大,不像select模型,直接就能变大,因为select模型本身就是个数组,直接遍历即可, 比较直接,而事件选择是按照异步来投放,由系统进行管理,咱们就不能随便修改了,要按照规则来。

参数2

事件列表。

参数3

事件等待方式。

1
2
3
4
5
6
TRUE
所有事件都产生信号,才返回。
FALSE
任何一个事件产生信号,立即返回。
返回值减去WSA_WAIT_EVENT_0表示事件对象的索引,其状态导致函数返回。
如果在调用期间发出多个事件对象的信号,则这是信号事件对象的数组索引,其中所有信号事件对象的索引值最小。

参数4

超时间隔,以毫秒为单位。与select参数5意义相同。

1
2
3
123 等待123秒,超时返回WSA_WAIT_TIMEOUT
0 检查事件对象的状态并立即返回。不管有没有信号
WSA_INFINITE 等待,直到事件发生。

参数5

1
2
TRUE 重叠I/O模型使用
FALSE

返回值

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
数组下标的运算值,参数3为TRUE 所有时间均有信号
参数3位FALSE 返回值减去WSA_WAIT_EVENT_0==数 组中事件的下标
WSA_WAIT_IO_COMPLETION 参数5为TRUE,才会返回这个值 WSA_WAIT_TIMEOUT 超时了,continue即可。



while (1)
{
//询问
DWORD nRes = WSAWaitForMultipleEvents(esSet.count, esSet.eventall, FALSE,WSA_INFINITE, FALSE);
if (nRes == WSA_WAIT_FAILED)
{
printf("错误码%d\n", WSAGetLastError());
break;
}


//超时使用
/*if (WSA_WAIT_TIMEOUT == nRes)
{
continue;
}*/


DWORD nIndex = nRes - WSA_WAIT_EVENT_0;

}

列举事件

1
2
3
4
5
int WSAAPI WSAEnumNetworkEvents(
SOCKET s,
WSAEVENT hEventObject,
LPWSANETWORKEVENTS lpNetworkEvents
);

获取事件类型,并将事件上的信号重置,accept,recv,close等

1
2
3
4
5
6
7
WSANETWORKEVENTS NetworkEvents;
if(SOCKET_ERROR==WSAEnumNetworkEvents(esSet.sockall[nIndex], esSet.eventall[nIndex], &NetworkEvents))
{
int a = WSAGetLastError();
printf("错误码:%d\n", a);
break;
}

参数1

对应的socket

参数2

对应的事件

参数3

触发的事件类型在这里装着。是一个结构体指针。

1
2
3
4
5
6
7
8
struct _WSANETWORKEVENTS
{
long lNetworkEvents;
//具体操作,一个信号可能包含两个信息,以按位或的形式存在
int iErrorCode[FD_MAX_EVENTS];
//错误码数组,FD_ACCEPT事件错误码在FD_ACCEPT_BIT下标里
//没有错误,对应的就是0
}

返回值

1
2
成功——返回0
失败——返回SOCKET_ERROR

事件分类处理逻辑

1
2
3
4
5
6
7
8
9
10
if (lpNetworkEvents->lNetworkEvents & FD_ACCEPT)
{
if (lpNetworkEvents->iErrorCode[FD_ACCEPT_BIT] == 0)
{
//接受链接
//创建事件
//投放事件
//元素增加
}
}
1
2
switch不行有大bug
else if 不太行,有小bug

有序处理

优化

1
2
3
4
5
6
7
8
9
for (i = Index; i < EventTotal; i++)
{
Index = WSAWaitForMultipleEvents(1, &EventArray[i], TRUE, 1000, FALSE);
if ((Index != WSA_WAIT_FAILED) && (Index != WSA_WAIT_TIMEOUT))
{
WSAEnumNetworkEvents(SocketArray[i], EventArray[i], &NetworkEvents);
//分类处理
}
}

增加事件数量

当前代码是一组一组投递,一组是64个,由WSAWaitForMultipleEvents这个函数决定。

1
2
3
增加事件数量————一个一个投,一个大数组就行了,
————一组一组投,单线程,一组一组顺序处理就好了。
创建多线程,每个线程处理一个事件表,最大是64

完整代码

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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
#define _CRT_SECURE_NO_WARNINGS
//#define FD_SETSIZE 128
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <Winsock2.h>
#include <string.h>
#pragma comment(lib, "Ws2_32.lib")

struct fd_es_set
{
unsigned short count;
SOCKET sockall[WSA_MAXIMUM_WAIT_EVENTS];
WSAEVENT evnetall[WSA_MAXIMUM_WAIT_EVENTS];
};

struct fd_es_set esSet;

BOOL WINAPI fun(DWORD dwCtrlType)
{
switch (dwCtrlType)
{
case CTRL_CLOSE_EVENT:
//释放所有socket
for (int i = 0; i < esSet.count; i++)
{
closesocket(esSet.sockall[i]);
WSACloseEvent(esSet.evnetall[i]);
}

break;
}

return TRUE;
}

int main(void)
{
SetConsoleCtrlHandler(fun, TRUE);

WORD wdVersion = MAKEWORD(2, 2); //2.1 //22
//int a = *((char*)&wdVersion);
//int b = *((char*)&wdVersion+1);
WSADATA wdScokMsg;
//LPWSADATA lpw = malloc(sizeof(WSADATA));// WSADATA*
int nRes = WSAStartup(wdVersion, &wdScokMsg);

if (0 != nRes)
{
switch (nRes)
{
case WSASYSNOTREADY:
printf("重启下电脑试试,或者检查网络库");
break;
case WSAVERNOTSUPPORTED:
printf("请更新网络库");
break;
case WSAEINPROGRESS:
printf("请重新启动");
break;
case WSAEPROCLIM:
printf("请尝试关掉不必要的软件,以为当前网络运行提供充足资源");
break;
}
return 0;
}

//校验版本
if (2 != HIBYTE(wdScokMsg.wVersion) || 2 != LOBYTE(wdScokMsg.wVersion))
{
//说明版本不对
//清理网络库
WSACleanup();
return 0;
}

SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//int a = WSAGetLastError();
if (INVALID_SOCKET == socketServer)
{
int a = WSAGetLastError();
//清理网络库
WSACleanup();
return 0;
}

struct sockaddr_in si;
si.sin_family = AF_INET;
si.sin_port = htons(12345);
si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
//int a = ~0;
if (SOCKET_ERROR == bind(socketServer, (const struct sockaddr*)&si, sizeof(si)))
{
//出错了
int a = WSAGetLastError();
//释放
closesocket(socketServer);
//清理网络库
WSACleanup();
return 0;
}

if (SOCKET_ERROR == listen(socketServer, SOMAXCONN))
{
//出错了
int a = WSAGetLastError();
//释放
closesocket(socketServer);
//清理网络库
WSACleanup();
return 0;
}



//创建事件
WSAEVENT eventServer = WSACreateEvent();
if (WSA_INVALID_EVENT == eventServer)
{
//出错了
int a = WSAGetLastError();
//释放
closesocket(socketServer);
//清理网络库
WSACleanup();
return 0;
}

if (SOCKET_ERROR == WSAEventSelect(socketServer, eventServer, FD_ACCEPT))
{
//出错了
int a = WSAGetLastError();
//释放事件句柄
WSACloseEvent(eventServer);
//释放所有socket
closesocket(socketServer);
//清理网络库
WSACleanup();
return 0;
}

//装进去
esSet.evnetall[esSet.count] = eventServer;
esSet.sockall[esSet.count] = socketServer;
esSet.count++;

while (1)
{

//询问
DWORD nRes = WSAWaitForMultipleEvents(esSet.count, esSet.evnetall, FALSE, WSA_INFINITE, FALSE);
if (WSA_WAIT_FAILED == nRes)
{
int a = WSAGetLastError();
//出错了
printf("错误码:%d\n", a);
break;
}
//超时使用
//if (WSA_WAIT_TIMEOUT == nRes)
//{
// continue;
//}
//
DWORD nIndex = nRes - WSA_WAIT_EVENT_0;

//得到下标对应的具体操作
WSANETWORKEVENTS NetworkEvents;
if (SOCKET_ERROR == WSAEnumNetworkEvents(esSet.sockall[nIndex], esSet.evnetall[nIndex], &NetworkEvents))
{
int a = WSAGetLastError();
//出错了
printf("错误码:%d\n", a);
break;
}

if (NetworkEvents.lNetworkEvents & FD_ACCEPT)
{
if (0 == NetworkEvents.iErrorCode[FD_ACCEPT_BIT])
{
//正常处理
SOCKET socketClient = accept(esSet.sockall[nIndex], NULL, NULL);
if (INVALID_SOCKET == socketClient)
{
continue;
}

//创建事件对象
WSAEVENT wsaClientEvent = WSACreateEvent();
if (WSA_INVALID_EVENT == wsaClientEvent)
{
closesocket(socketClient);
continue;
}

//投递给系统
if (SOCKET_ERROR == WSAEventSelect(socketClient, wsaClientEvent, FD_READ | FD_CLOSE | FD_WRITE))
{
closesocket(socketClient);
WSACloseEvent(wsaClientEvent);
continue;
}

//装进结构体
esSet.sockall[esSet.count] = socketClient;
esSet.evnetall[esSet.count] = wsaClientEvent;
esSet.count++;

printf("accept event\n");
}
else
{
continue;
}
}

if (NetworkEvents.lNetworkEvents & FD_WRITE)
{
if (0 == NetworkEvents.iErrorCode[FD_WRITE_BIT])
{
//初始化

if (SOCKET_ERROR == send(esSet.sockall[nIndex], "connect success", strlen("connect success"), 0))
{
int a = WSAGetLastError();
printf("send faild, error code:%d\n", a);
continue;
}
printf("write event\n");
}
else
{
printf("socket error code:%d\n", NetworkEvents.iErrorCode[FD_WRITE_BIT]);
continue;
}
}

if (NetworkEvents.lNetworkEvents & FD_READ)
{
if (0 == NetworkEvents.iErrorCode[FD_READ_BIT])
{
char strRecv[1500] = { 0 };
if (SOCKET_ERROR == recv(esSet.sockall[nIndex], strRecv, 1499, 0))
{
int a = WSAGetLastError();
printf("recv faild, error code:%d\n", a);
continue;
}
printf("recv data: %s\n", strRecv);
}
else
{
printf("socket error code:%d\n", NetworkEvents.iErrorCode[FD_READ_BIT]);
continue;
}
}

if (NetworkEvents.lNetworkEvents & FD_CLOSE)
{
/*if (0 == NetworkEvents.iErrorCode[FD_CLOSE_BIT])
{

}
else
{

}
WSAECONNABORTED;*/

//打印
printf("client close\n");
printf("client force out: %d\n", NetworkEvents.iErrorCode[FD_CLOSE_BIT]);
//清理下线的客户端 套接字 事件
//套接字
closesocket(esSet.sockall[nIndex]);
esSet.sockall[nIndex] = esSet.sockall[esSet.count - 1];
//事件
WSACloseEvent(esSet.evnetall[nIndex]);
esSet.evnetall[nIndex] = esSet.evnetall[esSet.count - 1];

//数量减一
esSet.count--;
}


}

for (int i = 0; i < esSet.count; i++)
{
closesocket(esSet.sockall[i]);
WSACloseEvent(esSet.evnetall[i]);
}

//清理网络库
WSACleanup();

system("pause");
return 0;
}

对比select模型

事件选择模型——异步

select模型——同步