第7章 网络编程
学习目标 了解网络编程的方法 理解流式套接字和数据报套接字的区别 理解Windows Sockets的工作原理 了解两种不同的通信方式 掌握利用MFC CSocket类进行网络编程的方法 了解利用MFC CAsyncSocket类进行网络编程的方法
7.1 网络编程概述 7.1.1 Windows Sockets规范 7.1 网络编程概述 7.1.1 Windows Sockets规范 Windows Sockets(即WinSock)规范是90年代初Microsoft公司联合其他几家大公司共同制定的—套在Windows下的二进制兼容网络编程接口规范。 Windows Sockets规范制定的本意是将基础网络抽象出来 。 遵守Windows Sockets规范的网络软件,称之为与windows Sockets兼容。
7.1.2 VC++ 2005网络编程 利用Windows Sockets 网络编程的方法 1.利用Windows Sockets API进行网络编程 麻烦,但灵活 2.利用MFC提供的WinSock类进行网络编程 在MFC中,提供了两个类用来实现对Windows Socket API的封装,从而实现网络编程
(1)CAsyncSocket类 CAsyncSocket,顾名思义是指采用了异步(非阻塞)套接字的类。该类主要是提供给那些具有一定网络编程经验,希望同时拥有Winsock API编程的灵活性和类库编程便利性的用户的。 (2)CScoket类(阻塞) CScoket类是CAsyncSocket类的派生类,该类对Windows Sockets API进行了更多的封装,并且利用CArchive类进行数据传输,从而使利用该类进行数据传输的过程与MFC的串行化一致。
7.2 套接字 套接字(Socket)是一个通信终结点,它是Windows Sockets应用程序用来在网络上发送或接收数据包的对象。一个套接字有自己的类型,并有一个与之相关的应用程序,它还可以有自己的名字。
7.2.1 流式套接字 使用流式套接字的过程: 在两个通信节点间建立连接 节点通过这个连接进行通信 通信完毕后取消连接 7.2.1 流式套接字 使用流式套接字的过程: 在两个通信节点间建立连接 节点通过这个连接进行通信 通信完毕后取消连接 流式套接字提供了双向的数据流,并且保证数据流的传输是可靠的、有序的(即:传输的数据顺序的正确性)、无重复的(即:数据包只被传送一次) 最大特点是通信信息能保证按顺序无遗漏地到达目的地。
7.2.2 数据报套接字 数据报套接字也提供双向的数据传输,与流式套接字不同,它用于实现无连接的通信方式。 7.2.2 数据报套接字 数据报套接字也提供双向的数据传输,与流式套接字不同,它用于实现无连接的通信方式。 在传输可靠性要求不高的信息或者通信量较少的信息,则可采用数据报套接字。
7.2.3 IP地址和端口 IP地址是一台计算机在网络上的地址,是一个32位的数字
7.3 Windows Sockets的工作原理 Windows Sockets的工作方式为客户/服务器模式 根据连接的方式分为 面向连接——流式套接字 无连接——数据报套接字
7.3.1 面向连接的通信方式 图7-1 面向连接的通信方式
7.3.2 无连接的通信方式 图7-2 无连接的通信方式
7.5 基于MFC的Windows Sockets编程 7.5.1 编制基于流式套接字网络应用程序的步骤 1.服务器端 服务器端应用程序的设计过程如下: (1)从CSocket类派生两个新类,假设一个新类的名称为CServeSocket,另一个新类的名称为CAcceptSocket。其中,CServeSocket套接字用来监听连接请求,CAcceptSocket套接字是真正用于与客户端进行连接、接收发送数据的套接字。 (2)创建一个CServeSocket类对象。
(3)调用CSocket::Create函数创建一个CServeSocket套接字,并指定一个端口,端口号一般要大于1024。 (4)调用CSocket::Listen函数侦听端口。 (5)在CServeSocket中添加虚函数OnAccept。在OnAccept函数中为每一个连接进来的客户端创建一个新的CAcceptSocket类对象专门用于读写。 (6)在用于读写的CSocket派生类CAcceptSocket的OnReceive函数中进行读写操作。 (7)通讯结束时,调用读写套接字的Close函数关闭为各个客户端分配的读写套接字。
(1)创建一个CSocket类的派生类,用于连接和读写,假设新类的名称为CClientSocket。 2.客户端 客户端应用程序的设计过程如下: (1)创建一个CSocket类的派生类,用于连接和读写,假设新类的名称为CClientSocket。 (2)调用CSocket::Create函数创建套接字。 (3)调用CSocket::Connect函数连接到服务器的指定端口。 (4)调用CSocket::Send函数发送数据,进行发送数据的操作 (5)在CClientSocket中添加虚函数onReceive,在该函数中进行读操作。 (6)在结束通讯时,调用CSocket::Close函数关闭套接字。
7.5.2 编制基于流式套接字的网络应用程序 【例7-1】编写两个程序,一个用于模拟服务器端的程序Mysev,一个用于模拟客户端的程序Myclient,这个网络应用程序的功能很简单,只是实现服务器和客户端的通信。当客户端连接上服务器端时,给服务器发送消息:“服务器,你好!”,服务器向客户端发送消息:“客户端,你好!”。服务器端程序的运行结果如图7-4所示,客户端程序的运行结果如图7-5所示。客户端与服务器进行连接后,相互发送信息后的运行结果分别见图7-6和图7-7。
图7-4 服务器端程序Mysev的运行结果 图7-5 客户端程序Myclient的运行结果 图7-6 服务器端程序接收到的信息 图7-7 客户端程序接收到的信息
程序的实现过程如下: 1.服务器端应用程序设计 (1)利用MFC应用程序向导新建一个基于对话框的应用程序Mysev
(2)添加菜单资源 (3)添加控件和关联的成员变量 (4)添加套接字类型 (5)建立套接字类与对话框类的关联 (6)在对话框中初始化套接字并监听连接请求 (7)接受连接请求 (8)接收信息 (9)关闭连接
2.客户端应用程序设计 (1)创建一个基于对话框的应用程序Myclient,在高级功能中选择“Windows 套接字支持”,同服务器端应用程序的第一步。 (2)添加菜单资源 (3)添加控件和关联的成员变量 (4)添加套接字类型 (5)建立套接字类与对话框类的关联 (6)初始化套接字并建立连接 (7)接收信息 (8)发送消息 (9)关闭连接 (10)编译并运行程序
7.5.3 编制基于数据报套接字网络 应用程序的步骤 基于数据报套接字的网络通信应用程序的服务器和客户端遵循同样的编程方法。 7.5.3 编制基于数据报套接字网络 应用程序的步骤 基于数据报套接字的网络通信应用程序的服务器和客户端遵循同样的编程方法。 具体的编程方法如下: (1)如果服务器(客户程序)需要读取客户的数据,则创建一个CAsyncSocket类的派生类,并生成 该类的对象;如果服务器(客户程序)仅发送数据,则直接生成一个CAsyncSocket类对象。 (2)调用CAsyncSocket::Socket函数创建一个数据报类型的套接字。 (3)调用CAsyncSocket::SetSockOpt函数设置端口可重用。
(4)设置一个端口号,并调用CAsyncSocket::Bind函数将该端口捆绑到本地地址。 (5)调用CAsyncSocket:: SetSockOpt函数设置端口属性,允许传输广播信息。 (6)调用CAsyncSocket::SendTo函数发送数据 (7)在OnReceive()函数中调用ReceiveFrom函数读取客户方发送的数据。 (8)通信结束后,调用CAsyncSocket::Close函数关闭套接字。
7.5.4 编制基于数据报套接字的 网络应用程序 【例7-2】创建两个应用程序,实现以无连接方式发送和接收数据。程序的运行结果如下图所示。 7.5.4 编制基于数据报套接字的 网络应用程序 【例7-2】创建两个应用程序,实现以无连接方式发送和接收数据。程序的运行结果如下图所示。 图7-14 无连接方式发送和结束数据的结果
1.服务器端应用程序设计 (1)利用MFC应用程序向导新建一个基于对话框的应用程序MyUDPsever,在高级功能中选择“Windows 套接字支持”。 (2)修改应用程序主窗口IDD_MYUDPSEVER_DIALOG对话框的布局。删除原有的【取消】按钮,保留原有的【确定】按钮,将【确定】按钮的“Default Button”属性设置为false。 (3)添加一个名为IDD_PORT_DIALOG的对话框,为该对话框添加相应的基于CDialog类的CPortDlg类,该对话框被用于输入通讯端口号。 (4)修改主窗口类CMyUDPseverDlg,在该类中进行通讯。 (5)在CMyUDPseverDlg类中添加WM_DESTROY类的消息处理函数OnDestroy,在该函数中关闭套接字
2.客户端应用程序设计 (1)利用MFC应用程序向导新建一个基于对话框的应用程序MyUDPclient,在高级功能中选择“Windows 套接字支持”。 (2)修改应用程序主窗口的IDD_MYUDPCLIENT_DIALOG对话框资源的布局。删除原有的【取消】按钮,保留原有的【确定】按钮 (3)由于本示例客户端需要从服务器读取数据,因此从CSocket类派生一个类CClientSocket,该类用于读取数据。
(4)添加自定义消息宏。当CClientSocket类对象读取了数据后,需要通过自定义消息将数据发送给应用程序主窗口。 (6)创建一个ID为IDD_PORT_DIALOG的对话框,并生成相应的基于CDialog类的派生类CPortDlg。 (7)创建一个ID为IDD_TRY_DIALOG的对话框,并生成相应的基于CDialog类的派生类CTryDlg。 (8)修改MyUDPclientDlg类,添加成员变量和成员函数。
7.6 综合实例:聊天室 【例7-3】创建基于客户/服务器模式的聊天室应用程序。这个应用程序包括客户端的应用程序和服务器端的应用程序。 7.6 综合实例:聊天室 【例7-3】创建基于客户/服务器模式的聊天室应用程序。这个应用程序包括客户端的应用程序和服务器端的应用程序。 (a)服务器端 (b)一个客户端
7.6.1 服务器端应用程序的功能介绍 等待用户连接 通知所有在线用户新登陆用户信息 中转消息 当用户离线时通知所有在线用户
7.6.2 客户端应用程序的功能介绍 获得连接参数后与服务器建立连接 发送/接收消息 断开时通知服务器
7.6.3 服务器端应用程序的编写过程 (1)利用MFC应用程序向导新建一个基于对话框的应用程序ChatRoom,在高级功能中选择“Windows套接字支持”。 (2)修改应用程序主窗口IDD_CHATROOM_DIALOG对话框的布局。 (3)添加一个头文件tagHeader.h (4)添加一个基于CSocket的类CClientSocket,用于和客户端进行数据交换。 (5)添加一个基于CSocket的类CServerSocket,用于接收客户端的连接。 (6)修改CChatRoomDlg类的代码 (7)编译并运行程序
7.6.4 客户端应用程序的编写过程 (1)利用MFC应用程序向导新建一个基于对话框的应用程序ChatClient,在高级功能中选择“Windows套接字支持”。 (2)修改应用程序主窗口IDD_CHATCLIENT_DIALOG对话框的布局。 (3)由于用户登录时需要添加服务器地址、自己的用户名等信息,所以添加一个对话框资源,其ID为IDD_LOGIN_DIALOG,并为该对话框添加基类为CDialog的派生类CLoginDlg。 (4)同服务器端类似,添加一个头文件tagHeader.h (5)添加一个基于CSocket类的派生类CClientSocket,用于完成数据的发送和接收。 (6)对类CChatClientDlg进行修改 。 (7)编译并运行程序 。