windows下的IO模型之異步選擇(WSAAsyncSelect)模型
異步選擇(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)模型