windows下的IO模型之非同步選擇(WSAAsyncSelect)模型

來源:互聯網
上載者:User

標籤:get   獲得   .net   todo   平台   www   invalid   recv   綁定   

非同步選擇(WSAAsyncSelect)模型是一個有用的非同步I/O 模型。其核心函數是WSAAsyncSelect,

(關於非同步io的理解詳情可以看:http://www.cnblogs.com/curo0119/p/8461520.html)

它可以用來在一個socket上接收以windows訊息為基礎的網路事件。它提供了讀寫資料的非同步通知功能,但不提供非同步資料傳送。WSAAsyncSelect模型的優勢在於只需要一個主線程即可。缺點是必須要綁定視窗控制代碼。即要先調用createwindow建立一個視窗。這也就是為什麼這個模型只適用於windows作業系統而不能跨平台的原因。

WSAAsyncSelect 的函數原型如下:

int WSAAsyncSelect( 

__in         SOCKET s,

  __in         HWND hWnd,

  __in         unsigned int wMsg,

  __in         long lEvent

);

 s 參數指定的是我們感興趣的那個通訊端。

● hWnd 參數指定一個視窗控制代碼,它對應於網路事件發生之後,想要收到通知訊息的那個視窗。

● wMsg 參數指定在發生網路事件時,打算接收的訊息。該訊息會投遞到由hWnd視窗控制代碼指定的那個視窗。

(通常,應用程式需要將這個訊息設為比Windows的WM_USER大的一個值,避免網路視窗訊息與系統預定義的標準視窗訊息發生混淆與衝突)

(即自訂訊息)

● lEvent 參數指定一個位元遮罩,對應於一系列網路事件的組合,大多數應用程式通常感興趣的網路事件類型包括:

FD_READ、FD_WRITE、FD_ACCEPT、FD_CONNECT、FD_CLOSE。當然,到底使用FD_ACCEPT,還是使用FD_CONNECT類型,

要取決於應用程式的身份是用戶端,還是伺服器。如應用程式同時對多個網路事件有興趣,只需對各種類型執行一次簡單的按位OR(或)運算,

然後將它們分配給lEvent就可以了,例如:

WSAAsyncSeltct(s, hwnd,WM_SOCKET, FD_CONNECT | FD_READ | FD_WRITE | FD_CLOSE);

解釋說明:我們的應用程式以後便可在通訊端s上,接收到有關串連、發送、接收以及通訊端關閉這一系列網路事件的通知。

FD_READ       應用程式想要接收有關是否可讀的通知,以便讀入資料

FD_WRITE   應用程式想要接收有關是否可寫的通知,以便寫入資料

FD_ACCEPT     應用程式想接收與進入串連有關的通知

FD_CONNECT 應用程式想接收與一次串連完成的通知

FD_CLOSE   應用程式想接收與通訊端關閉的通知

█ 注意 ①:

多個事件務必在通訊端上一次註冊!

另外還要注意的是,一旦在某個通訊端上允許了事件通知,那麼以後除非明確調用closesocket命令,

或者由應用程式針對那個通訊端調用了WSAAsyncSelect,從而更改了註冊的網路事件類型,否則的話,

事件通知會永遠有效!若將lEvent參數設為0,效果相當於停止在通訊端上進行的所有網路事件通知。

█ 注意 ②:

若應用程式針對一個通訊端調用了WSAAsyncSelect,那麼通訊端的模式會自動從“阻塞”變成“非阻塞”。

這樣一來,如果調用了像WSARecv這樣的Winsock函數,但當時卻並沒有資料可用,那麼必然會造成調用的失敗,並返回WSAEWOULDBLOCK錯誤。

為防止這一點,應用程式應依賴於由WSAAsyncSelect的uMsg參數指定的使用者自訂視窗訊息,來判斷網路事件類型何時在通訊端上發生;而不應盲目地進行調用。

 

應用程式在一個通訊端上成功調用了WSAAsyncSelect之後,會在與hWnd視窗控制代碼對應的視窗類別中,以Windows訊息的形式,接收網路事件通知。

視窗常式通常定義如下:

LRESULT CALLBACK WindowProc(

    HWND hwnd,

    UINT uMsg,

    WPARAM wParam,

    LPARAM lParam

);

● hWnd 參數指定一個視窗的控制代碼,對視窗常式的調用正是由那個視窗發出的。

● uMsg 參數指定需要對哪些訊息進行處理。這裡我們感興趣的是WSAAsyncSelect調用中定義的訊息。

● wParam 參數指定發生網路事件的socket。假若同時為這個視窗常式分配了多個通訊端,這個參數的重要性便顯示出來了。

● lParam參數中,包含了兩方面重要的資訊。其中,lParam的低字(低位字)指定了已經發生的網路事件,而lParam的高字(高位字)包含了可能出現的任何錯誤碼。

 

█ 步驟:網路事件訊息抵達一個視窗常式後,應用程式首先應檢查lParam的高字位,以判斷是否在網路錯誤。

這裡有一個特殊的宏: WSAGETSELECTERROR,可用它返回高字位包含的錯誤資訊。

若應用程式發現通訊端上沒有產生任何錯誤,接著便應調查到底是哪個網路事件類型,具體的做法便是讀取lParam低字位的內容。

此時可使用另一個特殊的宏:WSAGETSELECTEVENT,用它返回lParam的低字部分(也就是FD_)。

如:

  if(WSAGETSELECTERROR(lParam))

     return;

  else

{

    switch(WSAGETSELECTEVENT(lParam))

   {

      case FD_READ:

              ...

              break;

      case FD_WRITE:

              ...

              break;

      ...

    }

}

█ 注意 ③:應用程式如何對 FD_WRITE 事件通知進行處理。

只有在三種條件下,才會發出 FD_WRITE 通知:

■ 使用 connect 或 WSAConnect,一個通訊端首次建立了串連。

■ 使用 accept 或 WSAAccept,通訊端被接受以後。

■ 若 send、WSASend、sendto 或WSASendTo 操作失敗,返回了 WSAEWOULDBLOCK 錯誤,而且緩衝區的空間變得可用。

 

因此,作為一個應用程式,自收到首條 FD_WRITE 訊息開始,便應認為自己必然能在一個通訊端上發出資料,

直至一個send、WSASend、sendto 或WSASendTo 返回通訊端錯誤 WSAEWOULDBLOCK。

經過了這樣的失敗以後,要再用另一條 FD_WRITE 通知應用程式再次發送資料。

代碼:

[cpp] view plain copy<span style="font-size:14px;">伺服器端:  UINT CServerDlg::ThreadFun(LPVOID pParam )  {      CServerDlg* pDlg=(CServerDlg*)pParam;      pDlg->InitSock();        SOCKADDR_IN serAdd;      serAdd.sin_family=AF_INET; //AF_INET表示地址族,在windows下與PF_INET(協議族)是一樣的      serAdd.sin_port=htons(5000);      serAdd.sin_addr.s_addr=ADDR_ANY;      pDlg->m_listenSock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); //建立通訊端      if (INVALID_SOCKET==pDlg->m_listenSock)      {          AfxMessageBox(_T("初始化通訊端失敗"));          return 0;      }      if(SOCKET_ERROR==bind(pDlg->m_listenSock,(SOCKADDR*)&serAdd,sizeof(SOCKADDR)))      {          AfxMessageBox(_T("綁定地址失敗"));          return 0;      }      if (SP_ERROR==listen(pDlg->m_listenSock,SOMAXCONN))      {          AfxMessageBox(_T("啟動監聽失敗"));          return 0;        }  
//向windows註冊 WSAAsyncSelect(pDlg->m_listenSock,pDlg->GetSafeHwnd(),WM_SOCKET,FD_ACCEPT | FD_CLOSE); //當有感興趣的事件發生時用Windows訊息通知 //在視窗過程中實現該訊息的判斷 } LRESULT CServerDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) { // TODO: 在此添加專用代碼和/或調用基類 //在網路事件中wParam代表了控制代碼 lParam的高位表示了錯誤資訊 低位表示了相關的網路事件 switch(message) { case WM_SYSCOMMAND: { if(SC_CLOSE==wParam) { if(m_listenSock) closesocket(m_listenSock); if (m_cliSock) closesocket(m_cliSock); WSACleanup(); } } break; case WM_SOCKET: if (WSAGETASYNCERROR(lParam)) //WSAGENSELECTERROR宏獲得是否有錯誤 這裡用HIWORD(lParam)也是可以的 { MessageBox(_T("網路出錯")); closesocket(wParam); return 0; } switch(WSAGETSELECTEVENT(lParam)) //WSAGETSELECTEVENT擷取網路事件 這裡也可以用LODORD { case FD_ACCEPT: //case裡定義變數時要加入{} { SOCKADDR_IN cliAdd; int len=sizeof(SOCKADDR); m_cliSock=accept(wParam,(SOCKADDR*)&cliAdd,&len); WSAAsyncSelect(m_cliSock,this->GetSafeHwnd(),WM_SOCKET,FD_READ | FD_WRITE |FD_CLOSE); //該通訊端也要用WSAAsyncSelect處理 if (m_cliSock==INVALID_SOCKET) { MessageBox(_T("接收串連出錯")); return 0; } } break; case FD_READ: { TCHAR bufData[1024]={0}; int flag; flag=recv(m_cliSock,(char*)bufData,1024,0); if(flag==0) { MessageBox(_T("串連已經斷開")); return 0; } ShowMsg(bufData); } break; case FD_WRITE: wParam=wParam; //不做處理 break; case FD_CLOSE: closesocket(wParam); WSACleanup(); break; default: break; } } return CDialog::WindowProc(message, wParam, lParam); }

 

  

 

 本文轉載於:http://blog.csdn.net/skyandcode/article/details/8646630

windows下的IO模型之非同步選擇(WSAAsyncSelect)模型

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.