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