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(無效參數),檢查時才發現的。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM