【网络编程】异步选择模型

简介: 【网络编程】异步选择模型

异步选择模型

逻辑

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

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

WSAAsyncSelect

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

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

窗口

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

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

第三步:创建窗口——CreateWindowEx

第四步:显示窗口——ShowWindow

第五步:消息循环——GetMessage

​ ——TranslateMessage

​ ——DispatchMessage

第六步:回调函数

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

服务端

网络库 头文件 
打开网络库
校验版本
创建SOCKET
绑定地址与端口
开始监听

异步选择

异步选择

int WSAAsyncSelect(
  SOCKET s,
  HWND   hWnd,
  u_int  wMsg,
  long   lEvent
);

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一模一样

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保留 
重叠I/O模型中
FD_ROUTING_ INTERFACE_CHANGE
    想要接收指定目标的路由接口更改通知。
    数据到达对方的所经过的线路改变了,由于是动态优化选择
    要通过此函数WSAIoctl注册之后,才可以
    SIO_ROUTING_ INTERFACE_CHANGE
FD_ADDRESS_ LIST_CHANGE
    想要接收套接字地址族的本地地址列表更改通知。
    要通过此函数WSAIoctl注册之后,才可以有效
    服务器链接了很多客户端,服务器就记录着所有的客户端的地址信     息,就相当于一个列表,发生变化,会得到相关的信号。
    SIO_ADDRESS_ LIST_CHANGE

返回值

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

完整代码

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


要点

客户端socket
    (SOCKET)wParam
产生的错误码
    HIWORD(lParam)
具体的消息种类
    LOWORD(lParam)
窗口上打印数据
    textout

优化

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

问题

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

总结

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

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

相关文章
|
2月前
|
机器学习/深度学习 算法 数据安全/隐私保护
基于BP神经网络的苦瓜生长含水量预测模型matlab仿真
本项目展示了基于BP神经网络的苦瓜生长含水量预测模型,通过温度(T)、风速(v)、模型厚度(h)等输入特征,预测苦瓜的含水量。采用Matlab2022a开发,核心代码附带中文注释及操作视频。模型利用BP神经网络的非线性映射能力,对试验数据进行训练,实现对未知样本含水量变化规律的预测,为干燥过程的理论研究提供支持。
|
1月前
|
存储 网络协议 安全
30 道初级网络工程师面试题,涵盖 OSI 模型、TCP/IP 协议栈、IP 地址、子网掩码、VLAN、STP、DHCP、DNS、防火墙、NAT、VPN 等基础知识和技术,帮助小白们充分准备面试,顺利踏入职场
本文精选了 30 道初级网络工程师面试题,涵盖 OSI 模型、TCP/IP 协议栈、IP 地址、子网掩码、VLAN、STP、DHCP、DNS、防火墙、NAT、VPN 等基础知识和技术,帮助小白们充分准备面试,顺利踏入职场。
81 2
|
1月前
|
运维 网络协议 算法
7 层 OSI 参考模型:详解网络通信的层次结构
7 层 OSI 参考模型:详解网络通信的层次结构
81 1
|
2月前
|
网络协议 前端开发 Java
网络协议与IO模型
网络协议与IO模型
105 4
网络协议与IO模型
|
2月前
|
机器学习/深度学习 网络架构 计算机视觉
目标检测笔记(一):不同模型的网络架构介绍和代码
这篇文章介绍了ShuffleNetV2网络架构及其代码实现,包括模型结构、代码细节和不同版本的模型。ShuffleNetV2是一个高效的卷积神经网络,适用于深度学习中的目标检测任务。
92 1
目标检测笔记(一):不同模型的网络架构介绍和代码
|
1月前
|
网络协议 算法 网络性能优化
计算机网络常见面试题(一):TCP/IP五层模型、TCP三次握手、四次挥手,TCP传输可靠性保障、ARQ协议
计算机网络常见面试题(一):TCP/IP五层模型、应用层常见的协议、TCP与UDP的区别,TCP三次握手、四次挥手,TCP传输可靠性保障、ARQ协议、ARP协议
|
2月前
|
安全 NoSQL Java
一文搞懂网络通信的基石✅IO模型与零拷贝
【10月更文挑战第1天】本文深入探讨了网络通信中的IO模型及其优化方法——零拷贝技术。首先介绍了IO模型的概念及五种常见类型:同步阻塞、同步非阻塞、多路复用、信号驱动和异步IO模型。文章详细分析了每种模型的特点和适用场景,特别是多路复用和异步IO在高并发场景中的优势。接着介绍了零拷贝技术,通过DMA直接进行数据传输,避免了多次CPU拷贝,进一步提升了效率。最后总结了各种模型的优缺点,并提供了相关的代码示例和资源链接。
一文搞懂网络通信的基石✅IO模型与零拷贝
|
1月前
|
机器学习/深度学习 人工智能 算法
【车辆车型识别】Python+卷积神经网络算法+深度学习+人工智能+TensorFlow+算法模型
车辆车型识别,使用Python作为主要编程语言,通过收集多种车辆车型图像数据集,然后基于TensorFlow搭建卷积网络算法模型,并对数据集进行训练,最后得到一个识别精度较高的模型文件。再基于Django搭建web网页端操作界面,实现用户上传一张车辆图片识别其类型。
81 0
【车辆车型识别】Python+卷积神经网络算法+深度学习+人工智能+TensorFlow+算法模型
|
2月前
|
开发者
什么是面向网络的IO模型?
【10月更文挑战第6天】什么是面向网络的IO模型?
23 3
|
2月前
|
数据挖掘 开发者
网络IO模型
【10月更文挑战第6天】网络IO模型
44 3