1. 程式人生 > >WSAEventSelect(事件選擇)模型

WSAEventSelect(事件選擇)模型

http://joychou.org/index.php/Misc/WSAEventSelect.html

首先需要用WSACreateEvent建立一個事件物件,其初始化狀態為狀態為“未受信” 並且人工重置。


函式原型


WSAEVENT WSACreateEvent(void);
然後利用WSAEventSelect繫結“監聽”套接字到剛建立的事件物件中,並設定感興趣的網路事件。


int WSAEventSelect(
  __in  SOCKET s,    //套接字
  __in  WSAEVENT hEventObject,    //事件物件
  __in  long lNetworkEvents    // 網路事件
);
再利用WSAWaitForMultipleEvents等待事件物件為“受信”狀態。


問:怎麼才是成為“受信”狀態?
當事件物件繫結的套接字進行了I/O處理,
比如客戶端進行了連線或者客戶端傳送了資料,再或者客戶端關閉套接字,
事件物件都會變成“受信”狀態。


DWORD WSAWaitForMultipleEvents(
  __in  DWORD cEvents,    // 事件物件個數
  __in  const WSAEVENT* lphEvents,     // 事件物件陣列
  __in  BOOL fWaitAll,    // 是否全部等待 
  __in  DWORD dwTimeout,    // 等待時間
  __in  BOOL fAlertable    // 重疊I/O中使用,這裡設定為FALSE
);
最後用WSAEnumNetworkEvents檢查發生了什麼網路事件,最後一個引數返回發生了什麼網路事件,
這個API如果第二個引數如果不設定為NULL,那麼api呼叫完後,事件物件會變成“未受信”狀態。
如果設定為NULL,就必須用WSAResetEvent設定為“未受信”。


int WSAEnumNetworkEvents(
  __in   SOCKET s,    // 套接字,不過必須使用陣列
  __in   WSAEVENT hEventObject,    // 事件物件  不過必須使用陣列
  __out  LPWSANETWORKEVENTS lpNetworkEvents    // 網路事件結構體指標
);
WSANETWORKEVENTS結構體


typedef struct _WSANETWORKEVENTS
 { 
     long lNetworkEvents;  
     int iErrorCode[FD_MAX_EVENTS];
} WSANETWORKEVENTS,  *LPWSANETWORKEVENTS;
最後的主要程式碼:


// 建立一個事件物件,狀態為“未受信”  並且人工重置
    WSAEVENT m_listenEvent = WSACreateEvent();
    // 繫結 “監聽”套接字到事件物件中,並設定感興趣的網路事件
    WSAEventSelect(pThis->m_SockListen, m_listenEvent, FD_ACCEPT | FD_CLOSE);


    DWORD dwEventTotal = 0, dwIndex = 0, dwRet = 0;
    SOCKET ArrSocket[WSA_MAXIMUM_WAIT_EVENTS] = {0};
    WSAEVENT ArrEvent[WSA_MAXIMUM_WAIT_EVENTS] = {0};
    WSANETWORKEVENTS m_NetWorkEvents = {0};
    TCHAR szBuf[1024] = {0};


    ArrSocket[dwEventTotal] = pThis->m_SockListen;
    ArrEvent[dwEventTotal] = m_listenEvent;
    ++dwEventTotal;


    while(TRUE)
    {
        
        // 等待事件物件為受信,不受信的話會返回超時
        // 當事件物件繫結的套接字進行了I/O處理,事件物件會變成“受信”狀態
        dwRet = WSAWaitForMultipleEvents(dwEventTotal, ArrEvent, FALSE, 100, FALSE);
        // 0x102 WSA_WAIT_TIMEOUT
        if (dwRet == WSA_WAIT_TIMEOUT || dwRet == WSA_WAIT_FAILED)  
        {
            continue; //如果返回值是錯誤或是超時,那麼繼迴圈
        }
        // 獲取索引
        dwIndex = dwRet - WSA_WAIT_EVENT_0;


        // 檢查是發生了什麼網路事件
        // 第二個引數傳入事件物件控制代碼,將其設為“未受信”
        WSAEnumNetworkEvents(ArrSocket[dwIndex], ArrEvent[dwIndex], &m_NetWorkEvents);
        if (m_NetWorkEvents.lNetworkEvents == FD_ACCEPT)
        {
            // 如果發生錯誤
            if (m_NetWorkEvents.iErrorCode[FD_ACCEPT_BIT] != 0)
            {
                continue;
            }
            // 接收客戶端的連線
            // 如果第二個填入一個sockaddr_in結構體,可以獲取客戶端的ip
            pThis->m_SockClient = accept(ArrSocket[dwIndex], NULL,NULL);
            if (pThis->m_SockClient == INVALID_SOCKET)
            {
                continue;
            }
            // 建立一個新的事件物件用來繫結和客戶端連線的套接字
            WSAEVENT NewEvent = WSACreateEvent();
            // 繫結 “連線”套接字 到NewEvent事件物件中,監聽FD_READ和FD_CLOSE
            WSAEventSelect(pThis->m_SockClient, NewEvent, FD_READ | FD_CLOSE);
            
            ArrSocket[dwEventTotal] = pThis->m_SockClient;
            ArrEvent[dwEventTotal] = NewEvent;
            ++dwEventTotal;
            ++pThis->m_ClientNum;
        }


        if (m_NetWorkEvents.lNetworkEvents == FD_READ)
        {
            if (m_NetWorkEvents.iErrorCode[FD_READ_BIT] != 0)
            {
                continue;
            }
            recv(ArrSocket[dwIndex], (char *)szBuf, 1024, 0);
            pThis->ShowMsg(szBuf);
        }
        // 當連線被關閉時
        if (m_NetWorkEvents.lNetworkEvents & FD_CLOSE)
        {
                if (m_NetWorkEvents.iErrorCode[FD_CLOSE_BIT] != 0)
                {
                    continue;
                }
                pThis->ShowMsg(_T("客戶端已退出"));   
                closesocket(ArrSocket[dwIndex]);
                WSACloseEvent(ArrEvent[dwIndex]);
                //將此套節字和事件物件從陣列中清除。  
                for (UINT i = dwIndex; i < dwEventTotal-1; i++)
                {
                    ArrSocket[i] = ArrSocket[i+1];  //[i+1]為0
                    ArrEvent[i] = ArrEvent[i+1];    //[i+1]為0   
                }
                dwEventTotal--;         
        }
    }
注意:
在FD_CLOSE這裡,我總是遇到一個問題,m_NetWorkEvents.iErrorCode[FD_CLOSE_BIT]總是不等於0,即發生了錯誤,剛開始不知道為什麼,一直以為是服務端的問題。不過發生的錯誤碼是0x2745,網上搜索下錯誤碼的意思是


[10053(也就是0x2745的十進位制)] 您的主機中的軟體放棄了一個已建立的連線。


說明已經建立的連線在無意間關閉了,而且服務端不知道。
我們需要做的,只是在客戶端點選x的時候(MFC中過載對話方塊類的OnCancle函式),
關閉掉套接字即可。


特點:


多個客戶端連線
利用WSAWaitForMultipleEvents監聽是否有網路事件發生。有就處理,沒有就一直監聽