📄 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 + -