异步选择模型 异步选择模型

这是我参与11月更文挑战的第二十五天,活动详情查看:2021最后一次更文挑战

异步选择模型

前言

掌握创建基本窗口的代码,以及回调函数的概念;掌握异步选择模型的通讯过程;掌握异步选择模型的代码实现。

内容和步骤

服务器端:

实现基本窗口功能:

1、创建窗口结构体:WNDCLASSEX(这一步不能少设置属性,否则会在第三步失败)

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
ini复制代码WNDCLASSEX wndc;

    wndc.cbClsExtra = 0;//窗口类额外数据,不用就写0

    wndc.cbSize = sizeof(WNDCLASSEX);//窗口类大小

    wndc.cbWndExtra = 0;//窗口额外数据,不用就写0

    wndc.hbrBackground = NULL;//用默认白色

    wndc.hCursor = NULL;//默认光标

    wndc.hIcon = NULL;//默认窗口图标

    wndc.hIconSm = NULL;//默认任务栏图标

    wndc.hInstance = hInstance;//少这个就不能创建成功

    wndc.lpfnWndProc = callBackProc;//回调函数名称,要和定义的回调函数名字一样,由系统调用

    wndc.lpszClassName = "emptywnd";//窗口类名

    wndc.lpszMenuName = NULL;//窗口菜单名称

    wndc.style = CS_HREDRAW | CS_VREDRAW;//https://docs.microsoft.com/zh-cn/windows/win32/winmsg/window-class-styles

2、注册窗口结构体:RegisterClassEx

1
2
3
4
5
6
ini复制代码int regid = RegisterClassEx(&wndc);
    if (regid == 0)//如果注册失败
    {
         int RegisterClassExerr = GetLastError();

    }

3、创建窗口:CreateWindowEx

1
2
3
4
5
6
7
8
9
10
11
12
ini复制代码HWND hWnd = CreateWindowEx(WS_EX_OVERLAPPEDWINDOW, "emptywnd", "窗口标题", WS_OVERLAPPEDWINDOW, 100, 100, 640, 480, NULL, NULL, hInstance, NULL);
    if (hWnd == NULL)//如果创建失败

    {

         int CreateWindowExerr = GetLastError();

         //MessageBox(0,"注册窗口失败", "提示", MB_OK);

         return 0;

    }

4、显示窗口:ShowWindow

1
scss复制代码howWindow(hWnd, SW_NORMAL);

5、网络通信功能, SOCKET初始化操作

1
2
3
4
5
ini复制代码WORD wdVersion = MAKEWORD(2, 2);

    int a = *((char*)&wdVersion);

    int b = *((char*)&wdVersion + 1);
5.1.打开网络库
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
arduino复制代码if (0 != nRes)

    {

         switch (nRes)

         {

         case WSASYSNOTREADY:

             printf("解决方案:重启。。。\n");

             break;

         case WSAVERNOTSUPPORTED:

             break;

         case WSAEINPROGRESS:

             break;

         case WSAEPROCLIM:

             break;

         case WSAEFAULT:

             break;

         }

         return 0;

 

    }
5.2.校验版本
1
2
3
4
5
6
7
8
9
10
11
scss复制代码if (2 != HIBYTE(wdScokMsg.wVersion) || 2 != LOBYTE(wdScokMsg.wVersion))

    {

         printf("版本有问题!\n");

         WSACleanup();

         return 0;

    }
5.3.创建SOCKET
1
ini复制代码SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
5.4.绑定地址与端口
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
scss复制代码if (SOCKET_ERROR == bind(socketServer, (const struct sockaddr*)&si, sizeof(si)))

    {
         int err = WSAGetLastError();//取错误码

         printf("服务器bind失败错误码为:%d\n", err);

         closesocket(socketServer);//释放

         WSACleanup();//清理网络库
         return 0;

    }

    printf("服务器端bind成功!\n");5.5.开始监听

if (SOCKET_ERROR == listen(socketServer, SOMAXCONN))

    {

         int err = WSAGetLastError();//取错误码

         printf("服务器监听失败错误码为:%d\n", err);

         closesocket(socketServer);//释放

         WSACleanup();//清理网络库

         return 0;

    }
    printf("服务器端监听成功!\n");
5.6.绑定消息和服务器SOCKET,并投递给操作系统
1
2
3
4
5
6
7
8
9
10
11
scss复制代码if (WSAAsyncSelect(socketServer, hWnd, UM_ASYNCSELECTMSG, FD_ACCEPT) == SOCKET_ERROR)//失败处理
    {

         int WSAAsyncSelecterr = WSAGetLastError();

         closesocket(socketServer);

         WSACleanup();

         return 0;
    }
6、消息循环:GetMessage、TranslateMessage、DispatchMessage
1
2
3
4
5
6
7
scss复制代码MSG msg;
    while (GetMessage(&msg, NULL, 0, 0))
    {
         TranslateMessage(&msg);//将消息转换为可识别的代号
         DispatchMessage(&msg);//分发消息,让回调函数来处理

    }
7、创建回调函数

LRESULT CALLBACK callBackProc(HWND hWnd, UINT msgID, WPARAM wparam, LPARAM lparam)

7.1从wparam中获取socket句柄

SOCKET sock = (SOCKET)wparam;

7.2获取操作码,使用分支语句进行判断分别进行处理
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
scss复制代码switch (LOWORD(lparam))

         {

         case FD_ACCEPT:

         {

             //参数1:当前窗口的上下文句柄

             //参数2,3:要显示的位置坐标,左上角是0,0

             //参数4:要显示的字符串

             //参数5:参数4的长度,这里不用加后面的/0,因此可以sizeof要减一,或者直接用strlen

             TextOut(hdc, 10, y, "accept执行", sizeof("accept执行") - 1);

             y += 15;

 

             SOCKET socketClient = accept(sock, NULL, NULL);//获取客户端SOCKET句柄

             if (socketClient == INVALID_SOCKET)//出错拿错误码

             {

                  int accepterr = WSAGetLastError();

                  break;

             }

             //没错则将事件和客户端SOCKET句柄装消息队列并投递到操作系统

             if (WSAAsyncSelect(socketClient, hWnd, UM_ASYNCSELECTMSG, FD_READ | FD_WRITE | FD_CLOSE) == SOCKET_ERROR)

             {

                  int WSAAsyncSelecterr = WSAGetLastError();

                  closesocket(socketClient);

             }

 

             //成功则装进SOCKET数组,便于最后释放

             garr_sockAll[gi_sockCout] = socketClient;

             gi_sockCout++;

 

             break;

         }

         case FD_READ:

         {

             TextOut(hdc, 10, y, "read执行", sizeof("read执行") - 1);

             y += 15;

 

             char str[1000] = { 0 };

 

             //接收消息

             if (recv(sock, str, 999, 0) == SOCKET_ERROR)

             {

                  int recverr = WSAGetLastError();

                  break;

             }

             //显示消息

             TextOut(hdc, 10, y, str, strlen(str));

             y += 15;

 

             break;

         }

         case FD_WRITE:

         {

             TextOut(hdc, 10, y, "wirte执行", sizeof("wirte执行") - 1);

             y += 15;

             //窗口还没地方写消息,写个死的先

             if (send(sock, "异步选择模型连接成功~", sizeof("异步选择模型连接成功~"), 0) == SOCKET_ERROR)

             {

                  int FD_WRITEsenderr = WSAGetLastError();

             }

             break;

         }

        case FD_CLOSE:

         {

             TextOut(hdc, 10, y, "close执行", sizeof("close执行") - 1);

             y += 15;

             //通过将后面两个参数设置为0,即可关闭该socket上的消息

             WSAAsyncSelect(sock, hWnd, 0, 0);

             //关闭socket

             closesocket(sock);

             //记录数组中删除该socket

             for (int i = 0; i < gi_sockCout; i++)

             {

                  if (garr_sockAll[i] == sock)

                  {

                      garr_sockAll[i] = garr_sockAll[gi_sockCout - 1];//没有顺序要求,直接用最后一个元素补位

                      gi_sockCout--;

                      break;

                  }

             }

             WSACleanup();//清理网络库

             //最后一个分支不用break

         }

    }
8、关闭SOCKET句柄
1
2
3
4
5
scss复制代码ReleaseDC(hWnd, hdc);//释放hdc

    //对未处理的消息进行默认处理

    return DefWindowProc(hWnd, msgID, wparam, lparam);

运行结果

image.png

如何理解异步、同步

同步:提交请求->等待服务器处理->处理完毕返回 这个期间客户端浏览器不能干任何事
异步: 请求通过事件触发->服务器处理(这是浏览器仍然可以作其他事情)->处理完毕

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%