异步选择模型

逻辑

核心:消息队列,操作系统为每个窗口创建一个消息队列,并且维护,我们想要使用消息队列,那就要创建一个窗口。

第一步:将我们的socket,绑定在一个消息上,并且投递给操作系统。

WSAAsyncSelect

第二步:取消息分类处理,

该模型只能用于windows,windows处理用户操作的核心就是消息队列。但是思想是通用的。

窗口

第一步:创建窗口结构体——WNDCLASSEX

第二步:注册窗口结构体——RegisterClassEx

第三步:创建窗口——CreateWindowEx

第四步:显示窗口——ShowWindow

第五步:消息循环——GetMessage

​ ——TranslateMessage

​ ——DispatchMessage

第六步:回调函数

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
#include<windows.h>//窗口
LRESULT CALLBACK WinBackProc(HWND hwnd, UINT msgID, WPARAM wparaw, LPARAM lparam);


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPreInstance, LPSTR lpCmdLine, int nShowCmd)
{
//创建窗口结构体
WNDCLASSEX wc;
wc.cbClsExtra = 0;//窗口结构体额外的一块空间,一般用不到
wc.cbSize = sizeof(WNDCLASSEX);
wc.cbWndExtra = 0;
wc.hbrBackground = NULL;
wc.hCursor = NULL;
wc.hIcon = NULL;
wc.hIconSm = NULL;
wc.hInstance = hInstance;

wc.lpfnWndProc = WinBackProc;

wc.lpszClassName = L"mYwinDows";
wc.lpszMenuName = NULL;
wc.style = CS_HREDRAW | CS_VREDRAW;

//注册窗口结构体
RegisterClassEx(&wc);//窗口类变量地址
//创建窗口
//窗口句柄
HWND hwnd = CreateWindowEx(WS_EX_OVERLAPPEDWINDOW, L"mYwinDows", L"WuHu", WS_OVERLAPPEDWINDOW, 200, 200, 600, 400, NULL, NULL, hInstance, NULL);
if (NULL == hwnd)
{
return 0;
}

//显示窗口
ShowWindow(hwnd, nShowCmd);

//更新窗口
UpdateWindow(hwnd);

//消息循环!-窗口上的任何操作都会产生消息,然后被装进消息队列中。
//只要窗口在,就得不停的在窗口上取消息

//消息结构体——装消息
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))//点击关闭窗口,GetMessage会返回0,循环退出,两个范围填0——接收所有消息
{
//翻译消息
TranslateMessage(&msg);
//分发消息,到具体位置分类处理
DispatchMessageW(&msg);
}

return 0;
}

//回调函数
LRESULT CALLBACK WinBackProc(HWND hwnd, UINT msgID, WPARAM wparaw, LPARAM lparam)
{
switch (msgID)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProc(hwnd, msgID, wparaw, lparam);
}

服务端

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

异步选择

异步选择

1
2
3
4
5
6
int WSAAsyncSelect(
SOCKET s,
HWND hWnd,
u_int wMsg,
long lEvent
);

1
2
3
4
5
6
7
if (SOCKET_ERROR == WSAAsyncSelect(socketServer, hwnd, UM_ASYNCSELECTMSG, FD_ACCEPT))
{
int a = WSAGetLastError();
closesocket(socketServer);
WSACleanup();
return 0;
}

功能

绑定事件与socket并且投递出去。

参数1

服务器socket

参数2

窗口句柄,绑定到哪个窗口上。

本质:就是窗口的ID,编号。

参数3

消息编号,自定义消息。

本质:就是一个数。

参数4

消息类型。跟WSASelectEvent一模一样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
FD_ACCEPT
有客户端连接,与服务器socket绑定
FD_WRITE
可以给客户端发信,与客户端socket绑定,会在accept后立即主动产生该信号,可以说明,客户端连接成功,即可随时send
FD_READ
有客户端发来消息,与客户端socket绑定,可多个属性并列 用 |
FD_CLOSE
客户端下线了,与客户端socket绑定,包含强制下线,正常下线。
FD_CONNECT
客户端一方,给服务器绑定这个
0
取消事件监视
WSAAsyncSelect(.....FD_ACCEPT | FD_READ);投递多个消息用按位或
WSAAsyncSelect(....0,0);取消消息托管
FD_OOB
带外数据,一般不使用
FD_QOS
套接字服务质量状态发生变化消息通知
WSAIoctl,得到服务质量信息
char strOut[2048] = { 0 };
DWORD nLen = 2048;
WSAIoctl(socketServer, SIO_QOS, 0, 0, strOut, nLen, & nLen, NULL, NULL);
FD_GROUP_QOS
windows保留
1
2
3
4
5
6
7
8
9
10
11
重叠I/O模型中
FD_ROUTING_ INTERFACE_CHANGE
想要接收指定目标的路由接口更改通知。
数据到达对方的所经过的线路改变了,由于是动态优化选择
要通过此函数WSAIoctl注册之后,才可以
SIO_ROUTING_ INTERFACE_CHANGE
FD_ADDRESS_ LIST_CHANGE
想要接收套接字地址族的本地地址列表更改通知。
要通过此函数WSAIoctl注册之后,才可以有效
服务器链接了很多客户端,服务器就记录着所有的客户端的地址信 息,就相当于一个列表,发生变化,会得到相关的信号。
SIO_ADDRESS_ LIST_CHANGE

返回值

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

完整代码

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
#define _CRT_SECURE_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS

#define UM_ASYNCSELECTMSG WM_USER+1//这个数以上的数系统还没有使用,这样不会引起冲突

//#include<windows.h>//窗口
#include<WinSock2.h>
#include<stdlib.h>
#include<stdio.h>
#pragma comment(lib,"ws2_32.lib")


LRESULT CALLBACK WinBackProc(HWND hwnd, UINT msgID, WPARAM wparaw, LPARAM lparam);

//SOCKET数组——用于释放
#define MAX_SOCK_COUNT 1024
SOCKET g_sockall[MAX_SOCK_COUNT];
//记住socket个数
int g_count = 0;


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPreInstance, LPSTR lpCmdLine, int nShowCmd)
{
//创建窗口结构体
WNDCLASSEX wc;
wc.cbClsExtra = 0;//窗口结构体额外的一块空间,一般用不到
wc.cbSize = sizeof(WNDCLASSEX);
wc.cbWndExtra = 0;
wc.hbrBackground = NULL;
wc.hCursor = NULL;
wc.hIcon = NULL;
wc.hIconSm = NULL;
wc.hInstance = hInstance;

wc.lpfnWndProc = WinBackProc;

wc.lpszClassName = "mYwinDows";
wc.lpszMenuName = NULL;
wc.style = CS_HREDRAW | CS_VREDRAW;

//注册窗口结构体
RegisterClassEx(&wc);//窗口类变量地址
//创建窗口
//窗口句柄
HWND hwnd = CreateWindowEx(WS_EX_OVERLAPPEDWINDOW, "mYwinDows", "WuHu", WS_OVERLAPPEDWINDOW, 200, 200, 600, 400, NULL, NULL, hInstance, NULL);
if (NULL == hwnd)
{
return 0;
}

//显示窗口
ShowWindow(hwnd, nShowCmd);

//更新窗口
UpdateWindow(hwnd);


//**********************************************************************

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

if (HIBYTE(wdSockMsg.wVersion) != 2 || LOBYTE(wdSockMsg.wVersion) != 2)
{
printf("版本错误");
int a = WSAGetLastError();
WSACleanup();
return 0;
}

SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (socketServer == INVALID_SOCKET)
{
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");

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;
}

//**********************************************************************

if (SOCKET_ERROR == WSAAsyncSelect(socketServer, hwnd, UM_ASYNCSELECTMSG, FD_ACCEPT))
{
int a = WSAGetLastError();
closesocket(socketServer);
WSACleanup();
return 0;
}

g_sockall[g_count] = socketServer;
g_count++;


//消息循环!-窗口上的任何操作都会产生消息,然后被装进消息队列中。
//只要窗口在,就得不停的在窗口上取消息
//消息结构体——装消息
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))//点击关闭窗口,GetMessage会返回0,循环退出,两个范围填0——接收所有消息
{
//翻译消息
TranslateMessage(&msg);
//分发消息,到具体位置分类处理
DispatchMessageW(&msg);
}

//关闭socket
for (int i = 0; i < g_count; i++)
{
closesocket(g_sockall[i]);
}
WSACleanup();
return 0;
}



int x = 0;//x坐标是左侧竖着的

//回调函数
//一次取一个
LRESULT CALLBACK WinBackProc(HWND hwnd, UINT msgID, WPARAM wparaw, LPARAM lparam)
{
HDC hdc = GetDC(hwnd);
//分类处理
switch (msgID)
{
case UM_ASYNCSELECTMSG:
{
//MessageBox(NULL,L"有信号啦",L"提示",MB_OK);

//获取socket
SOCKET sock = (SOCKET)wparaw;
//获取消息
if (HIWORD(lparam) != 0)
{
if (WSAECONNABORTED == HIWORD(lparam))
{
TextOut(hdc, 0, x, "close", strlen("close"));
x += 15;
//关闭socket上的消息
WSAAsyncSelect(sock, hwnd, 0, 0);//后两个参数置零就是把这个socket上的消息取消了。
//关闭socket
closesocket(sock);
//记录数组中删除该socket
for (int i = 0; i < g_count; i++)
{
if (sock == g_sockall[i])
{
g_sockall[i] = g_sockall[g_count - 1];
g_count--;
break;
}
}
}
break;
}
//具体消息
switch (LOWORD(lparam))
{
case FD_ACCEPT:
{
TextOut(hdc,0, x, "accept", strlen("accept"));
x += 15;
SOCKET socketClient = accept(sock, NULL, NULL);
//如果是一个无效的SOCKET
if (socketClient == INVALID_SOCKET)
{
int a = WSAGetLastError();
break;
}
//将客户端投递给消息队列
if (SOCKET_ERROR == WSAAsyncSelect(socketClient, hwnd, UM_ASYNCSELECTMSG, FD_READ | FD_WRITE | FD_CLOSE))
{
int a = WSAGetLastError();
closesocket(socketClient);
break;
}

//记录
g_sockall[g_count] = socketClient;
g_count++;
}
break;
case FD_READ:
{
//走read,肯定传过来的是客户端的socket

TextOut(hdc, 0, x, "read", strlen("read"));
char str[1024] = { 0 };
if (SOCKET_ERROR == recv(sock, str, 1023, 0))
{
break;
}
TextOut(hdc, 60, x, str, strlen(str));
x += 15;
}
break;
case FD_WRITE:
//当客户端成功连接上服务器后,他会先后产生两个消息,
//accept和write,同事件选择模型
//与选择模型逻辑相同,事件选择模型基于事件,异步选择模型基于消息队列
//队列是有序的,理论起来操作更方便一些。

//send也可以写在accept中,以做提示
TextOut(hdc, 0, x, "write", strlen("write"));
x += 15;
break;
case FD_CLOSE:
TextOut(hdc, 0, x, "close", strlen("close"));
x += 15;
//关闭socket上的消息
WSAAsyncSelect(sock, hwnd, 0, 0);
//后两个参数置零就是把这个socket上的消息取消了。
//关闭socket
closesocket(sock);
//记录数组中删除该socket
for (int i = 0; i < g_count; i++)
{
if (sock == g_sockall[i])
{
g_sockall[i] = g_sockall[g_count - 1];
g_count--;
break;
}
}

break;
}
break;
}
case WM_CREATE://初始化-只执行一次
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}

ReleaseDC(hwnd,hdc);
return DefWindowProc(hwnd, msgID, wparaw, lparam);
}



要点

1
2
3
4
5
6
7
8
客户端socket
(SOCKET)wParam
产生的错误码
HIWORD(lParam)
具体的消息种类
LOWORD(lParam)
窗口上打印数据
textout

优化

每个窗口维护一定的消息,然后创建多线程,每个线程一个窗口,每个窗口投递一定数量的客户端。

问题

在一次处理过程中,客户端产生多次send,服务器会产生多次接收消息,第一次接收消息会收完所有信息。

总结

事件选择模型和异步选择模型是解决select模型中select()同步阻塞的问题的。

重叠I/O模型和完成端口模型将recv(send)操作变成异步的 ,从而这个网络模型没有阻塞。全都顺利执行下来,且执行效率非常高。