當前Windows支持的各種Socket I/O模型,主要的模型如下:
一:select模型
二:WSAAsyncSelect模型
三:WSAEventSelect模型
四:Overlapped I/O 事件通知模型
五:Overlapped I/O 完成例程模型
六:IOCP模型
其中本文主要講解WSAAsyncSelect模型,以一種無線通信信令模擬軟件的客戶端程序爲例子,模擬軟件是服務器軟件和客戶端軟件組成,採用TCP通信。對於客戶端程序來說,由於傳輸的數據量不大,只開啓一個socket與服務器端進行通信,可以採用WSAAsyncSelect模型,這樣可以避免採用線程方式的同步問題。WSAAsyncSelect模型是Windows下最簡單易用的一種Socket I/O模型。利用這個模型,應用程序可在一個套接字上,接收以Windows消息爲基礎的網絡事件通知。其優缺點如下:
優點:可在系統開銷不大的情況下同時處理許多連接。
缺點:即使用不需要窗口(如服務器,控制檯)它也不得不額外使用一個窗口。同時如果處理成千上萬套接字的所有事件,性能可想而知。
MFC的CSocket所使用的正是這種事件通知模型,要想使用WSAAsyncSelect模型,在應用程序中,首先必須用CreateWindow函數創建一個窗口,再爲該窗口提供一個窗口例程支持函數(Winproc)。亦可使用一個對話框,爲其提供一個對話例程,而非窗口例程,因爲對話框本質也是“窗口”。
本例子用MFC的對話框模式,以對話框爲窗口例程,窗口例程消息處理函數來處理socket的觸發事件。具體的做法如下:
1.初始化socket通信環境。
具體對話框實例爲CTKClientDlg,在CTKClientDlg::OnInitDialog()函數中調用WSAStartup()函數初始化,如下:
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 2, 2 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 )
{
return FALSE;
}
if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 )
{
WSACleanup( );
return FALSE;
}
2.建立一個套接字後,調用WSAAsyncSelect函數。可以在一個按鈕的消息處理函數中添加如下:
void CTKClientDlg::OnButConn()
{
//創建套接字
m_Socket=WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,0);
if(INVALID_SOCKET==m_Socket)
{
return TK_FAILED;
}
//服務器地址信息
SOCKADDR_IN addrSock;
addrSock.sin_addr.S_un.S_addr=htonl(dwAddr);
addrSock.sin_family=AF_INET;
addrSock.sin_port=htons(iPort);
//連接服務器
if(SOCKET_ERROR==connect(m_Socket,(SOCKADDR*)&addrSock,sizeof(SOCKADDR)))
{
closesocket(m_Socket);
return TK_FAILED;
}
//註冊網絡事件
if(SOCKET_ERROR==WSAAsyncSelect(m_Socket,hWnd,UM_SOCK,FD_READ | FD_CLOSE))
{
closesocket(m_Socket);
return TK_FAILED;
}
return TK_SUCCESS;
}
以上一個比較重要的函數是 WSAAsyncSelect (),這個函數用於註冊窗口要處理的網絡事件,已經網絡事件發生時向窗口發送的用戶的消息。在WSAAsyncSelect(m_Socket,hWnd,UM_SOCK,FD_READ | FD_CLOSE))
函數中,註冊了socket接收到數據和socket關閉時觸發的事件消息。這兩個網絡事件分別用FD_READ和FD_CLOSE註冊,類似的網絡時間還有FD_WRITE、 FD_OOB 、FD_ACCEPT、 FD_CONNECT。對應一系列網絡事件的組合,
Event 含義
FD_READ 程序想要接收有關是否可讀的通知,以便讀入數據
FD_WRITE 程序想要接收有關是否可寫的通知,以便寫入數據
FD_OOB 程序想要接收是否有OOB數據到達的通知
FD_ACCEPT 程序想要接收與進入連接有關的通知
FD_CONNECT 程序想要接收與一次連接或多點接入有關的通知
FD_CLOSE 程序想要接收與套接字關閉有關的通知
FD_QOS 程序想要接收套接字“服務質量(QoS)”發生變化的通知
FD_GROUP_QOS 暫時沒用,屬於保留事件
FD_ROUTING_INTERFACE_CHANGE 程序想要接收有關到指定地址的路由接口發生變化的通知
FD_ADDRESS_LIST_CHANGE 程序想要接收本地地址變化的通知
UM_SOCK爲註冊的消息參數標量,
//自定義消息
#define UM_SOCK WM_USER + 1
3.映射消息處理函數,並實現消息處理函數,映射如下:
BEGIN_MESSAGE_MAP(CTKClientDlg, CDialog)
ON_MESSAGE(UM_SOCK, OnSock)
ON_MESSAGE(UM_DISP, OnDisplayMsg)
ON_MESSAGE(UM_RELEASE, OnRelease)
END_MESSAGE_MAP()
以上是將UM_SOCK消息和OnSock函數進行了關聯。
定義消息處理函數,在CTKClientDlg類定義如下:
DECLARE_MESSAGE_MAP()
afx_msg void OnSock(WPARAM,LPARAM);
實現消息處理函數如下:
void CTKClientDlg::OnSock(WPARAM wParam,LPARAM lParam)
{
switch(LOWORD(lParam))
{
case FD_READ:
{
//socket收到數據的處理代碼
}
break;
case FD_CLOSE:
{
//socket關閉時的處理代碼
}
break;
default:
break;
}
}
最後感謝高成eason提供代碼。