本书重点突出,结构层次清晰,语言通俗易懂。有许多的典型应用实例。实例采用引导型模式,主要环节均留出思考空间。每个实例针对性很强,叙述和分析透彻,它包括网络拓扑结构、实验环境说明、实验目的和要求、配置步骤、测试结果等,每章都配有相关实验习题,具有可读性、可操作性和实用性强的特点,特别适合于课堂教学。
作者在本书第1版的基础上,对原内容做了一些调整。原第1章不变,但内容有删减。原第9~11章调整为第2~4章,原第2~8章调整为第5~11章,并增加了新的第12章无线网络,原综合实验调整为第13章。
本书第1版大部分内容的操作系统是基于Windows XP的,第2版修改了不适用于Windows 10的部分。交换机与路由器的端口也从百兆升级为千兆端口,以锐捷的S5750、RSR20为参考设备,但实际上对于网络设备的配置管理命令并没有太大改变。书中交换机与路由器等网络设备的配置实验,对于不具备硬件实验环境的可以采用第1章实验基础所介绍的Cisco仿真软件模拟实现。针对目前无线网络比较流行的情况,本版增加了相关内容,供读者学习参考。本书内容覆盖较为广泛,将网络知识和技术融于网络配置实验中,每章均配有用于巩固所讲授内容的思考与练习题和上机实验题,本版还增加了一些有挑战性的实验。本书可作为计算机网络专业应用本科的实验教材,也可作为网络专业从业人员的自学教材。
全书共有13章,主要内容包括实验基础(第1章)、网络嗅探与协议分析(第2章)、网络编程(第3章)、网络安全(第4章)、双绞线实验(第5章)、交换机技术(第6章)、路由技术(第7章)、访问控制列表(第8章)、网络地址转换(第9章)、VPN技术(第10章)、IPv6技术(第11章)、无线技术(第12章)、综合实验(第13章)。
本书重点突出,结构层次清晰,语言通俗易懂,有众多的网络实验,每个实验针对性很强,叙述和分析透彻,包括网络拓扑结构、实验环境说明、实验目的和要求、配置步骤、测试结果等,具有可读性、可操作性和实用性强的特点。本书十分重视实验前后的验证,同时在实验中插入了许多思考和讨论环节。
在本书的编写过程中,参考了大量锐捷网络的技术资料和培训教材,借鉴了许多网络工程和网络同仁的宝贵经验,在此表示诚挚的谢意。由于作者水平有限,书中的不妥和错误在所难免,诚请各位专家和读者批评指正。
编者
2017年2月
第3章网 络 编 程 本章主要介绍网络编程的一些主要方法,并配有编程实验。一些实验只提出要求,程序编写过程需要读者自行完成。 如果应用程序涉及本地与远程之间的通信,就需要采用网络编程。网络编程*主要的工作就是在发送端把信息通过规定好的协议进行包的组装,在接收端按照规定好的协议把包进行解析并提取出对应的信息,从而达到通信的目的。中间*主要的就是数据包的组装、过滤、捕获和分析以及其他处理。 通过使用套接字达到进程间通信目的的编程就是网络编程。套接字即Socket,应用程序通常通过套接字向网络发出请求或者应答网络请求,实际上是网络应用程序接口(API)。套接字是由传输层提供的应用程序(进程)和网络之间的接入点,如图3.1所示。应用程序(进程)可以通过套接字访问网络,套接字利用主机的网络层地址和端口号为两个进程建立逻辑连接。
图3.1Socket是应用层与传输层之间的桥梁 套接字可以用于多种协议,包括TCP/IP协议。常用的端口号如表3.1所示。表3.1TCP/IP常用端口号协议NNTPFTP(数据)FTP(控制)TelnetSTMPHTTPPOP3端口号192021232580110为了方便网络编程,20世纪90年代初,Microsoft联合其他几家公司共同制定了一套Windows下的网络编程接口,即Windows Sockets规范。它不是一种网络协议,而是一套开放的、支持多种协议的Windows下的网络编程接口。现在的Winsock已经基本上实现了与协议无关,可以使用Winsock调用多种协议的功能,但较常使用的是TCP/IP协议。Socket实际上是在计算机中提供了一个通信端口,可以通过这个端口与任何一个具有Socket接口的计算机通信。应用程序在网络上传输,接收的信息都通过这个Socket接口实现。 Socket是TCP/IP网络的API,Socket接口提供了很多的函数,可以用于开发网络应用程序。Socket数据传输是一种特殊的I/O,同时Socket也是一种文件描述符。Socket的使用主要有Socket建立、配置、建立连接、数据传输和结束传输等过程。
3。1利用套接字建立逻辑信道 一般发起通信请求的程序被称为客户端,用户一般是通过客户端软件访问某种服务。客户端应用程序通过与服务器建立连接和发送请求,然后接收服务器返回的内容。服务器则一般是等待并处理客户端请求的应用程序。服务器通常由系统执行,在系统生存期间一直存在和等待客户端的请求,并且在接收到客户端的请求后,根据请求向客户端返回合适的内容。 通信的一方(被动方,称为服务器)监听某个端口;通信的另一方(主动方,称为客户端)如果知道服务器的IP地址和它所监听的端口, 便可以试图发送请求建立连接。该连接请求包含: 服务器IP地址、服务器端口号、客户IP地址、客户端口号。由于客户端口号由客户端的系统(TCP进程)自动选取一个当前未用的端口,该四元组便可以在因特网中*标识一个逻辑连接。服务器收到客户端发来的连接请求后,便发出响应建立该连接,这样就建立了一条逻辑信道。 客户和服务器通过请求响应方式可以进行双向数据传输。当结束数据传输时,需要关闭该连接。这种工作模式是有连接的客户端/服务器模式(Client/Server)。 根据连接启动的方式以及本地套接字要连接的目标,套接字之间的连接过程可以分为三个步骤: 服务器监听,客户端请求,连接确认。
(1) 服务器监听: 是指服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态。 (2) 客户端请求: 是指由客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后向服务器端套接字提出连接请求。
(3) 连接确认: 是指当服务器端套接字监听到或者接收到客户端套接字的连接请求,它就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,连接就建立好了。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。 这三个步骤类似于三次握手,如图3.2所示。 图3.2客户端与服务器端的三次握手3。2Client/Server工作模式分类 Client/Server工作模式一般按下列分类:
(1) 有状态和无状态: 服务器是否记录客户端的当前状态。
(2) 有连接(TCP)和无连接(UDP): 客户端和服务器之间是否先建立连接再传输数据。
(3) 循环和并发: 服务器对多客户端请求的服务是采用循环方法还是并发程序方法。 TCP协议面向连接,使用可靠的字节流传送服务;而UDP协议面向非连接,使用非可靠的数据报服务。TCP协议提供高可靠性的传输,UDP协议提供高效的传输。它们在实际应用中有其各自所适应的场合。
3。3面向连接的Client/Server模式 在面向连接的Client/Server结构中,操作过程采取的是主动请求方式: 服务器首先启动,并根据请求提供相应服务。 通过调用socket()建立一个套接口,然后调用bind()将该套接口和本地网络地址联系在一起,再调用listen()使套接口做好侦听的准备,并规定它的请求队列的长度,之后调用accept()接收连接。客户端在建立套接口后就可调用connect()和服务器建立连接。连接一旦建立,客户机和服务器之间就可以供对方读取或者读取对方数据。*后在数据传送结束后,双方调用close()关闭套接口。
3。3。1面向连接的服务器工作流程 面向连接的服务器工作流程包括以下几个环节。
1。 创建套接字 Socket建立是通过调用Socket函数实现的,该函数定义如下: SOCKET socket(int domain, int type, int protocol)其中参数: domain: 指明使用的协议族,如果取值AF_INET,用于网络通信;如果取值AF_UNIX,用于单一UNIX系统中进程间通信。 type: 指明socket类型,如果取值SOCK_STREAM,表示是流式,面向连接的比特流,顺序、可靠、双向,用于TCP通信;如果取值SOCK_DGRAM,表示数据报式、无连接、定长、不可靠,用于UDP通信。 protocol: 由于指定了type,一般用0。 函数返回: 一个整型的socket描述符,供后面使用。如果调用失败,返回一个INVALID_SOCKET值,错误信息可以通过WSAGetLastError函数返回。 例如,一个socket可如下建立: int sockfd=socket(AF_INET,SOCK_STREAM,0)2。 将本地IP地址和端口号绑定到套接字 Socket的建立实际上是为socket数据结构分配了一个名字空间并返回指针,接着要对数据结构提供数据。bind()将一本地地址与一套接口捆绑,它适用于未连接的数据报或流类套接口,在connect()或listen()调用前使用。bind()函数通过给一个未命名套接口分配一个本地名字为套接口建立本地捆绑(主机地址/端口号)。 bind()定义如下: int bind(SOCKET socket, struct sockaddr address, int addr_len)其中参数: sockfd: 由socket()调用返回的套接口文件描述符。 sockaddr: 数据结构sockaddr中包括了关于本地地址、端口和IP地址的信息。 addr_len: 地址长度,可以设置成sizeof(structsockaddr)。 通常服务器在启动时都会绑定一个众所周知的地址(如IP地址+端口号),用于提供服务,客户端可以通过它连接服务器;而客户端不用指定,有的系统会自动分配一个端口号和自身的IP地址组合。这就是为什么通常服务器端在listen()之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。 函数返回: 如无错误发生,则bind()返回0;否则返回SOCKET_ERROR,应用程序可通过WSAGetLastError()获取相应错误代码。
3。 服务端使用listen()开启监听 listen()在套接字函数中表示让一个套接字处于监听到来的连接请求的状态。从客户端发来的连接请求将首先进入该等待队列,等待本进程的处理。listen()定义如下: int listen(SOCKET socket, int backlog)其中参数: socket: 一个已绑定未被连接的套接字描述符。 backlog: 进入队列中允许的连接的个数。进入的连接请求在使用系统调用accept()应答之前要在进入队列中等待。该值是队列中*多可以拥有的请求的个数,大多数系统的默认设置为20。 函数返回: 无错误返回0;否则返回SOCKET ERROR,可以调用函数WSAGetLastError取得错误代码。 例如,listen(s,1)表示连接请求队列长度为1,即只允许有一个请求,若有多个请求,则出现错误,给出错误代码WSAECONNREFUSED。
4。 接受从客户端发来的请求 accept()是网络编程的重要函数,其作用是在一个套接口接受一个连接,其头文件对于Windows系统是在#include中,而Linux系统则在#include 中。 accept()从端口的请求连接的等待连接队列中抽取*个连接,创建一个与此同类的新的套接口并返回句柄。如果队列中无等待连接,且套接口为阻塞方式,则accept()阻塞调用进程直至新的连接出现。如果套接口为非阻塞方式且队列中无等待连接,则accept()返回一错误代码。已接受连接的套接口不能用于接受新的连接,原套接口仍保持开放。accept()定义如下: SOCKETaccept(SOCKET socket, struct sockaddr address, int addr_len)其中参数: Socket: 正在监听端口的套接口文件描述符。 Address: 客户端的socket地址。 addr_len: socket地址的长度。 函数返回: 如果没有错误产生,则accept()返回一个描述所接收包的SOCKET类型的值;否则返回INVALID_SOCKET错误,应用程序可通过调用WSAGetLastError()获得特定的错误代码。
5。 发送和接收数据 建立连接后,客户端和服务器端就可以进行数据传输了,通过使用send()发送数据,使用recv()接收数据。int send(SOCKETsocket, char message, int msg_len,int flags)其中参数: socket: 发送数据的套接口文件描述符。它可以通过socket()系统调用返回,也可以通过accept()系统调用得到。 message: 指向要发送的数据的指针。 msg_len: 要发送数据的字节长度。 flags: 标志,一般设置为0。 函数返回: 无错时返回实际发送的字节数,否则返回SOCKET_ERROR。int recv(SOCKETsocket, char message, int msg_len,int flags)其中参数: socket: 要读取的套接口文件描述符。 message: 保存读入信息的缓冲区起始地址。 msg_len: 缓冲区的*大长度。 flags: 标志,一般设置为0。 函数返回: 无错时返回实际接收的字节数,否则返回SOCKET_ERROR。
6。 关闭连接套接字 使用close()调用关闭连接的套接口文件描述符: int closesocket(SOCKETsocket);之后就不能再对此套接口做任何的读/写操作。
7。 转4或结束
3。3。2面向连接的客户端工作流程
1。 创建套接字SOCKETsocket(int domain, int type, int protocol)
2。 发出连接请求 connect()用于建立与指定socket的连接。对于流类套接口(SOCK_STREAM类型),利用名字与一个远程主机建立连接,一旦套接口调用成功返回,它就能收发数据了。对于数据报类套接口(SOCK_DGRAM类型),则设置成一个默认的目的地址,并用它进行后续的send()与recv()调用。int connect(SOCKET socket, struct sockaddr address, int addr_len)其中参数: Socket: 由系统调用socket()返回的套接口文件描述符。 Address: 指向数据结构sockaddr的指针,其中包括目的(即服务器)端口和IP地址。 addr_len: 地址长度,可以使用sizeof(struct sockaddr)获得。 函数返回: 若无错误发生,则connect()返回0;否则返回SOCKET_ERROR错误,可通过WSAGetLastError()获取相应错误代码。
3。 发送和接收数据
4。 关闭此连接的套接字 其工作过程如图3.3所示。 图3.3基本TCP客户.服务器工作过程 3。4无连接的Client/Server模式 在无连接的Client/Server结构中,服务器使用socket()和bind()函数调用建立和连接Socket。由于此时的Socket是无连接的,服务器使用recvfrom()函数从Socket接收数据。客户端也只调用bind()函数而不调用connect()函数。注意: 无连接的协议不在两个端口之间建立点对点的连接,因此sendto()函数要求程序在一个参数中指明目的地址。recvfrom()函数不需要建立连接,它对到达相连协议端口的任何数据作出响应。当recvfrom()函数从Socket收到一个数据报时,它将保存发送此数据包的进程的网络地址以及数据包本身。程序(服务器和客户)用保存的地址去确定发送(客户)进程。在必要的条件下,服务器将其应答数据报送到从recvfrom()函数调用中所得到的网络地址中去。其工作过程如图3.4所示。 图3.4基本UDP客户.服务器工作过程 一般而言,大多数TCP服务器是并发的,而大多数UDP服务器是迭代的。多数TCP服务器是与调用fork处理每个客户连接的服务器并发执行的。迭代服务器没有对fork的调用,所以单一服务器进程就处理了所有客户。
3。5编 程 实 验 在Visual C++中进行Winsock的API编程开发时,需要在项目中使用下面3个文件,否则会出现编译错误。 (1) winsock。h: Winsock API的头文件,需要包含在项目中。 (2) wsock32。lib: Winsock API连接库文件。在使用中,一定要把它作为项目的非默认的连接库包含到项目文件中。 (3) winsock。dll: Winsock的动态连接库,位于Windows的安装目录下。
【例3.1】下面是一个有连接的编程实例。程序分两部分: 服务器端程序和客户端程序。 (1) 服务器端程序。//TCPdtd_server。cpp - main, TCPdaytimed #include #include #include #include void errexit(const char ,…); void TCPdaytimed(SOCKET); SOCKET passiveTCP(const char , int); #define QLEN5 #defineWSVERSMAKEWORD(2, 0) ……