zoukankan      html  css  js  c++  java
  • 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(无效参数),检查时才发现的。

  • 相关阅读:
    POJ 1659 Frogs' Neighborhood
    zoj 2913 Bus Pass(BFS)
    ZOJ 1008 Gnome Tetravex(DFS)
    POJ 1562 Oil Deposits (DFS)
    zoj 2165 Red and Black (DFs)poj 1979
    hdu 3954 Level up
    sgu 249 Matrix
    hdu 4417 Super Mario
    SPOJ (BNUOJ) LCM Sum
    hdu 2665 Kth number 划分树
  • 原文地址:https://www.cnblogs.com/lvdongjie/p/4502652.html
Copyright © 2011-2022 走看看