?? 16.5.2 網絡聊天室程序的實現.txt
字號:
現數據發送的功能,結果如例 16-11所示。
iJlJ16-11
void CChatDlg::OnBtnSend()
1. // TODO: Add your control notification handler code here
2. DWORD dwIP;
3. CString strSend;
4. WSABUF wsabuf ;
5. DWORD dwSend;
6. int len;
7. SOCKADDR_IN addrTo;
8. ((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS1))->GetAddress(dwIP) ;
9. addrTo.sin_addr.S_un.S_addr=htonl(dwIP) ;
10. addrTo.sin_family=AF_INET;
11. addrTo.sin-port=htons(6000);
12. GetDlgItemText(IDC_EDIT_SEND, strSend);
13.len=strSend.GetLength();
14. wsabuf.buf=strSend.GetBuffer(len);
15. wsabuf.len=len+1;
16. if(SOCKET_ERROR==WSASendTo(m_socket , &wsabuf , 1 , &dwSend, 0 ,
17.
18. (SOCKADDR*)&addrTo, sizeof(SOCKADDR) , NULL, NULL))
19. {
20. MessageBox ( "發送數據失敗!");
21. return;
22. }
SetDlgItemText(IDC_EDIT_SEND,"");
在發送數據時,首先獲取F地址控件上的用戶輸入的對方E地址,這可以通過調用 GetDlgltem函數得
到IP地址控件,然后調用該控件對象的GetAddress函數得到E地址。
接著,定義一個地址結構(SOCKADDR_IN)變量: addr旬,并設置其成員,成員 sin-f缸回ly設置為
M-mET:成員sin_port是發送端端口號,因為接收端是在6000這個端口號等待接收數據的,所以發送
端也需要將端口設置為 6000;成員 sin_addr.S_uo.S_addr是對方E地址,并且是DWORD類型,雖然上
面剛剛獲得的E地址: dwIP也是DWORD類型,但是這里仍然需要調用 htonl函數進行轉換,因為這里
需要的是網絡宇節順序的地址,而dwIP變量保存的是主機字節順序的地址。
本例在發送數據時,也利用Winsock提供的擴展函數: WSASendTo來實現。首先定義一個 CString類
型的變量: strSend,然后調用 GetDlgltemText函數從發編輯框上獲取要發送的數據,并保存到
strSend變量中。又定義了一個變量: len,用來保存strSend對象中字符的數目。接著,因為WASSendTo
函數參數的需要,定義了一個WSABUF結構類型的變量: wsabuf,用來保存將要發送的數據和長度,
但是不能直接將strSend中的數據賦給wsabuf變量的buf成員,又因為該成員的類型是char*,而
strSend是CString類型。可以通過CString類提供的GetBu仔er函數將一個CString類型的對象轉換為
char*類型的對象并返回。同時,給wsabuf的len成員賦值時,在發送數據的數目上多加一個字節,
這主要是多傳送一個'舊'字符,這樣在接收端將接收到以"飛。"為結尾的數據。上述代碼中又定義
了一個DWORD類型的變量: dwSend,用來接收實際發送的字節數。接下來,調用 SetDlgltemText函
數將發送編輯框中的內容清空。然后,就調用 WSASendTo函數發送數據。同樣,可以對該函數的返
回值進行判斷,如果該函數返回的是 SOCKET_ERROR,說明發送數據操作失敗了,則提示用戶:"發送
數據失敗!",然后直接返回。
當然,與上一章的 Chat程序一樣,還可以將【發送】按鈕設置為默認按鈕,這樣用戶在輸入了將要
發送的數據之后,只要按下回車鍵就可以發送該數據了。
讀者可以自行測試本程序,將會Z發現本程序實現了上一章Chat程序相同的功能。
讀者應該注意到,本程序是在同一個線程中實現了接收端和發送端,如果采用阻塞套接字,可能會
因為WSARecvFrom函數的阻塞調用而導致線程暫停運行,所以本程序采用
了異步選擇機制在同一個線程中完成了接收端和發送端的功能,程序運行的效果與上一章
采用多線程技術實現的聊天室程序的結果是類似的。在編寫網絡應用程序時,
擇機制可以提高網絡應用程序的性能,如果再配合多錢程技術,將大大提高所編寫的網絡應用程序
的性能。
5.終止套接字庫的使用
最后需要為 CChatApp類增加一個析構函數,主要是要在此函數中調用 WSACleanup函數,終止對套
接字庫的使用,具體代碼如例16-12所示。
fJlJ16-12
未完先到此
CChatApp::-CChatApp()
{
// TODO: add construction code here,
// Place all significant initialization in InitInstance
WSACleanup() ;
同樣,為CChatDlg類也提供一個析構函數,在此函數中判斷m_socket變量是否有值,如果該套接字
有值,則調用closesocket函數關閉該套接字,釋放該套接字相關的資源,具體代碼如例 16-13所示。
例16-13
CChatDlg : :-CChatDlg()
if(m_socket) closesocket(m_socket) ;
6.利用主機名實現網絡訪問
上述Chat程序是通過輸入對方的E地址來向對方發送數據的,但是IP地址記憶起來不太方便,用戶可
能希望能夠通過指定對方的主機名來發送數據。但是需要注意,在填充 SOCKADDR一的這個地址結構
中sin_addr.S_un.S3ddr成員時,它需要使用E地址,所以程序中需要將主機名轉換為E地址,這可以
通過調用gethostbyname函數完成這種轉換。該函數的原型聲明如下所示:
struct hostent FAR * gethostbyname ( const char FAR * name );
gethostbyname函數從主機數據庫中獲取主機名相對應的IP地址,該函數只有一個參數,是一個指向
空終止的字符串。 gethostbyname函數返回 hostent結構體類型的指針,該結構體類型的定義如下
所示。
struct hostent {
char FAR * h_name;
char FAR * FAR * h_aliases;
short h_addrtype;
short h_length;
char FAR * FAR * h_addr_list;
hostent結構中最后一個成員:tLaddLlist,是一個指針數組,它的每一個元素存放的是一個以網絡
字節順序表示的主機E地址,其中 h_addclist[O]被定義為 h addr宏,這主要是為了兼容以前的軟
件。因為一臺主機可能會有多個 E地址,利用主機名查詢該主機的F時,可能會返回多個E地址,所
以需要一個指針數組來存放這些地址。讀者只需要記住,這個指針數組中的每個元素都是一個字符
指針,其所指向的內存中存放的數據是以網絡字節順序存放的IP地址。
下面,首先在 Chat程序的對話框中增加一個編輯框,允許用戶在基中輸入對方的主機名,并將其E
設置為IDC_EDIT_HOSTNAME。接著,在上述16-11所示代碼中的OnBtnSend函數第7行代碼后添加下述
代碼,以定
"‘ I 619
第 16線程罔步
義一個 CString對象類型的對象 strHostName,用來保存用戶輸入的主機名,和一個 HOSTENT結構
類型的指針,以便 gethostbyname函數使用。
CString strHostName;
HOSTENT* pHost;
然后,修改如例 16-11所示 OnBtnSend函數中對 addrTo變量的 sin-addrSJILS-addr成員進行賦值
的代碼(即用如例 16-14所示代碼替換上述如例 16-11所示 OnBtnSend函數中第 8和第 9行代碼):
例 16-14
工 f(GetDlgItemText(IDC_EDIT_HOSTNAME, strHostName), strHostName=="")
( (C工 PAddressCtrl*)GetDlgItem(IDC_IPADDRESS1))->GetAddress(dwIP); addrTo . sin_addr .
S_un . S_addr=htonl(dwIP) ;
else
pHost=gethostbyname(strHostName) ;
addrTo .sin_addr.S_un.S_addr=*((DWORD*)pHost->h_addr_list[O]);
這時,首先調用 GetDIgItemText函數獲得主機名編輯上的數據,然后判斷得到的主機名是否為空,
如果為空,則像先前一樣,從 E地址控件上得到 E地址,然后給地址結構體變量 addrTo中的地址成
員賦值:如果用戶輸入了主機名,就利用 gethostbyname函數根據主機名獲得 IP地址,之后,就可
以對地址結構體變量 addrTo中的地址成員 Csin_ad由. S_un.S_addr)賦值了。但這個成員需要的是
網絡字節順序表示的 u_long類型的地址,而雖然 gethostbyname函數返回的 IP地址也是以網絡字
節順序表示的,但其類型是 char* , 所以這里先從地址數組 C pHost->h_addclist )中將得到的 IP
地址取出來,可能有多個 E地址信息,這里使用第一個地址就可以了,該元素是 char*類型,并且
該指針所指向的內存中存放的就是以網絡字節順序表示的 IP地址。那么如何將一個 char*類型的數
據轉換為 u_Iong類型呢?我們知道,只要內存中存放的數據類型是兼容的,那么各種指針之間可以
相互轉換。所以,可以先將 char*轉換為 DWORD*類型,也就是 u_long*類型,然后就可以利用取值
符 C*)取出該指針所指向的內存中的數據了。對 u_Iong類型來說,其值占四個字節,因此這時讀取
一個 u_long類型的值,也就是取出了四個字節的數據。因為該內存中本來存放的就是網絡字節順序
的 IP地址,所以這時取出的這四個字節的數值正好就是 u_Iong類型的 IP地址。這就是筆者在前面
己經提到過的,在編程過程中,我們頭腦中要有一個相應的內存模型,這樣在對一些數據類型進行
轉換操作時就比較清楚了。
運行這時的 Chat程序,在主機名編輯框中輸入對方機器的主機名,井在發送數據編輯框中輸入數據,
然后按下回車鍵,即可在接收編輯框中看到接收到的數據。程序界面如圖 16.5所示。
620 I ~~~
vc..深λ詳解
l土山主 JLAJ睛'國 r~棚'
節ττI頁 H.llo
…一…~一一一一一
坐」
圖 16.5通過指定對方的主機名發送數據
讀者可以看到,在接收編輯框中,這時顯示的仍是發送方的 E地址,如果希望顯示發送方的主機名,
也就是說,在接收到對方發送的數據之后,需要將對方的 IP地址轉換為對方機器的主機名,這可以
通過另一個函數: gethostbyaddr來完成,該函數的原型聲明如下所示:
struct HOSTENT FAR * gethostbyaddr (
const char FAR * addr ,
int len,
int type
gethostbyaddr函數有三個參數,其中第一個參數是const char*類型,是一個指向以網絡字節順序
表示的地址的指針;第二個參數是地址長度,對于 AtmET地址族,地址長度必須是4個字節;最后一個
參數是地址類型, Windows平臺下必須設置為AF-INET。
gethostbyaddr函數將根據指定的 IP地址,獲取相應的主機信息。其返回值是一個 HOSTENT結構體
類型的指針。如果能找到相應的主機信息,那么返回的HOSTENT結構體中的h narne數據成員就是主
機名。
因此,在上述如例 16-10所示代碼中接收數據的消息響應函數: OnSock中,定義一個 HOSTENT結構
儂指針變量: pHost,并在調用 WSARecvFrom函數接收數據之后,調用 gethostbyaddr函數,并修改
進行數據格式化的那行代碼,具體代碼如下所示(即用下述代. 碼段替換上述如例 16-10所示OnSock
函數中第19行代碼):
HOSTENT *pHost; pHost=gethostbyaddr((char*)&addrFrom.sin_addr.S_un.S_addr,4,AF_ 工NET) ;
str.Format("%s說 :%s",pHost->h_n缸晤,wsabuf .buf) ; / / str. Format ( "%s說:%s" , inet_ntoa
(addrFrom. sin_addr) ,wsabuf. buf) ;
由于gethostbyaddr函數需要一個以網絡字節順序表示的地址,從SOCKADDR_IN地址結構體變量:
addrFrom中的sin-addrs-IIns-addr成員中可以取出一個u_long類型的以網絡字節順序表示的IP地
址數據,然而這里需要的是constch缸*類型。剛才已經提到,只
‘~4111 I 621
第16章線程同步
要我們清楚地知道數據的內存模型,就可以很容易地完成不同數據類型之間的轉換。這里,
addrFrom.sin_addr.S_un.S_addr成員是u_long類型,而需要的是const char飛則可以首先對該成員
進行取地址操作,得到的是一個 u_long*的數據,再利用 Cchar*)操作符將對該結果強制轉換即可。
在得到對方主機的信息后,通過HOSTENT結構體變量pHost的 h name成員取出主機名稱,并進行相應
的格式化。
再次運行 Chat程序,在主機名編輯框中輸入對方的主機名,并在發送數據編輯中輸入要發送的數據,
然后按下回車鍵,即可在接收編輯框中看到接收到的數據。程序界面如圖 16.6所示。可以看到,這
時顯示的是就是發送數據方的主機名。這時,也可以在IP地址控件中輸入 IP地址,然后發送數據,
同樣,在接收數據編輯框中的顯示的也是發送數據方的主機名。
事"
) 1
Ib.yond立」
叫阿
圖 16.6接收方顯示數據發送方的主機名
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -