异步选择模型

314 阅读4分钟

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

异步选择模型

前言

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

内容和步骤

服务器端:

实现基本窗口功能:

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

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

int regid = RegisterClassEx(&wndc);
    if (regid == 0)//如果注册失败
    {
         int RegisterClassExerr = GetLastError();

    }

3、创建窗口:CreateWindowEx

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

howWindow(hWnd, SW_NORMAL);

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

WORD wdVersion = MAKEWORD(2, 2);

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

    int b = *((char*)&wdVersion + 1);
5.1.打开网络库
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.校验版本
if (2 != HIBYTE(wdScokMsg.wVersion) || 2 != LOBYTE(wdScokMsg.wVersion))

    {

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

         WSACleanup();

         return 0;

    }
5.3.创建SOCKET
SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
5.4.绑定地址与端口
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,并投递给操作系统
if (WSAAsyncSelect(socketServer, hWnd, UM_ASYNCSELECTMSG, FD_ACCEPT) == SOCKET_ERROR)//失败处理
    {

         int WSAAsyncSelecterr = WSAGetLastError();

         closesocket(socketServer);

         WSACleanup();

         return 0;
    }
6、消息循环:GetMessage、TranslateMessage、DispatchMessage
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获取操作码,使用分支语句进行判断分别进行处理
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句柄
ReleaseDC(hWnd, hdc);//释放hdc

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

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

运行结果

image.png

如何理解异步、同步

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