?? 16.5.2 網絡聊天室程序的實現.txt
字號:
16.5.2 網絡聊天室程序的實現
下面采用基于消息的異步套接字實現前面第 15章中已經實現的網絡聊天程序。首先,新建一個基于
對話框的工程,工程名取為: Chat,并將該對話框資源上自動創建的控件全部刪除,然后添加一些
控件,本章實現的程序界面與第 15章 Chat程序的界面完全相同,所以可以直接復制后者己有的對
話框資源,具體的復制方法,本書前面章節中已經介紹過,這里不再贅述。
1.加載套接字庫與前面使用套接字編程一樣,首先需要加載套接字庫并進行版本協商。上一章我們
使用了 MFC函數: MxSocket1nit來完成這一任務,但是該函數只能加載1. 1版本的套接字庫,本程
序中需要使用套接字庫 2.0版本中的一些函數,因此應該調用 WSAStartup函數初始化
程序所使用的套接字庫。同樣,應該在應用程序類,即在 CChatApp類的InitInstance函數中加載套
接字,因此,在該函數的開始位置添加下述如例 16-6所示的代碼。
例 16-6
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 2 , 2 );
err = WSAStartup( wVersionRequested, &wsaData );
i f ( err ! = 0 ) {
return FALSE;
if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 ) {
WSACleanup( );
return FALSE;
如果調用成功, WSAStartup函數將返回 0值;否則,它返回一個預定義的錯誤代碼。因此,在上述
如例 16-6所示代碼中,對此函數的返回值進行檢測,如果不是 O值,說明 WSAStartup函數調用失
敗, Initlnstance函數立即返回 FALSE值。
本例加載 2.2版本的 Winsock庫,如果版本不合要求,就調用 WSACleanup函數終止對套接字庫的使
用,然后 Ini tIn stance函數立即返回 FALSE值。
同樣,因為調用了 Winsock 2.0版本中的函數,所以還需要包含相應的頭文件: winsock2 .h。與上
一章的 Chat程序一樣,將該文件的包含語句放在 stdafx.h文件中,即在該文件中添加下面這條語
句:
#include <winsock2.h>
當然,還需要為 Chat工程鏈接 ws2_32.lib文件。
2.創建并初始化套接字
接下來創建并初始化套接字,實現步驟與上一章的 Chat程序一樣,為 CChatD lg類增加一個 SOCKET
類型的成員變量: m_socket,即套接字描述符,并將其訪問權限設置為私有的。然后為 CChatDlg
類添加一個 BOOL類型的成員函數: InitSocket,用來初始化該類的套接字成員變量,該函數的實現
代碼如例 16-7所示。
例 16-7
BOOL CChatDlg : : InitSocket()
m_socket=WSASocket(AF_INET, SOCK_DGRAM , 0,NULL , 0, 0) ;
if(INVALID_SOCKET==m_socket)
MessageBox ( "創建套接字失敗!");
return FALSE;
SOCKADDR_IN addrSock;
addrSock . sin_addr.S_un.S_addr=htonl(INADDR_ANY) ;
addrSock.sin_family=AF_INET;
addrSock .sin-port=htons(6000);
if(SOCKET_ERROR==bind(m_socket , (SOCKADDR*)&addrSock , sizeof(SOCKADDR
) ) )
MessageBox("綁定失敗!");
return FALSE;
}
if(SOCKET_ERROR==WSAAsyncSelect(m_socket ,m_hWnd ,UM_SOCK,FD_READ))
MessageBox ( "注冊網絡讀取事件失敗!");
return FALSE;
return TRUE;
上一章示例中使用的是 socket函數創建套接字,這里使用 Winsock庫中的擴展函數 z WSASocket
來完成這一功能。
注意:在 Windows Sockets中,對增加的擴展函數來說,櫚樹 WSA前
綴。
在上述如例 16-7所示代碼中,接著對 WSASocket函數的返回值進行判斷,如果是 INVALID _ SOCKET,
則說明該函數調用失敗,于是提示用戶:"創建套接宇失敗!",井讓 InitSocket函數立即返回,返回
值是 FALSE。
如果套接字創建成功,接下來,就要將該套接宇綁定到某個 IP地址和端口上, WinSock
2.0版本的庫中沒有提供 bind函數的擴展函數,所以這里仍使用該函數來完成套接字的綁定。首先
定義一個地址結構體 (SOCKADDR_IN)變量 : addrSock,并為其成員賦值。最后調用 bind函數,并
對其返回值進行判斷,如果是 SOCKET_ERROR,說明 bind函數調用出錯了,就提示用戶:"綁定失敗!"。
接下來,就可以調用 WSAAsyncSelect函數請求一個基于 Windows消息的網絡事件通知,該函數的第
一個參數就是標識請求網絡事件通知的套接字描述符:本例讓對話框窗口
! 接收消息,因此第二個參數就是該對話框的窗口句柄,即 CChatDlg類的 m hWnd成員:第三個參
數指定了一個自定義的消息 (UM_SOCK),一旦指定的網絡事件發生時,操作系統就會發送該自定義
的消息通知調用線程:第四個參數是注冊的事件,本例注冊了一個讀取事件 (FD_READ)。這樣,一旦
有數據到來,就會觸發 FD阻AD事件,系統就會通過 UM SOCK這則消息來通知調用線程,于是,在該
消息的響應函數中接收數據,就可以接收到數據了。上述代碼中,對 WSAAsyncSelect函數的返回值
作以判斷,如果函數調用失敗,則提示用戶:"注冊網絡讀取事件失敗!",并返回 FALSE。
如果上述操作全部成功,則 InitSocket函數返回 TRUE。這就是在InitSocket函數中對套接字進行
初始化的處理過程。然后可以在 CChatDlg類的 OnlnitDialog函數中調用這個函數,以便使程序完
成套接字的初始化工作。因此,在 OnlnitDialog函數最后的 return語句之前添加下面這條語句:
InitSocket() ;
然后在 CChatDlg類的頭文件中,定義自定義消息: UM_SOCK,定義代碼如下所示:
#define UM_SOCK WM_USER+1
3.實現接收端功能
這里應注意,在注冊的事件發生后,操作系統向調用進程發送相應的消息時,還會將該事件相應的
信息一起傳遞給調用進程,這些信息是通過消息的兩個參數傳遞的。因此在定義 UM_SOCK消息響應
函數時應帶有 wParam和 lParamp這兩個參數。在 CChatDlg類的頭文件中添加如例 16-8所示代碼中
加灰顯示的那條代碼,即 UM SOCK消息響應函數
原型的聲明。
例IJ 16-8
// Generated message map functions 11{{AFX_MSG(CChatDlg)
virtual BOOL OnlnitD工 alog ( ) ;
afx_msg void OnSysCommand(UINT nID, LPARAM lParam) ;
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDraglcon();
II}}AFX_ MSG
afx_msg void OnSock(WPARAM, LPARAM); DECLARE_MESSAGE_MAP()
接下來在 CChatD lg類的源文件中添加 UM SOCK 消息映射,即添加如例 16-9所示代碼中加灰顯示
的那條代碼。
例 16-9
BEGIN_MESSAGE_MAP(CChatD1g , CDia1og)
11{{AFX_MSG_MAP(CChatDlg)
ON_WM_SYSCOMMAND()
ON_WM_PAINT ()
ON_WM_QUERYDRAG工 CON( )
I /} } AFX_MSG_MAP
ON_MESSAGE(UM_SOCK, OnSock)
END_MESSAGE_MAP()
最后就是該消息響應函數的實現,具體代碼如例 16-10所示。例 16-10
void CChatDlg : : OnSock(WPARAM wParam, LPARAM lParam)
{
1 . switch(LOWORD(lParam))
2. {
3 . case FD READ :
4. WSABUF wsabuf ;
5 . wsabuf . buf=new char[2001;
6 . wsabuf . len=200;
7. DWORD dwRead;
8. DWORD dwFlag=0;
9. SOCKADDR_IN addrFrom;
10. int len=sizeof(SOCKADDR) ;
11. CString str;
12. CString strTemp ;
if(SOCKET_ERROR==WSARecvFrom(m_socket, &wsabuf,1, &dwRead,
&dwFlag ,
13. (SOCKADDR*)&addrFrom, &len,NULL ,NULL))
14.
15. MessageBox ( "接收數據失敗!");
.
第16
16. delete[] wsabuf.buf;
17. return;
18. }
19. str.Format("%s說:%s",inet_ntoa(addrFrom.sin_addr),wsabuf.buf);
20. str+="\r\n";
21. GetDlgItemText(IDC_EDIT_RECV,strTemp);
22. str+=strTemp;
23. SetDlgItemText(IDC_EDIT_RECV,str);
24. delete[] wsabuf.buf;
25. break;
26. }
讀者可能會認為,既然我們只請求了一個基于Windows消息的網絡事件通知,即只請求了 FD_阻AD
這種網絡事件,那么在OnSock函數就可以直接調用WSARecvFrom函數接收數據了。這種想法本身并沒
有錯誤。但是需要注意,在基于套接字上請求網絡事件通知時,可以同時請求多個網絡事件,也就
是說,不但可以請求而'_READ網絡讀取事件,還可以同時請求 FD_WRITE網絡寫入事件,那么在對指
定的消息進行處理時,即在相應的消息響應函數中,就可以根據當前發生的網絡事件進行相應的處
理。在本例中,因為只請求了m-阻AD網絡讀取事件,所以在在OnSock函數可以直接調用WSARecvFrom
函數去接收數據。但這并不是一種良好的代碼設計風格。作為一種良好的代碼風格,應該在 此函數
中判斷是否是網絡讀取事件發生了,如果是,然后再讀取數據。
一旦網絡事件發生時,系統會發送自定義的消息通知調用線程,隨該消息發送的信息都是隨著捎息
的兩個參數發送的。當一個指定的網絡事件在指定的套接宇上發生時,應用程序窗口接收到指定的
消息,該消息的 wParam參數標識已經發生網絡事件的套接字, lParam參數的低位字標識己經發生
的網絡事件,高位字包含了任何錯誤代碼,也就是說通過取出lParam參數的低位字,就可以知道當
前發生的網絡事件類型。
因此在上述例16-10所示代碼的OnSock函數中,首先使用switch語句,利用LOWORD宏取出lParam參數
的低位字,井判斷是否是網絡讀取事件發生了,如果是,就調用Winsock庫的擴展函數WSARecvFrom
接收數據。在調用該函數之前,先根據其需要的參數定義一些變量。首先定義了一個WSABubf結構體
類型的變量: wsabuf,并為其成員賦值,將其緩沖區設置為2001個字節:接著,定義一個dwRead變量,
用來保存實際接收到的數據長度:又定義一個 SOCKADDR_IN結構體變量: addrFrom.用來接收發送方
的地址信息,接著定義了一個變量len,并用SOCKADDR IN地址結構體長度對其初始化。然后,就調
用WSWRecvFrom函數接收數據,第一個參數是套接宇描述符:第二個參數指定接收數據的WSABUF結構
體數組:第三個參數指定用來接收數據的WSABUF結構體變量的數目:第四個參數保存實際接收到的數
據:第五個參數是標志位,本例指定為o即可:第六個參數是用來接收發送方地址信息的地址結構體指
針;第七個參數是上一參數的長度:本例將最后兩個參數都設置為NULL。另外,因為數據接收操作是
在網絡讀取事件發生時進行的,所以一般這種讀取操作都能成功,但是為了代碼風格統一,在程序
中也可以對 WSARecvFrom函數的返回值進行判斷,如果出錯,利用MessageBox函數顯示一個消息框,
提示用戶:"接收數據失敗!",然后釋放己分配的內容,最后直接返回。
上述例 16-10所示代碼中的 OnSock函數在接收到數據后,將該數據進行格式化。首先,取出發送端
地址(保存在 addrFrom變量的 sin addr成員中),并將它轉換為點分十進制表示的字符串:接著,從
對話框的接收編輯框中取出己有的數據,保存到 strTemp變量中:接下來,將當前接收到的數據加上
回車換行符后,再加上 strTemp變量中保存的以前接收到的數據,然后調用 SetDlgItemText函數將
所有數據都放置到接收編輯框上。最后一定不要忘記釋放己分配的內存。
另外,為了讓接收編輯框控件支持回車換行,還需要設置其 Multiline屬性。以上就是數據的接收
部分。
4.實現發送端功能
接下來,編寫發送端程序。雙擊 Chat程序主界面對話框資源上的【發送】按鈕, VC++
開發環境將為該按鈕自動生成一個按鈕單擊命令響應函數: OnBtnSend,然后在此函數中添加代碼實
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -