WSAASYNCSELECT模型

Winsock提供了一個有用的異步I/O模型。利用這個模型,應用程式可在一個套接字上,接收以Windows訊息為基礎的網路事件通知。具體的做法是在建好一個套接字後,調用WSAAsyncSelect函式。

概述

異步通知,但不提供異步數據傳送。異步數據傳送有“重疊及完成連線埠模型提供”。

要想使用WSAAsyncSelect模型,在應用程式中,首先必須用CreateWindow函式創建一個視窗,再為該視窗提供一個視窗例程支持函式(Winproc)。亦可使用一個對話框,為其提供一個對話例程,而非視窗例程,因為對話框本質也是“視窗”。
intWSAAsyncSelect(

SOCKETs,//要進行操作的SOCKET

HWNDhWnd,//要綁寫的視窗句柄(當事件發生後要接收訊息的視窗)

unsignedintwMsg,//網路事件發生時的的訊息回響

longlEvent//感興趣的網路事件,請查閱MSDN

);使用方法可分為:

(1)初始化套接字相關信息:

(2)開始啟動一個事件通知。WSAAsyncSelect(Sock,hWnd,自定義訊息,網路事件)

(3)回響視窗的自定義訊息處理函式, 其中lparam的高位字包含了可能出現的錯誤

代碼,低位元組表示發生的網路事件。wParam表示發生網路事件的套接字。

WSAGETSELECTERROR(lParam);//查看是否出現錯誤,獲取低位元組位

WSAGETSELECTEVENT(lParam);//查看發生了什麼事件,獲取高位元組位

事件種類請查看MSDN,可用WSAGetLastError()來獲取錯誤信息。

注意:多個事件必須一次註冊完成,closesocketWSAAsyncSelect

優點:可在系統開銷不大的情況下同時處理許多連線。

缺點:即使用不需要視窗(如伺服器,控制台)它也不得不額外使用一個視窗。同時如果處理成千上萬套接字的所有事件,性能可想而知。

MFC的CSocket所使用的正是這種事件通知模型

今天看了一下,訊息通知的幾個類型。用的比較多的可能就是FD_SEND,FD_WRITE,在網上查了一下資料,總結如下:
【FD_WRITE事件】

(1)呼叫WSAAsyncSelect()來設定FD_WRITE事件時,Socket已經可以傳送資料(TCPscoket已經和對方連線成功了,或UDPsocket已建立完成),且目前outputbuffer仍有空間可寫入資料。
(2)呼叫WSAAsyncSelect()來設定FD_WRITE事件時,Socket尚不能傳送資料,不過一旦Socket與對方連線成功,馬上就會收到FD_WRITE的通知。
(3)呼叫send()或sendto()傳送資料時,系統告知錯誤,且錯誤碼為10035WSAEWOULDBLOCK(呼叫WSAGetLastError()得知這項錯誤),這時表示outputbuffer已經滿了,無法再寫入任何資料(此時即令呼叫再多次的send()也都一定失敗);一旦系統將部份資料成功送抵對方,空出outputbuffer後,便會送一個FD_WRITE給使用者,告知可繼續傳送資料了。換句話說,讀者在呼叫send()傳送資料時,只要不是返回錯誤10035的話,便可一直繼續呼叫send()來傳送資料;一旦send()回返錯誤10035,那麽便不要再呼叫send()傳送資料,而須等收到FD_WRITE後,再繼續傳送資料。

如果只是傳送很少的數據,可能只出現第二種情況,所以在傳送少量數據的時候,不要使用FD_WRITE機制。

【FD_READ事件】

(1)呼叫WSAAsyncSelect函式來對此Socket設定FD_READ事件時,inputbuffer中已有資料。
(2)原先系統的inputbuffer是空的,當系統再收到資料時,會通知我們。
(3)使用者呼叫recv或recvfrom函式,從inputbuffer讀取資料,但是並沒有一次將資料讀光,此時會再驅動一個FD_READ事件,表示仍有資料在inputbuffer中。

讀者必須注意:如果我們收到FD_READ事件通知的訊息,但是我們故意不呼叫recv或recvfrom來讀取資料的話,爾後系統又收到資料時,並不會再次通知我們,一定要等我們呼叫了recv或recvfrom後,才有可能再收到FD_READ的事件通知。

下面是一個利用WSAAsyncSelect的小程式:

WORDwVersionRequested;

WSADATAwsaData;

interr;

wVersionRequested=MAKEWORD(2,2);

err=WSASTARTUP(wVersionRequested,&wsaData);

if(err!=0)

{

return;

}

if(LOBYTE(wsaData.wVersion)!=2||

HIBYTE(wsaData.wVersion)!=2)

{

WSACleanup();

return;

}

charhostname[256];

gethostname(hostname,sizeof(hostname));//這一代碼是為

hostent*hos=gethostbyname(hostname);//了實現自動獲取安

CStringCS=inet_ntoa(*(structin_addr*)hos->h_addr_list[0]);//裝程式的主機代碼

SOCKETserverSocket;

serverSocket=socket(AF_INET,SOCK_STREAM,0);

SOCKADDR_INaddr;

addr.sin_family=AF_INET;

addr.sin_port=htons(6000);

addr.sin_addr.S_un.S_addr=inet_addr(CS);

bind(serverSocket,(SOCKADDR*)&addr,sizeof(SOCKADDR));

//註冊網路事件

if(SOCKET_ERROR==WSAAsyncSelect(serverSocket,m_hWnd,WM_NETMESSAGE,FD_ACCEPT|FD_CLOSE))

{

MessageBox("註冊事件出錯!");

closesocket(serverSocket);

WSACleanup();

return;

}

listen(serverSocket,5);

}

voidCAsyselectsrvDlg::Onrecvmessage(WPARAMwParam,LPARAMlParam)

{

if(WSAGETSELECTERROR(lParam))

{

AfxMessageBox("接受訊息錯誤");

closesocket(wParam);

return;

}

switch(WSAGETSELECTEVENT(lParam))

{

caseFD_ACCEPT:

acceptclient(wParam,m_hWnd);

break;

caseFD_WRITE:

MessageBox("write");

break;

caseFD_READ:

MessageBox("read");

break;

caseFD_CLOSE:

closesocket(wParam);

break;

default:

break;

}

return;

}

voidacceptclient(SOCKETsock,HWNDhwnd)

{

SOCKETacceptclient;

SOCKADDR_INclientaddr;

intlen=sizeof(SOCKADDR_IN);

acceptclient=accept(sock,(structsockaddr*)&clientaddr,&len);

if(SOCKET_ERROR==WSAAsyncSelect(acceptclient,hwnd,WM_NETMESSAGE,FD_READ|FD_WRITE|FD_CLOSE))

{

AfxMessageBox("註冊訊息出錯!");

closesocket(sock);

closesocket(acceptclient);

WSACleanup();

return;

}

}

這段程式可以正常運行,前面定義全局函式:voidacceptclient(SOCKETsock,HWNDhwnd);

相關詞條

相關搜尋

熱門詞條

聯絡我們