1. 異步套接字編程:
Windows套接字在兩種模式下執(zhí)行I/O操作,阻塞和非阻塞。在阻塞模式下,在I/O操作完成前,執(zhí)行操作的Winsock函數(shù)會一直等待下去,不會立即返回程序(將控制權(quán)交還給程序)。而在非阻塞模式下,Winsock函數(shù)無論如何都會立即返回。采用異步套接字,可有效改善程序的運(yùn)行性能。
Windows Sockets為了支持Windows消息驅(qū)動機(jī)制,使應(yīng)用程序開發(fā)者能夠方便地處理網(wǎng)絡(luò)通信,它對網(wǎng)絡(luò)事件采用了基于消息的異步存取策略。
Windows Sockets的異步選擇函數(shù)WSAAsyncSelect()提供了消息機(jī)制的網(wǎng)絡(luò)事件選擇,當(dāng)使用它登記的網(wǎng)絡(luò)事件發(fā)生時(shí),Windows應(yīng)用程序相應(yīng)的窗口函數(shù)將收到一個消息,消息中指示了發(fā)生的網(wǎng)絡(luò)事件,以及與事件相關(guān)的一些信息。
在上一章中編寫的Chat程序中,因?yàn)榻邮粘绦蚍旁诹艘粋€線程中,所以雖然它是阻塞的,也沒有影響到主線程的運(yùn)行性能。
2. 編寫基于異步套接字的聊天室程序:
a. 因?yàn)?/span>MFC自帶的AfxSocketInit函數(shù)初始化支持的是1.1版本的套接字,不適合異步套接字,我們需要調(diào)用的是Winsock2版本的套接字,那么加載套接字庫的過程只能使用WSAStartup了。在CChatApp的InitInstance初始化函數(shù)中添加:
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 2, 2 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return;
}
if ( LOBYTE( wsaData.wVersion ) != 2 ||
HIBYTE( wsaData.wVersion ) != 2 ) {
WSACleanup( );
return;
}
b. 在StdAfx.h里添加#include <winsock2.h>,在setting里添加ws2_32.lib庫文件。
c. 給CChatApp類添加析構(gòu)函數(shù),在其中添加WSACleanup來終止對套接字庫的使用。
d. 給CChatDlg類添加成員變量SOCKET m_socket,并在構(gòu)造函數(shù)中初始化為0
e. 給CChatDlg類添加析構(gòu)函數(shù),添加:
if(m_socket) //判斷socket是否有值
closesocket(m_socket);
f. 創(chuàng)建初始化函數(shù)InitSocket(),代碼如下:
說明:在Winsock2版本中提供的WSASocket這樣一個擴(kuò)展方法用于創(chuàng)建套接字,對應(yīng)于socket方法;bind方法在winsock2中沒有提供相應(yīng)的擴(kuò)展方法。然后調(diào)用WSAAsyncSelect方法請求一個windows基于消息的網(wǎng)絡(luò)事件通知。
m_socket=WSASocket(AF_INET,SOCK_DGRAM,0,NULL,0,0);
if(INVALID_SOCKET==m_socket)
{
MessageBox("創(chuàng)建套接字失??!");
return FALSE;
}
SOCKADDR_IN addrSock;
addrSock.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
addrSock.sin_family=AF_INET;
addrSock.sin_port=htons(1234);
int retVal;
retVal=bind(m_socket,(SOCKADDR*)&addrSock,sizeof(SOCKADDR));
if(SOCKET_ERROR==retVal)
{
MessageBox("套接字綁定失??!");
return FALSE;
}
說明:WSAAsyncSelect方法第二個參數(shù)表示網(wǎng)絡(luò)事件發(fā)生時(shí)用來接收消息的窗口,第三個參數(shù)表示處理響應(yīng)的消息,第四個參數(shù)表示網(wǎng)絡(luò)事件類型,采用或操作。我們當(dāng)前采用讀這樣一個事件,網(wǎng)絡(luò)上一旦有數(shù)據(jù)到來的時(shí)候就會觸發(fā)這個事件,系統(tǒng)就會通過我們自定義的消息UM_SOCK來通知我們進(jìn)行處理if(SOCKET_ERROR==WSAAsyncSelect(m_socket,m_hWnd,UM_SOCK,FD_READ))
{
MessageBox("注冊網(wǎng)絡(luò)讀取事件失?。?/span>");
return FALSE;
};
return TRUE;
g. 消息響應(yīng)函數(shù)的處理:
1. 創(chuàng)建自定以的消息UM_SOCK,注意:在消息響應(yīng)函數(shù)的申明中還是要添加WPARAM和LPARAM參數(shù),因?yàn)榫W(wǎng)絡(luò)上的數(shù)據(jù)是通過這兩個參數(shù)傳遞給消息響應(yīng)函數(shù)進(jìn)行處理的。
2. 參看MSDN中WSAAsyncSelect方法的說明如下:
When one of the nominated network events occurs on the specified socket s, the application's window hWnd receives messagewMsg. The wParam parameter identifies the socket on which a network event has occurred. The low word of lParam specifies the network event that has occurred. The high word of lParam contains any error code.
3. WSARecvFrom函數(shù)的第二個參數(shù)可表示一個WSABUF的結(jié)構(gòu)體數(shù)組,可用于存放多個從網(wǎng)絡(luò)上接收到的信息塊,當(dāng)然也可以將所有信息放在一個結(jié)構(gòu)體中,然后將自己關(guān)心的信息塊取出,但這樣做比較麻煩,可以直接用WSABUF結(jié)構(gòu)體數(shù)組接收不同信息的塊即可。(沒有具體的實(shí)際操作經(jīng)驗(yàn))
在消息響應(yīng)函數(shù)中添加如下代碼
switch(LOWORD(lParam)) { //lParam的低字節(jié)指明網(wǎng)絡(luò)事件的類型
case FD_READ: //我們當(dāng)前只有讀取這樣一個事件,這是在WSAAsyncSelect中設(shè)定的
WSABUF wsabuf;
wsabuf.buf=new char[200]; //網(wǎng)絡(luò)上接收到的數(shù)據(jù)
wsabuf.len=200;
DWORD dwRead;
DWORD dwFlag=0;
SOCKADDR_IN addrFrom;
int len=sizeof(addrFrom);
CString str;
if(SOCKET_ERROR==WSARecvFrom(m_socket,&wsabuf,1,&dwRead,&dwFlag,(SOCKADDR*)&addrFrom,&len,NULL,NULL))
{
//下面的消息框基本不會運(yùn)行,因?yàn)?/span>WSARecvFrom方法是在有網(wǎng)絡(luò)數(shù)據(jù)的情況下才會被調(diào)用的,所以運(yùn)行到這段,基本是有數(shù)據(jù)的,做這樣一個判斷,只是出于編程風(fēng)格一致而已
MessageBox("接收數(shù)據(jù)失敗!");
return;
}
str.Format("from %s said:%s",inet_ntoa(addrFrom.sin_addr),wsabuf.buf);
CString temp;
GetDlgItemText(IDC_EDIT_RECV,temp);
temp+="\r\n"+str;
SetDlgItemText(IDC_EDIT_RECV,temp);
break;
}
h. 信息的發(fā)送:
DWORD dwIP; //控件上填寫的IP地址
CString strSend; //需要發(fā)送的信息內(nèi)容
WSABUF wsbuf; //需要發(fā)送的信息內(nèi)容
DWORD dwSend;
((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS1))->GetAddress(dwIP);
GetDlgItemText(IDC_EDIT_SEND,strSend);
SOCKADDR_IN addrTo;
addrTo.sin_addr.S_un.S_addr=htonl(dwIP);
addrTo.sin_family=AF_INET;
addrTo.sin_port=htons(1234);
//GetBuffer函數(shù)將CString類型轉(zhuǎn)換為char*類型
wsbuf.buf=strSend.GetBuffer(strSend.GetLength());
wsbuf.len=strSend.GetLength()+1; //多一個字節(jié)用于存放結(jié)束操作符
if(SOCKET_ERROR==WSASendTo(m_socket,&wsbuf,1,&dwSend,0,(SOCKADDR*)&addrTo,sizeof(SOCKADDR),NULL,NULL))
{
MessageBox("發(fā)送數(shù)據(jù)失敗!");
return;
}
else
{
SetDlgItemText(IDC_EDIT_SEND,"");
}
i. 綜上所述,創(chuàng)建一個基于winsock2版本的異步套接字的網(wǎng)絡(luò)聊天室程序有以下幾個步驟:
1. 調(diào)用WSAStartup加載套接字庫
2. 調(diào)用WSASocket創(chuàng)建套接字
3. 調(diào)用WSAAsyncSelect請求基于windows消息的網(wǎng)絡(luò)事件通知
4. 創(chuàng)建自定義的消息響應(yīng)函數(shù),來處理捕獲的網(wǎng)絡(luò)事件
5. 在消息響應(yīng)函數(shù)內(nèi)部調(diào)用WSARecvFrom來處理接收到的數(shù)據(jù)
6. 調(diào)用WSASendTo處理發(fā)送數(shù)據(jù)
3. 小結(jié):
當(dāng)前程序?qū)⑾⒌慕邮蘸桶l(fā)送放在了同一個線程中,即主線程中。如果采用先前使用過的阻塞套接字的話,程序會因?yàn)榻邮蘸瘮?shù)的調(diào)用導(dǎo)致主線程的暫停運(yùn)行,就無法及時(shí)的發(fā)送消息了。但是采用異步套接字可使得發(fā)送和接收放在同一個線程中而不會有相互的影響。
如果采用異步套接字加上多線程編程,則大大會提高網(wǎng)絡(luò)運(yùn)用程序的性能。
在第十四課中講winsock1.1的編程中一般將接收函數(shù)放在一個while循環(huán)中,來使得程序一直處于接收響應(yīng)狀態(tài),在異步套接字中,利用了在程序初始化的時(shí)候調(diào)用了WSAAsyncSelect方法來聲明程序的網(wǎng)絡(luò)的事件有相應(yīng)的自定義消息來處理,其真正的核心部分還是封裝在MFC中