zoukankan      html  css  js  c++  java
  • 异步选择模型

        异步选择(WSAAsyncSelect)模型是一个有用的异步 I/O 模型。利用这个模型,应用程序可在一个套接字上,接收以 Windows 消息为基础的网络事件通知。具体

    的做法是在建好一个套接字后,调用WSAAsyncSelect函数。该模型的核心即是WSAAsyncSelect函数。

        WSAAsyncSelect函数定义如下:

        int WSAAsyncSelect(

            __in SOCKET s,              //指定的是我们感兴趣的那个套接字。

            __in HWND hWnd,          //指定一个窗口句柄,它对应于网络事件发生之后,想要收到通知消息的那个窗口。

            __in unsigned int wMsg,  //指定在发生网络事件时,打算接收的消息。该消息会投递到由hWnd窗口句柄指定的那个窗口。

            __in long lEvent              //指定一个位掩码,对应于一系列网络事件的组合
       );

    注意:

          1、wMsg参数指定的消息通常是我们自定义的消息,应用程序需要将这个消息设为比Windows的WM_USER大的一个值,避免网络窗口消息与系统预定义的标准窗

    口消息发生混淆与冲突。

          2、lEvent参数指定的网络类型为:FD_READ、FD_WRITE、FD_ACCEPT、FD_CONNECT、FD_CLOSE等。当然,到底使用FD_ACCEPT,还是使用

    FD_CONNECT类型,要取决于应用程序的身份是客户端,还是服务器。如应用程序同时对多个网络事件有兴趣,只需对各种类型执行一次简单的按位OR(或)运算就OK。

          FD_READ              应用程序想要接收有关是否可读的通知,以便读入数据

          FD_WRITE            应用程序想要接收有关是否可写的通知,以便写入数据

          FD_ACCEPT           应用程序想接收与进入连接有关的通知

          FD_CONNECT        应用程序想接收与一次连接完成的通知

          FD_CLOSE            应用程序想接收与套接字关闭的通知

          3、多个事件务必在套接字上一次注册!另外还要注意的是,一旦在某个套接字上允许了事件通知,那么以后除非明确调用closesocket命令,或者由应用程序针对那

    个套接字调用了WSAAsyncSelect,从而更改了注册的网络事件类型,否则的话,事件通知会永远有效!若将lEvent参数设为0,效果相当于停止在套接字上进行的所有

    网络事件通知。

          4、若应用程序针对一个套接字调用了WSAAsyncSelect,那么套接字的模式会从“锁定”变成“非锁定”。这样一来,如果调用了像WSARecv这样的Winsock函数,

    但当时却并没有数据可用,那么必然会造成调用的失败,并返回WSAEWOULDBLOCK错误。为防止这一点,应用程序应依赖于由WSAAsyncSelect的uMsg参数指定的

    用户自定义窗口消息,来判断网络事件类型何时在套接字上发生;而不应盲目地进行调用。

         应用程序在一个套接字上成功调用了WSAAsyncSelect之后,会在与hWnd窗口句柄对应的窗口例程中,以Windows消息的形式,接收网络事件通知。窗口例程通常

    定义如下:

        LRESULT CALLBACK WindowProc(

        HWND hwnd,           //指定一个窗口的句柄,对窗口例程的调用正是由那个窗口发出的。

        UINT uMsg,             //指定需要对哪些消息进行处理。这里我们感兴趣的是WSAAsyncSelect调用中定义的消息。

        WPARAM wParam,   //指定在其上面发生了一个网络事件的套接字。(假若同时为这个窗口例程分配了多个套接字,这个参数的重要性便显示出来了。)

        LPARAM lParam       //包含了两方面重要的信息。其中, lParam的低字(低位字)指定了已经发生的网络事件,而lParam的高字(高位字)包含了可能出现的任何

                                    //错误代码。

        );

        流程:网络事件消息抵达一个窗口例程后,应用程序首先应检查lParam的高字位,以判断是否在网络错误。这里有一个特殊的宏: WSAGETSELECTERROR,可用

    它返回高字位包含的错误信息。若应用程序发现套接字上没有产生任何错误,接着便应调查到底是哪个网络事件类型,具体的做法便是读取lParam低字位的内容。此时可

    使用另一个特殊的宏:WSAGETSELECTEVENT,用它返回lParam的低字部分。

        FD_WRITE 事件通知:只有在三种条件下,才会发出 FD_WRITE 通知:

            1、使用 connect 或 WSAConnect,一个套接字首次建立了连接。

            2、使用 accept 或 WSAAccept,套接字被接受以后。

            3、若 send、WSASend、sendto 或 WSASendTo 操作失败,返回了 WSAEWOULDBLOCK 错误,而且缓冲区的空间变得可用。

        下面,我们来看看具体的演示,客户端是用select模型实现的,服务器端是用WSAAsyncSelect实现的,两者界面都相当的简洁

        服务器界面如下:    

        

        客户端界面如下:    

        

        其中,多个客户端可以同时与服务器端实现了,由于使用WSAAsyncSelect模型及我只起一个工作线程的缘故,故最多只能接受64个客户端。    

        

       理论知识与演示完之后,我们来看看服务器端具体的代码实现

       1、当然是初始化socket了,没什么好说的

        WSAData data;
        int error;
        error = WSAStartup(MAKEWORD(2, 2), &data);
        if (0 != error)
        {
            return FALSE;
        }
        if(HIBYTE(data.wVersion) != 2 && LOBYTE(data.wVersion))
    {
    WSACleanup();
    return FALSE;
    }
    return TRUE;

        2、定义一个自定义消息,前面理论知识已讲过就不再重复了    

    #define WM_SOCKET WM_USER + 0x01

        3、开启一个工作线程,初始化socket,开始监听。其实,这个线程可以封装成一个函数,具体看代码就知道了,封装成线程只是个人习惯哈。  

    UINT ThreadProc(LPVOID lpParameter)
    {
        CWSAAsyncSelectDlg *pDlg = (CWSAAsyncSelectDlg*)lpParameter;
        ASSERT(pDlg != NULL);
    
        pDlg->m_listenSocket = socket(AF_INET, SOCK_STREAM, 0);
        if (INVALID_SOCKET == pDlg->m_listenSocket)
        {
            return FALSE;
        }
        char ipbuf[1024] = {0};
        wcstombs(ipbuf, pDlg->GetIpAddress(), pDlg->GetIpAddress().GetLength());
        const char *p = ipbuf;
        sockaddr_in serverAddress;
        serverAddress.sin_addr.S_un.S_addr = inet_addr(p);
        serverAddress.sin_family = AF_INET;
        serverAddress.sin_port = htons(pDlg->m_iPort);
    
        if (SOCKET_ERROR == bind(pDlg->m_listenSocket, (sockaddr*)&serverAddress, sizeof(sockaddr_in)))
        {
            return FALSE;
        }
    
        WSAAsyncSelect(pDlg->m_listenSocket, pDlg->GetSafeHwnd(), WM_SOCKET, FD_ACCEPT | FD_CLOSE);
    
        listen(pDlg->m_listenSocket, SOMAXCONN);
    
        pDlg->ShowText(_T("系统消息:服务器开始监听。。。"));
    
        return TRUE;
    }

        4、WSAAsyncSelect模型的核心就是这个窗口过程,但是格式也是一样一样的。。。

    LRESULT CWSAAsyncSelectDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
    {
        // TODO: 在此添加专用代码和/或调用基类
        TCHAR buf[1024] = {0};
        CString cstrMsg;
        CString cstrIP;
        szClientItem clientItem;
        sockaddr_in clientAddr = {0};
        int iLength = sizeof(sockaddr_in);
        switch (message)
        {
        case WM_SOCKET:
            if (WSAGETASYNCERROR(lParam))
            {
                vector<szClientItem>::iterator iter = m_ClientSockets.begin();
                for (vector<szClientItem>::size_type i = 0; i < m_ClientSockets.size(); i++)
                {
                    if (m_ClientSockets.at(i).m_socket == (SOCKET)wParam)
                    {
                        ShowText(_T("系统消息: ") + m_ClientSockets.at(i).m_cstrIP + _T("客户端已经关闭。"));
                        closesocket(wParam);
                        m_ClientSockets.erase(iter + i);
                        break;
                    }
                }
            }
            switch(WSAGETSELECTEVENT(lParam))
            {
            case FD_ACCEPT:
                clientItem.m_socket = accept(wParam, (sockaddr*)&clientAddr, &iLength);
                cstrIP.Format(_T("%d.%d.%d.%d"),clientAddr.sin_addr.S_un.S_un_b.s_b1,clientAddr.sin_addr.S_un.S_un_b.s_b2,clientAddr.sin_addr.S_un.S_un_b.s_b3,clientAddr.sin_addr.S_un.S_un_b.s_b4);
                clientItem.m_cstrIP = cstrIP;
                WSAAsyncSelect(clientItem.m_socket, m_hWnd, WM_SOCKET, FD_READ | FD_WRITE | FD_CLOSE);
                m_ClientSockets.push_back(clientItem);
                ShowText(_T("系统消息: 客户端 ")+ cstrIP + _T(" 已经连接成功"));
                break;
            case FD_READ:
                recv(wParam, (char *)buf, 1024, 0);
                cstrMsg = buf;
                for (vector<szClientItem>::size_type i = 0; i < m_ClientSockets.size(); i++)
                {
                    if (m_ClientSockets.at(i).m_socket == (SOCKET)wParam)
                    {
                        ShowText(_T("Client: ") + m_ClientSockets.at(i).m_cstrIP + _T(">") + cstrMsg);
                        break;
                    }
                }
                break;
            case FD_WRITE:
                break;
            case FD_CLOSE:
    break;
    } default:break; } return CDialogEx::WindowProc(message, wParam, lParam); }

     具体核心代码我想应该就是这些了,这个模型相当简单,操作无非是这几步:

        1、定义一个自定义消息

        2、WSAAsyncSelect函数关联自定义消息并选择感兴趣的网络事件

        3、定义一个窗口过程,WSAAsyncSelect函数绑定到这个窗口过程

        4、在窗口过程捕获感兴趣的网络事件并做相应的处理。

        是不是很简单。。。。

        

  • 相关阅读:
    Qt ini文件
    Qt我的文档 桌面路径
    windows zlib库编译步骤
    环形缓冲区
    openssl生成随机数
    怎样安装Scrapy
    CentOS7怎样安装GoAccess1.3
    Docker创建数据卷容器
    Docker创建数据卷
    Docker创建容器
  • 原文地址:https://www.cnblogs.com/venow/p/2543053.html
Copyright © 2011-2022 走看看