• WSAEventSelect模型详解


           WSAEventSelect 是 WinSock 提供的一种异步事件通知I/O模型,与 WSAAsyncSelect模型有些类似。
           该模型同样是接收 FD_XXX 之类的网络事件,但是是通过事件对象句柄通知,而非像 WSAAsyncSelect一样依靠Windows的消息驱动机制。

          

          与WSAAsyncSelect模型相同,WSAEventSelect将所有的SOCKET事件分为如下类型:(共十种)
                    FD_READ , FD_WRITE , FD_OOB , FD_ACCEPT, FD_CONNECT , FD_CLOSE,
                    FD_QOS , FD_GROUP_QOS , FD_ROUTING_INTERFACE_CHANGE , FD_ADDRESS_LIST_CHANGE
          还有一个 FD_ALL_EVENTS  代表所有的事件
          其中 FD_READ 的定义如下:
     #define FD_READ_BIT      0
     #define FD_READ          (1 << FD_READ_BIT)   // = 1 
           其他的定义也都类似,比如: FD_ACCEPT_BIT = 3
           但是并不是每一种SOCKET都能发生所有的事件,比如监听SOCKET只能发生 FD_ACCEPT 和 FD_CLOSE 事件。

           在WSAEventSelect模型中,基本流程如下:
     1. 创建一个事件对象数组,用于存放所有的事件对象;
     2. 创建一个事件对象(WSACreateEvent);
     3. 将一组你感兴趣的SOCKET事件与事件对象关联(WSAEventSelect),然后加入事件对象数组;
     4. 等待事件对象数组上发生一个你感兴趣的网络事件(WSAWaitForMultipleEvents);
     5. 对发生事件的事件对象查询具体发生的事件类型(WSAEnumNetworkEvents);
     6. 针对不同的事件类型进行不同的处理;
     7. 循环进行 .4

            对于TCP服务端程序而言,在创建一个监听SOCKET,绑定至某个端口然后监听后,可以创建一个事件对象然后与 FD_ACCEPT 和 FD_CLOSE 事件
    关联。在第6步时对于 FD_ACCEPT 事件可以将accept得到的SOCKET关联 FD_WRITE,FD_READ,FD_CLOSE事件后加入事件对象数组。

    WSAEVENT WSACreateEvent(void);

          创建一个 事件对象,实际上 WSAEVENT就是一个 HANDLE

    int WSAEventSelect(
      _In_  SOCKET s,            // 需要关联的SOCKET
      _In_  WSAEVENT hEventObject,    // 需要关联的事件对象
      _In_  long lNetworkEvents     // 感兴趣的网络事件,不同的事件可以用 | 合并, FD_ALL_EVENTS 代表所有的事件
    );

           函数执行成功将返回 0 ,否则返回 SOCKET_ERROR, 可调用WSAGetLastError() 查看具体的错误代码

    DWORD WSAWaitForMultipleEvents(
      _In_  DWORD cEvents,    // 事件对象数组的数量
      _In_  const WSAEVENT *lphEvents,   // 事件对象数组
      _In_  BOOL fWaitAll,    // 是否等待所有的事件对象受信,显然一般情况下是false
      _In_  DWORD dwTimeout,   // 超时时限,单位是毫秒,WSA_INFINITE 为无穷大
      _In_  BOOL fAlertable  // 该模型下忽略,应该设置为false
    );

           如果执行失败返回 WSA_WAIT_IO_COMPLETION ; 如果是超时,则返回 WSA_WAIT_TIMEOUT
           如果 函数执行成功将会返回一个值,分布在 区间 [ WSA_WAIT_EVENT_0 ,(WSA_WAIT_EVENT_0+cEvents-1) ] 内
           也就是说返回值 nRet-WSA_WAIT_EVENT_0 将是发生事件的对象在事件对象数组中的下标。

    int WSAEnumNetworkEvents(
      _In_   SOCKET s,     //    发生事件的SOCKET
      _In_   WSAEVENT hEventObject,   //    发生事件的事件对象
      _Out_  LPWSANETWORKEVENTS lpNetworkEvents //     发生的网络事件
    );

          如果该函数执行成功将会返回0,然后可以通过查询网络事件判断到底发生了什么事件。

          WSANETWORKEVENTS的定义如下:

    typedef struct _WSANETWORKEVENTS {
           long lNetworkEvents;   // 发生的网络事件类型
           int iErrorCode[FD_MAX_EVENTS]; // 网络事件对应的错误代码
    } WSANETWORKEVENTS, FAR * LPWSANETWORKEVENTS;

          比如当发生 FD_READ 事件时, 那么 networkEvent.lNetworkEvents&FD_READ 将为真,同时 networkEvent.iErrorCode[FD_READ_BIT]
    标明了此时的错误代码。


    代码示例:(你还需要一个 client程序,自己写或者找吧)

    #include <Windows.h>
    #include <iostream>
    #pragma comment(lib,"ws2_32.lib")
    using std::cout;
    using std::cin;
    using std::endl;
    using std::ends;

    void WSAEventServerSocket()
    {
        SOCKET server = ::socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
        if(server == INVALID_SOCKET){
            cout<<"创建SOCKET失败!,错误代码:"<<WSAGetLastError()<<endl;
            return ;
        }

        int error = 0;
        sockaddr_in addr_in;
        addr_in.sin_family = AF_INET;
        addr_in.sin_port = htons(15000);
        addr_in.sin_addr.s_addr = INADDR_ANY;
        error= ::bind(server,(sockaddr*)&addr_in,sizeof(sockaddr_in));
        if(error == SOCKET_ERROR){
            cout<<"绑定端口失败!,错误代码:"<<WSAGetLastError()<<endl;
            return ;
        }

        listen(server,5);
        if(error == SOCKET_ERROR){
            cout<<"监听失败!,错误代码:"<<WSAGetLastError()<<endl;
            return ;
        }
        cout<<"成功监听端口 :"<<ntohs(addr_in.sin_port)<<endl;

        WSAEVENT eventArray[WSA_MAXIMUM_WAIT_EVENTS];        // 事件对象数组
        SOCKET sockArray[WSA_MAXIMUM_WAIT_EVENTS];            // 事件对象数组对应的SOCKET句柄
        int nEvent = 0;                    // 事件对象数组的数量 

        WSAEVENT event0 = ::WSACreateEvent();
        ::WSAEventSelect(server,event0,FD_ACCEPT|FD_CLOSE);
        eventArray[nEvent]=event0;
        sockArray[nEvent]=server;
        nEvent++;

        while(true){
            int nIndex = ::WSAWaitForMultipleEvents(nEvent,eventArray,false,WSA_INFINITE,false);
            if( nIndex == WSA_WAIT_IO_COMPLETION || nIndex == WSA_WAIT_TIMEOUT ){
                cout<<"等待时发生错误!错误代码:"<<WSAGetLastError()<<endl;
                break;
            }
            nIndex = nIndex - WSA_WAIT_EVENT_0;
            WSANETWORKEVENTS event;
            SOCKET sock = sockArray[nIndex];
            ::WSAEnumNetworkEvents(sock,eventArray[nIndex],&event);
            if(event.lNetworkEvents & FD_ACCEPT){
                if(event.iErrorCode[FD_ACCEPT_BIT]==0){
                    if(nEvent >= WSA_MAXIMUM_WAIT_EVENTS){
                        cout<<"事件对象太多,拒绝连接"<<endl;
                        continue;
                    }
                    sockaddr_in addr;
                    int len = sizeof(sockaddr_in);
                    SOCKET client = ::accept(sock,(sockaddr*)&addr,&len);
                    if(client!= INVALID_SOCKET){
                        cout<<"接受了一个客户端连接 "<<inet_ntoa(addr.sin_addr)<<":"<<ntohs(addr.sin_port)<<endl;
                        WSAEVENT eventNew = ::WSACreateEvent();
                        ::WSAEventSelect(client,eventNew,FD_READ|FD_CLOSE|FD_WRITE);
                        eventArray[nEvent]=eventNew;
                        sockArray[nEvent]=client;
                        nEvent++;
                    }
                }
            }else if(event.lNetworkEvents & FD_READ){
                if(event.iErrorCode[FD_READ_BIT]==0){
                    char buf[2500];
                    ZeroMemory(buf,2500);
                    int nRecv = ::recv( sock,buf,2500,0);
                    if(nRecv>0){
                        cout<<"收到一个消息 :"<<buf<<endl;
                        char strSend[] = "I recvived your message.";
                        ::send(sock,strSend,strlen(strSend),0);
                    }
                }
            }else if(event.lNetworkEvents & FD_CLOSE){
                ::WSACloseEvent(eventArray[nIndex]);
                ::closesocket(sockArray[nIndex]);
                cout<<"一个客户端连接已经断开了连接"<<endl;
                for(int j=nIndex;j<nEvent-1;j++){
                    eventArray[j]=eventArray[j+1];
                    sockArray[j]=sockArray[j+1];
                }
                nEvent--;
            } else if(event.lNetworkEvents & FD_WRITE ){
                cout<<"一个客户端连接允许写入数据"<<endl;
            }
        } // end while
        ::closesocket(server);
    }

    int _tmain(int argc, _TCHAR* argv[])
    {
        WSADATA wsaData;
        int error; 
        WORD wVersionRequested;    
        wVersionRequested = WINSOCK_VERSION; 
        error = WSAStartup( wVersionRequested , &wsaData );
        if ( error != 0 ) {
            WSACleanup();
            return 0;
        }

        WSAEventServerSocket();

        WSACleanup();
        return 0;
    }


    // 解释一下,为什么我在 socket函数前面加上 ::
    因为我前面写的时候本来用了thread库准备开一个线程运行Server,另一个运行Client。
    结果 用了 using namespace std;  后,正好引入了bind函数(std的那个模板)把 socket的bind给覆盖了,
    然后就一直是 错误了,查下错误代码是 10022(无效参数),检查时才发现的。

  • 相关阅读:
    ajax GET 传输中文乱码
    php 验证码 图像存在错误 无法显示 解决方法
    ajax 简单实例
    PHP continue break 区别 用法
    php注意事项
    php7注意事项
    腾讯2015后台模拟题
    【leetcode】_3Sum
    最小的k个数 2.5
    《Hadoop权威指南》笔记 第三章 并行复制及存档
  • 原文地址:https://www.cnblogs.com/wanda1416/p/3702976.html
Copyright © 2020-2023  润新知