1. 程式人生 > >windows下的IO模型之異步選擇(WSAAsyncSelect)模型

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

系列 發送數據 post 使用 寫入 種類 cleanup 標準 過程

異步選擇(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)模型