1. 程式人生 > >c++ socket 非同步程式設計

c++ socket 非同步程式設計

在網路通訊中,由於網路擁擠或一次傳送的資料量過大等原因,經常會發生交換的資料在短時間內不能傳送完,收發資料的函式因此不能返回,這種現象叫做阻塞。 Winsock對有可能阻塞的函式提供了兩種處理方式:阻塞和非阻塞方式。

阻塞模式

     在阻塞方式下,收發資料的函式在被呼叫後一直要到傳送完畢或者出錯才能返回。在阻塞期間,被阻的函式不會斷呼叫系統函式GetMessage()來保持訊息迴圈的正常進行。

非阻塞模式
        將一個套接字置為非阻塞模式之後, Winsock API呼叫會立即返回。一般這些呼叫都會“失敗”,並返回一個WSAEWOULDBLOCK。表明其操作在呼叫期間沒有時間完成。如在系統的輸入緩衝區中,並不存在等待的資料,那recv呼叫就會返回WSAEWOULDBLOCK錯誤。通常,我們需要重複呼叫同一個函式,直至獲得一個成功返回程式碼。這不是一個好的方法。通常採用Winsock的套接字I/O模型去處理。

套接字I/O模型共有五種型別,如下:

  select(選擇) 
  WSAAsyncSelect(非同步選擇)
  WSAEventSelect(事件選擇)
  overlapped(重疊)
  completion port(完成埠)

*WSAAsyncSelect

      Winsock通過WSAAsyncSelect()自動地設定套接字處於非阻塞方式。使用WindowsSockets實現Windows網路程式設計的關鍵就是它提供了對網路事件基於訊息的非同步存取,用於註冊應用程式感興趣的網路事件。它請求Windows Sockets DLL在檢測到套接字上發生的網路事件時,向視窗傳送一個訊息。

 int PASCAL FAR WSAAsyncSelect(SOCKET s,HWND hWnd,unsigned int wMsg,long lEvent);
hWnd:視窗控制代碼
wMsg:需要傳送的訊息
lEvent:事件(以下為事件的內容)
值: 含義:
FD_READ 期望在套接字上收到資料(即讀準備好)時接到通知
FD_WRITE 期望在套接字上可傳送資料(即寫準備好)時接到通知
FD_OOB 期望在套接字上有帶外資料到達時接到通知
FD_ACCEPT 期望在套接字上有外來連線時接到通知
FD_CONNECT 期望在套接字連線建立完成時接到通知
FD_CLOSE 期望在套接字關閉時接到通知

       進行非同步選擇使用WSAAsyncSelect()函式時,有以下幾點需要引起特別的注意:
  .連續使用兩次WSAAsyncSelect()函式時,只有第二次設定的事件有效,如:
           WSAAsyncSelect(s,hwnd,wMsg1,FD_READ);
           WSAAsyncSelect(s,hwnd,wMsg2,FD_CLOSE);
        這樣只有當FD_CLOSE事件發生時才會傳送wMsg2訊息。
  .可以在設定過非同步選擇後通過再次呼叫WSAAsyncSelect(s,hwnd,0,0);的形式取消在套接字上所設定的非同步事件。
  .Windows Sockets DLL在一個網路事件發生後,通常只會給相應的應用程式傳送一個訊息,而不能傳送多個訊息。但通過使用一些函式隱式地允許重發此事件的訊息,這樣就可能再次接收到相應的訊息。
  .在呼叫過closesocket()函式關閉套接字之後不會再發生FD_CLOSE事件。


     對UDP協議,這些網路事件主要為:
      FD_READ   期望在套接字收到資料(即讀準備好)時接收通知;
      FD_WRITE 期望在套接字可傳送數(即寫準備好)時接收通知;
    FD_CLOSE 期望在套接字關閉時接電通知
  訊息變數wParam指示發生網路事件的套接字,變數1Param的低位元組描述發生的網路事件,高字包含錯誤碼。如在視窗函式的訊息迴圈中均加一個分支:
int ok=sizeof(SOCKADDR);
case wMsg;
switch(1Param)
{
    case FD_READ:  //套接字上讀資料 
    if(recvfrom(sr.lpPlayData[j],dwDataSize,0,(struct sockaddr FAR*)&there1,
     (int FAR*)&ok)==SOCKET_ERROR0) {
                MessageBox(hwnd,“資料接收失敗!”,“”,MB_OK);
                return(FALSE);
       }
    case FD_WRITE:    //套接字上寫資料
  }
break;

*WSAEventSelect
      事件通知模型要求在程式中針對使用的每個套接字建立一個事件物件,然後通過事件模式通知程式其套接字是否收到或傳送的資訊。一般來說這種模式,一般就是通過類似呼叫waitformultipleObject一樣在一個執行緒中等待訊號事件來,來了就處理。具體呼叫的函式如下:

    建立WSACreateEvent函式.該函式的返回值是一個建立好的事件物件控制代碼。事件物件控制代碼完後,接下來將其與某個套接字關聯在一起,同時註冊自己感興趣的網路事件型別,方法是呼叫WSAEventSelect函式,對它的定義如下:

   int WSAEventSelect (
              SOCKET s,                              //需要非阻塞處理的套接字
              WSAEVENT hEventObject,    //WSACreateEvent 建立來的,關聯到socket
              long lNetworkEvents     
               );
    lNetworkEvents,對應一個“位掩碼”,用於指定應用程式感興趣的各種網路事件型別的一個組合。要想獲知對這些事件型別的詳細說明,請參考早先討論過的WSAAsyncSelect I/O模型。
      為WSAEventSelect建立的事件擁有兩種工作狀態,以及兩種工作模式。
    兩種工作狀態分別是“已傳信”(signaled)和 “未傳信”(nonsignaled)。
    工作模式則包括“人工”(manual reset)和“自動”(auto reset)。
      

      WSACreateEvent預設時其訊號狀態為0,且為人工設定,當網路事件觸發了與一個套接字關聯在一起的事件物件,其事件訊號置1。在完成了一個I/O請求的處理之後,需要呼叫WSAResetEvent復位處理(置訊號為0)。
     一個套接字同一個事件物件控制代碼關聯在一起後,應用程式便可開始I/O處理;方法是等待網路事件觸發事件物件控制代碼的工作狀態。
     一般而言,在等待網路傳來事件時,類似WaitforMultipleObject,其WSAWaitForMultipleEvents函式的設計宗旨便是用來等待一個或多個事件物件控制代碼,並在事先指定的一個或所有控制代碼進入有訊號狀態後,或在超過了一個規定的時間週期後,立即返回(執行緒往往在這裡死等)。

   下面是 WSAWaitForMultipleEvents函式的定義:
DWORD WSAWaitForMultipleEvents(
  DWORD cEvents,                 
  const WSAEVENT FAR *lphEvents
  BOOL fWaitAll,                 
  DWORD dwTimeOUT,               
  BOOL fAlertable                
);

其用法和WaitForMultipleObject類似。
cEvents和lphEvents引數定義了由WSAEVENT物件構成的一個數組。在這個陣列中,cEvents指定的是事件物件的數量,而lphEvents對應的是一個指標,用於直接引用該陣列。
     要注意的是, WSAWaitForMultipleEvents只能支援由WSA_MAXIMUM_WAIT_EVENTS物件規定的一個最大值,在此定義成64個。故該I/O模型一次最多都只能支援64個套接字。假如想讓這個模型同時管理不止64個套接字,必須建立更多的工作者執行緒,以便等待更多的事件物件。

fWaitAl l 引數指定了WSAWaitForMultiple Events如何等待在事件陣列中的物件。
   =TRUE,那麼只有等lphEvents陣列內包含的所有事件物件都處於有訊號狀態,函式才會返回;

   =FALSE,任一個事件物件進入有訊號時,函式就會返回。

 dwTimeout引數規定了 WSAWaitForMultipleEvents最多可等待一個網路事件發生有多長時間。超過規定的時間,函式就會立即返回。並返回WSA_WAIT_TIMEOUT。如dwsTimeout設為WSA_INFIN ITE(永遠等待),那麼根據fWaiiAll或等待一個網路事件或所有網路事件都傳訊號後,才能從該函式退出。
 fAlertable,預設設為FALSE。主要用於在重疊式I/O模型中.

  當設定fWaiAll=false,WaitForMultipleObject再有網路事件時,會返回一個值,指出造成函式返回的事件物件。根據WSAWaitForMultipleEvents的返回值,減去預定義值WSA_WAIT_EVENT_0,得到具體的引用值(即索引位置),程式便可用事件陣列中已發訊號的事件,檢索與那個事件對應的套接字,知道了造成網路事件的套接字後,呼叫 WSAEnumNetworkEvents函式,調查發生了什麼型別的網路事件。該函式定義如下:
int WSAEnumNetworkEvents (
  SOCKET s,                                      //檢索該套接字
  WSAEVENT hEventObject,             
  LPWSANETWORKEVENTS lpNetworkEvents 
);
    hEventObject引數則是可選的;它指定了一個事件控制代碼,對應於打算重設的那個事件物件。由於我們的事件物件處在一個有訊號狀態,所以可將它傳入,令其自動成為無訊號狀態。
    也可以採用使用 WSAResetEvent 函式復位事件訊號。
   lpNetworkEvents,就是返回的結果資訊,它是一個指向WSANETWORKEVENTS結構的指標,用於接收套接字上發生的網路事件型別以及可能出現的任何錯誤程式碼。

其WSANETWORKEVENTS結構的定義:
typedef struct _WSANETWORKEVENTS
{
     long lNetworkEvents;
     int iErrorCode[FD_MAX_EVENTS];
} WSANETWORKEVENTS, FAR * LPWSANETWORKEVENTS;
lNetworkEvents引數指定了一個值,對應於套接字上發生的所有網路事件型別。
      注意一個事件進入置1(有訊號)狀態時,可能會同時發生多個網路事件型別。如,一個忙的伺服器可能同時收到FD_READ和FD_WRITE通知。 iErrorCode引數指定的是一個錯誤程式碼陣列,同lNetworkEvents中的事件關聯在一起。針對每個網路事件型別,都存在著一個特殊的事件索引,名字與事件型別的名字類似,只是要在事件名字後面新增一個“ _BIT”字尾字串即可。如,對FD_READ事件型別來說,iErrorCode陣列的索引識別符號便是FD_READ_BIT。