3.填空题
第15章 MFC网络通信
互联网网络通信的基础是TCP/IP,即传输控制协议(Transmission Control Protocol)和网络互联协议(Internet Protocol)。每台连接在互联网上的计算机,都分配一个32位编号的IP地址。就如同一条大街上每个大厦和房屋都有一个门牌号,这样邮递员才能按照门牌号将包裹准确送达。按照TCP/IP规定,32位IP地址用4个字节来表示,例如,“192.168.1.100”。
在Windows中查看本机IP地址的方法是,在控制台内输入“ipconfig”命令,如图15-1所示。

图15-1 通过命令查看本机网络配置
在“本地连接”的属性设置对话框中,双击“Internet协议(TCP/IP)”,如图15-2所示。
每台计算机的IP地址必须是独立的,在同一个网络中不能有重复的IP地址,否则会造成IP冲突,如图15-3所示。

图15-2 查看网络邻居属性

图15-3 TCP/IP属性设置
在TCP/IP中计算机软件通信必须依赖于通信端口,端口号的范围是0~65535。每个进程启动后可以申请一个或多个端口号,但在一台计算机内每个端口号同时只能被一个进程占用,否则就会造成端口冲突。如果把网络中多台计算机的IP地址比作火车站,那么端口就是每个火车站的站台。如同要去北京的从4号站台上车,去深圳的从5号站台上车等一样,一台计算机上QQ和360等软件分别使用不同的端口与服务器通信。
第1节 TCP/IP的层次
从整体架构上TCP/IP分为四个层次,即网络接口层、网络层、传输层和应用层。TCP/IP也可以再细分为OSI七层结构,包括物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。在计算机网络产生之初,每个计算机厂商都有一套自己的网络体系结构的概念,它们之间互不相容。国际标准化组织(ISO)在1979年建立了一个分委员会,为此专门研究出一种用于开放系统互连的体系结构,即OSI(Open Systems Interconnection)。TCP/IP两种分层结构的关系见表15-1。
表15-1 TCP/IP两种分层结构的关系

TCP/IP包括TCP、IP、UDP、ICMP、RIP、TELNETFTP、SMTP、ARP、TFTP等许多子协议,这些协议一起称为TCP/IP协议族。OSI七层协议及子协议族见表15-2。
表15-2 OSI七层协议及子协议族

第2节 简单UDP通信
Socket的中文称为“套接字”,是专门用于开发通信程序的句柄。在软件通信过程中,Socket管理端口的创建、侦听、连接和数据收发等功能。
创建一个工程名为“u”的对话框程序,用于演示创建UDP端口并进行简单的数据通信。
1)在主对话框中增加一些控件,如图15-4所示。

图15-4编辑主对话框资源
2)修改控件属性,见表15-3。
表15-3 主对话框的控件属性

3)新建一个CSocket派生类CSocku,用于截获接收到的网络通信数据,如图15-5所示。

图15-5 添加新类
4)在新建的CSocku类的头文件中包含基类头文件。

5)在CSocku类中添加虚函数OnReceive,用于截获接收到的网络数据,如图15-6所示。

图15-6 添加虚函数
6)修改虚函数代码。

7)在主对话框类的头文件中,添加一个CSocku类型的成员变量。

8)修改主对话框初始化函数代码。

9)编译并运行,测试代码,启动后弹出Socket错误号码是10093。
10)在主菜单中,执行Tools->Error Lookup命令,如图15-7所示。

图15-7 查看错误代码
11)在错误查找对话框中,可以看到错误原因是未执行Socket初始化,如图15-8所示。

图15-8 错误代码说明
12)在App类的进程启动函数InitInstance开头,添加一行代码初始化Socket。

13)在主对话框类中,添加“发送(&S)”按钮的消息映射函数OnOK。


14)编译并运行,测试代码,如图15-9所示。
输入本机IP地址以及8828的端口号后,在输入编辑框内输入一些文字再按<Enter>键。发送数据的目标是本机,因此,在本机接收到了单击“发送”按钮时发出的数据。

图15-9 查看运行结果
15)继续修改主对话框类的OnOK函数,在函数末尾追加以下代码。

16)再次修改CSocku类中的OnReceive函数,将收到的数据和对方IP添加到历史编辑框内。


17)编译并运行,测试代码,如图15-10所示。
将生成的可执行文件在多台计算机中运行,分别输入不同的IP地址进行数据发送测试。

图15-10 查看运行结果
18)如果只有一台计算机,也可以使用VMware软件安装一台虚拟机,如图15-11所示。

图15-11 使用虚拟机进行网络测试
第3节 简单TCP通信
TCP的通信过程比UDP复杂得多,必须依据C/S架构基础进行开发,一般TCP通信软件都分为服务器端和客户端两部分。.TCP服务器端由一个侦听Socket和多个用于应答的Socket组成,每个应答Socket与客户端建立一对一的数据流通道。客户端程序只包含一个连接Socket,它在成功连接服务器后与某一个应答Socket进行一对一的数据通信。
服务器的侦听Socket就如同公司的前台接待员,不参与客户的订单买卖。当有上门联系的客户时引荐给客户经理,每一个应答Socket就代表一个客户经理。具体的数据交互过程,必须由应答Socket与客户端之间一对一地进行,如图15-12所示。

图15-12 TCP服务器的侦听和应答结构
创建工程名为“Server”和“Client”的两个对话框程序,用于演示通过CSocket类实现简单的TCP数据通信。在创建应用程序向导的第2页,选中“Windows Sockets”复选框。该选项在创建工程时自动向stdafx.h预编译头文件中增加afxsock.h头文件,并在App类的InitInstance进程启动函数中调用AfxSocketInit函数,如图15-13所示。

图15-13 MFC应用程序向导
1)创建工程完毕后查看stdafx.h预编译头文件,如图15-14所示。

2)再查看InitInstance函数。

图15-14 程序向导自动添加的代码
3)在Server工程中创建一个CSocket类的派生类CListenSocket,如图15-15所示。

图15-15 添加新类
4)用同样的方法再创建一个CScoket类的派生类CClientSocket,如图15-16所示。

图15-16 创建后的CSocket派生类
5)在CServerApp类的头文件中,添加一个指针链表类型的成员变量。

6)在CListenSocket类中添加虚函数OnAccept,用于截获客户端的连接。

7)在CClientSocket类中添加虚函数OnReceive,用于截获客户端发来的网络数据。


8)在CClientSocket类中添加虚函数OnClose,用于监测某个客户端是否断开了连接。

9)在主对话框类中,添加CListenSocket类型的成员变量。

10)修改主对话框初始化函数代码,创建Socket并开始侦听。

11)在Client工程的主对话框中增加一些控件,如图15-17所示。

图15-17 编辑主对话框资源
12)修改控件属性,见表15-4。
表15-4 主对话框的控件属性

13)创建一个CSocket的派生类CConnectSocket,如图15-18所示。

图15-18 创建后的CSocket派生类
14)在CConnectSocket类中添加虚函数OnReceive,用于截获服务器发来的网络数据。


15)在主对话框类的头文件中,添加一个CConnectSocket类型的成员变量。

16)修改主对话框初始化函数的代码。

17)在主对话框类中,添加“发送(&S)”按钮的消息映射函数OnOK。

18)编译并运行,测试服务器和客户端代码,如图15-19所示。
首先开启服务器程序,再在一台或多台计算机开启多个客户端程序进行测试,很明显这是一个简易的聊天室软件框架。

图15-19 查看运行结果
第4节 一般TCP通信
任何通信协议的格式都是由命令号和附带数据两部分组成,包括网络通信协议和硬件通信协议。命令号也叫协议号或协议编号,就是用一个数字或字符串通知对方执行一个对应的命令。大部分协议在发送命令号后还要发送附带数据,例如,登录协议要附带账号和密码等数据。
创建工程名为“s”和“c”的两个对话框程序,演示C/S结构的网络版信息管理软件开发过程。客户端通过TCP对服务器上的数据进行增、删、改、查等功能。在应用程序向导的第2页,选中“Windows Sockets”复选框。如果没有选择该选项,那么可以在stdafx.h中包含“afxsock.h”头文件,并在App类的InitInstance函数中调用AfxSocketInit函数。
1)在服务器“s”工程的CSApp类头文件中,添加一个用于数据管理的链表变量。


2)在“s”工程中创建两个CSocket类的派生类CListenSocket和CClientSocket,如图15-20所示。

图15-20 创建后的CSocket派生类
3)在侦听类(CListenSocket)中添加虚函数OnAccept,用于截获客户端的连接。

4)在应答类(CClientSocket)中添加虚函数OnClose,在客户端连接断开后及时清理堆空间。

5)在应答类(CClientSocket)中添加虚函数OnReceive,用于接收和解析客户端协议。


6)在CClientSocket类中,添加两个私有函数AddData和Browse作为协议处理函数。

7)在主对话框类的头文件中,添加一个CListenSocket类型的成员变量。

8)修改主对话框初始化函数的代码。


9)编译并运行,测试服务器端代码。
服务器端代码就完成了,接下来编写客户端代码。
10)在客户端“c”工程的主对话框中增加一些控件,如图15-21所示。

图15-21 编辑主对话框资源
11)修改控件属性,见表15-5。
表15-5 主对话框的控件属性

12)通过类向导为列表控件建立控件型关联变量,如图15-22所示。
(https://www.daowen.com)
图15-22 添加控件型关联变量
13)在主对话框类的头文件中,添加一个CSocket类型的成员变量,以及通信数据和协议定义。

14)修改主对话框初始化函数的代码。

15)在主对话框类中添加一个私有成员函数OnRefresh,用于从服务器接收数据来刷新列表。


16)建立“添加”按钮的消息映射函数OnAdd。

17)编译并运行,测试服务器端和客户端代码,如图15-23所示。
首先开启服务器程序,再在一台或多台计算机开启多个客户端程序进行测试。所有客户端添加的数据都统一保存在服务器上,并且在各个客户端中都能正常显示。

图15-23 查看运行结果
第5节 UDP通信
UDP的格式也是由命令号和附带数据两部分组成。但是UDP必须先使用结构体对命令号和数据进行打包后再发送,因为UDP不执行正确性检查。如果不打包则可能出现用户协议或协议数据不能完整到达,或者到达的顺序颠倒等问题。
创建工程名为“f”的对话框程序,用于演示UDP打包以及局域网P2P软件的开发过程。在应用程序向导的第2页,选中“Windows Sockets”复选框。如果没有选择该选项,那么也可以在stdafx.h中包含“afxsock.h”头文件,并在App类的InitInstance函数中调用AfxSocketInit函数。
1)添加一个异步通信派生类CSocku,用于收发UDP网络协议数据,如图15-24所示。

图15-24 添加新类
2)在CFApp类的头文件中,添加一些数据定义和类成员变量。


3)在主对话框中只使用一个列表控件(ID是IDC_LIST),用于显示在线的计算机,如图15-25所示。

图15-25 编辑主对话框资源
4)通过类向导为列表控件建立控件型关联变量,如图15-26所示。

图15-26 添加控件型关联变量
5)修改主对话框初始化函数的代码。


6)在CFApp类中添加一个公有成员函数InitInfo,用于初始化本机信息。

7)在CSocku类中添加一个虚函数OnReceive,用于接收UDP通信数据。


8)在主对话框类中,添加一个公有成员函数InsertHost。

9)编译并运行,测试代码,如图15-27所示。
在多台计算机上运行可执行程序,列表显示的是所有在线用户的信息。

图15-27 查看运行结果
10)在CFApp类中添加虚函数ExitInstance,用于进程退出时群发离线消息。

11)在CSocku::OnReceive函数的switch语句中,添加一个case条件。

12)在主对话框类中,添加一个公有成员函数RemoveHost。


13)编译并运行,测试代码。
在多台计算机上运行启动和关闭进程,查看用户上线和下线时列表是否显示正常。
14)在资源视图中插入一个对话框资源,用于向其他在线用户发送文字消息,如图15-28所示。

图15-28 编辑聊天对话框资源
15)修改对话框ID为IDD_CHAT_DLG,修改外观和字体并插入一些控件,见表15-6。
表15-6 聊天对话框的控件属性

16)创建与IDD_CHAT_DLG关联的CDialog派生类,并添加WM_INITDIALOG消息映射函数。

17)在主对话框类中,添加“发送(&S)”按钮的消息映射函数OnOK。


18)在主对话框类中添加双击列表项(NM_DBLCLK)的消息反射函数。


19)在CSocku::OnReceive函数的switch语句中,添加一个case条件。

20)在主对话框类中,添加一个公有成员函数OnText。

21)编译并运行,测试代码。在多台计算机上相互发送聊天文字进行测试,如图15-29所示。

图15-29 查看运行结果
第6节 TCP的短连接模式
TCP开发可以分为长连接和短连接两种模式。长连接就是客户端与服务器端一直保持连接状态,直到客户端进程退出时才断开连接。短连接就是客户端每次发送请求前连接服务器,完成一系列请求和应答之后立即断开连接。一般客户端数量较少而且与服务器通信频繁的情况下使用长连接,例如,第3节的C/S结构项目使用的就是长连接。如果每个客户端与服务器的通信并不频繁,而且客户端数量非常庞大的情况下则最好使用短连接模式。TCP短连接模式在保证稳定传输的前提下,大大降低了服务器的资源占用。例如,Web网站的HTTP服务使用的就是短连接模式,每次下载完成一个网页后立即关闭与服务器的连接。
打开第4节建立的客户端工程“c”继续修改代码,用于演示TCP短连接模式的协议开发。
1)修改主对话框头文件cDlg.h,添加一个CSocket类的派生类CSockc。


2)删除头文件中用于长连接的成员变量m_sock,并删除对话框初始化时的长连接代码。

3)使用短连接类CSockc修改OnRefresh和OnAdd函数的代码。


4)编译并运行,测试代码。先启动服务器程序,再启动多个客户端进行并发测试。
在客户端数量较少的情况下,测试短连接模式的C/S架构通信程序还体现不出太大的优势。但是当数量庞大的客户端程序与服务器通信时,例如,几万到几百万的客户端程序,使用短连接模式可以大大降低服务器的资源消耗。
第7节 相关类库介绍
1)CAsyncSocket类,如图15-30所示。CAsyncSocket类的常用成员见表15-7。
CAsyncSocket类封装了Windows套接字API,提供了面向对象的网络通信功能。

图15-30 CAsyncSocket类
①TCP服务器端通信流程。
a)调用CAsyncSocket::Create函数,创建套接字并绑定端口。
b)调用CAsyncSocket::Listen函数,开始侦听,等待客户端连接。
c)调用CAsyncSocket::Accept函数,接收客户端连接并获取用于数据收发的套接字。
d)调用CAsyncSocket::Send/Receive函数,发送数据到客户端或接收客户端数据。
e)调用CAsyncSocket::Close函数,关闭数据收发套接字和侦听套接字。
②TCP客户端通信流程。
a)调用CAsyncSocket::Create函数,创建套接字并绑定端口(客户端一般使用随机端口)。
b)调用CAsyncSocket::Connect函数,使用套接字与服务器端连接。
c)调用CAsyncSocket::Send/Receive函数,发送数据到服务器端或接收服务器端数据。
d)调用CAsyncSocket::Close函数,关闭套接字并断开与服务器的连接。
③UDP通信流程。
a)调用CAsyncSocket::Create函数,创建套接字并绑定端口(指定SOCK_DGRAM类型)。
b)调用CAsyncSocket::SendTo/ReceiveFrom函数,发送数据到其他计算机或接收其他计算机发来的数据。
c)调用CAsyncSocket::Close函数,关闭套接字。
表15-7 CAsyncSocket类常用成员

2)CSocket类,如图15-31所示。CSocket类的常用成员见表15-8。

图15-31 CSocket类
CSocket类是从CAsyncSocket类派生而来的,它继承了基类中的Receive、Send、ReceiveFrom、SendTo和Accept等所有操作函数。CSocket对象在执行Receive、ReceiveFrom和Accept函数时是阻塞的,而CAsyncSocket对象在执行以上函数时是非阻塞的,会返回WSAEWOULDBLOCK错误。当以上函数正在以阻塞模式执行时,如果调用CancelBlockingCall函数,则该函数调用将因为WSAEINTR错误而终止。
表15-8 CSocket类的常用成员

本章作业
1.测试题
测试本章列表中的CAsyncSocket类和CSocket类成员函数,每个按钮对应测试一个类成员函数。
2.上机作业
1)完善第5节中的局域网P2P软件。
①开发个人资料修改功能,包括姓名、性别、年龄以及头像和备注信息等。
②把修改好的个人资料群发给每个在线用户,每次群发信息时以姓名代替计算机名。
③在列表中的每个在线用户上单击鼠标右键,在弹出的快捷菜单中内容包括发送信息和查看个人资料等。
④使用TCP短连接模式开发文字发送协议,因为UDP发送文字会发生丢包问题。
⑤思考如何实现新建用户组、加入组和分组显示在线用户的功能。
2)完善第3节中的聊天室软件。
①使用TCP长连接模式开发用户注册、登录、注销等功能。
②使用TCP长连接模式开发用户聊天室的创建、删除和列表浏览功能。
③使用TCP长连接模式开发用户进入、退出聊天室和在聊天室内发言的功能。
④使用TCP长连接模式开发获取聊天室内人员信息列表、查看个人资料和修改个人资料的功能。
3)完善第6节中信息管理系统的客户端和服务器端(参照第5章中的单机版信息管理软件“QQ”)。
①使用TCP短连接模式开发登录账户信息管理,包括账户名称、登录密码和管理权限等信息管理。
②使用TCP短连接模式开发账户添加、删除、修改和登录功能,账户不存在或密码不匹配无法登录。
③只有账户名和密码匹配才能进入主界面,主界面包括账户管理和信息管理两部分。
④普通权限的账户登录只能进行信息管理操作,高级权限的账户登录还可以进行账户信息管理操作。
3.填空题
1)互联网网络通信的基础是TCP/IP,即传输控制(____)和网络互联协议(____)。
2)每台计算机的____必须是独立的,在同一个网络内不能有重复的____,否则会造成____。
3)每个进程启动后可以申请一个或多个____,但在一台计算机内每个____同时只能被一个进程占用,否则就会造成____。
4)从整体架构上TCP/IP分为四个层次,即____、____、____和____。
5)TCP/IP也可以再细分为OSI七层结构,包括____、____、____、____、____、____和____。
6)服务器的____就如同公司的前台接待员,不参与客户的订单买卖。当有上门联系的客户时引荐给客户经理,每一个____就代表一个客户经理。具体的数据交互过程,必须由____与客户端之间一对一地进行。
7)在创建应用程序向导的第2页,选中“Windows Sockets”复选框。该选项在创建工程时自动向stdafx.h预编译头文件中包含____头文件,并在App类的InitInstance进程启动函数中调用____函数。
8)任何通信协议的格式都是由____和____两部分组成,包括网络通信协议和硬件通信协议。____也叫____或____,就是用一个数字或字符串通知对方执行一个对应的命令。大部分协议在发送____后还要发送____,例如,登录协议要附带账号和密码等数据。
9)UDP的格式也是由____和____两部分组成。但是UDP必须先使用结构体对____和____进行打包后再发送,因为UDP不执行正确性检查。如果不打包则可能出现用户协议或协议数据不能____,或者到达的____等问题。
10)TCP开发可以分为____和____两种模式。____就是客户端与服务器端一直保持连接状态,直到客户端进程退出时才断开连接。____就是客户端每次发送请求前连接服务器,完成一系列请求和应答之后立即断开连接。
11)TCP服务器端通信流程。
①调用CAsyncSocket::____函数,创建套接字并绑定端口。
②调用CAsyncSocket::____函数,开始侦听,等待客户端连接。
③调用CAsyncSocket::____函数,接收客户端连接并获取用于数据收发的套接字。
④调用CAsyncSocket::____/____函数,发送数据到客户端或接收客户端数据。
⑤调用CAsyncSocket::____函数,关闭数据收发套接字和侦听套接字。
12)TCP客户端通信流程。
①调用CAsyncSocket::____函数,创建套接字并绑定端口(客户端一般使用随机端口)。
②调用CAsyncSocket::____函数,使用套接字与服务器端连接。
③调用CAsyncSocket::____/____函数,发送数据到服务器端或接收服务器端数据。
④调用CAsyncSocket::____函数,关闭套接字并断开与服务器的连接。
13)UDP通信流程。
①调用CAsyncSocket::____函数,创建套接字并绑定端口(指定SOCK_DGRAM类型)。
②调用CAsyncSocket::____/____函数,发送数据到其他计算机或接收其他计算机发来的数据。
③调用CAsyncSocket::____函数,关闭套接字。
14)请在表15-9中填写CAsyncSocket类的成员说明。
表15-9 CAsyncSocket类的成员说明

(续)

15)请在表15-10中填写CSocket类的成员说明。
表15-10 CSocket类的成员说明
