第9章网络通信应用在信息社会,随着互联网的普及,网络应用越来越广泛,通过互联网传输信息成为PC的必备要素。在嵌入式设备上,也越来越多的利用网络传输信息。Linux操作系统从一开始就提供网络功能,并且,Linux上的socket库兼容BSDsocket库,为开发网络应用提供良好的支持。对应用程序员来说,掌握socket开发可以快速的实现网络应用程序。本章主要内容如下:TCP/IP协议簇介绍socket通信的概念通过socket进行面向数据流的通信通过socket进行面向数据报的通信socket开发的高级应用9.1网络通信基础互联网(internet)是目前世界上应用最广泛的网络,最早从美国军方的科研项目ARPA(AdvancedResearchProjectsAgency)发展而来。互联网采用TCP/IP协议传输数据,虽然TCP/IP协议并不是ISO规定的标准协议,但是作为应用最广泛的协议已经成为大规模网络通信的事实标准。本节介绍TCP/IP协议簇以及其中重要的IP协议、TCP协议和UDP协议。9.1.1TCP/IP协议族TCP/IP协议实际上是由一组协议组成的,通常也称作TCP/IP协议簇。根据ISO/OSI参考模型对网络协议的规定,对网络协议划分为7层。9.1.2IP协议在图看出,IP协议工作在传输层,负责数据包的传输管理。IP协议实现两个基本功能:寻址和分段。寻址是IP协议提供的最基本功能,IP协议根据包头中目的地址传送数据报文。在传送数据报文过程中,IP协议可以根据目的地址选择报文在网络中的传输路径,这个过程称作路由。分段是IP协议一个重要功能。由于不同类型的网络之间传输的网络报文长度是不同的,为了能适应在不同的网络中传输TCP/IP协议报文,IP协议提供分段机制帮助数据包穿过不同类型的网络。IP协议在协议头记录了分段后的报文数据,但是IP协议并不关心数据的内容。9.1.2IP协议9.1.3TCP协议TCP协议是一个传输层协议。如图9-1所示,TCP协议位于网络互联层后,是IP协议的上层协议。TCP是一个面向连接的可靠传输协议。在一个协议栈处理程序中,如果发现数据包的IP层后携带了TCP头,会把数据包交给TCP协议层处理。TCP协议层对数据包排序并进行错误检查,按照TCP数据包头中的序列号排序,如果发现排序队列中少某个数据包,则启动重传机制重新传送丢失的数据包。TCP协议层处理完毕后,把其余数据交给应用层程序处理,如FTP的服务程序和客户程序。面向连接的应用几乎都使用TCP协议作为传输协议。TCP传输协议有高度可靠性,可以最大限度保证数据在传递过程中不丢失。9.1.4UDP协议UDP与TCP一样是传输层协议,但是UDP协议没有控制数据包的顺序和出错重发机制。因此,UDP的数据传输时不稳定的。通常UDP被用在对数据要求不是很高的场合,如查询应答服务等。使用UDP作为传输层协议的有NTP(网络时间协议)和DNS(域名服务系统)。UDP另一个重要问题就是安全性不高。由于UDP没有连接的概念,在一个数据传输过程中,UDP数据包可以很容易的被伪造或者篡改。9.1.5学习分析协议的方法网络协议一般都比较抽象,给人感觉枯燥。学习网络协议需要一个直观的认识,推荐读者使用网络协议分析的工具分析协议。目前有很多的网络协议分析工具,著名的Sniffer就是一款专业的网络协议分析利器,本书介绍一个比较流行的工具Ethereal,这是一个开源的网络协议分析工具,功能十分强大,使用libpcap库做数据包解析,使用GTK+库做界面,由于这两个库是跨平台的,所以Ethereal可以在多种平台使用。Ethereal最大的特点是支持用表达式书写包过滤条件,同时支持常见协议的深度分析,如HTTP,SIP等。Ethereal最新版本已经更名为WireShark,官方网站是,官方网站有软件的使用手册以及下载。软件的安装本书不做介绍,安装过程一般不需要选择,按照提示一步一步进行即可。本节介绍WireShark软件的使用。9.2socket通信基本概念socket常被翻译成套接字或者插口,socket实际上就是网络上的通信端点。使用者或应用程序只要连接到socket便可以和网络上任何一个通信端点连接,传送数据。socket封装了通信的细节,在Linux系统,为使用者提供了类似文件描述符的操作方法,程序员可以不必关系通信协议内容而专注应用程序开发。根据数据传送方式,可以把socket分成面向连接的数据流通信和无连接的数据报通信。9.2.1创建socket对象在使用socket通信之前,需要创建socket对象。对应用程序员来说,soeket对象就是一个文件句柄,通常使用socket()函数创建socket()对象。函数定义如下:#includesys/types.h#includesys/socket.hintsocket(intdomain,inttype,intprotocol);9.2.2面向连接的socket通信实现面向连接的数据流通信在TCP/IP协议簇是使用TCP作为传输层协议通信,按照TCP协议的要求,通信双方需要在传输数据前建立连接,术语上称作“TCP的三次握手”。9.2.2面向连接的socket通信实现9.2.3面向连接的echo服务编程实例本节给出一个echo服务的编程实例,echo_serv.c是服务端源代码,提供创建服务端,绑定套接字到本机IP和8080端口,当收到客户端发送的字符串就在屏幕打印出来,并且把字符串发送给客户端,如果客户端发送”quit”,服务器端退出。//echo_serv.c–gcc–osecho_serv.c9.2.4无连接的socket通信实现无连接的socket通信相对于建立连接的流socket简单,因为数据传输过程中不能保证到达,常用在一些对数据要求不高的地方,如在线视频等。无连接的套接字不需要建立连接,省去了维护连接的开销,所以,同样环境下一般比流套接字传输数据速率快。实际应用中,一些应用软件会自己维护无连接的套接字数据传输状态。无连接的套接字使用TCP/IP协议簇的UDP协议传输数据。9.2.4无连接的socket通信实现9.2.5无连接的时间服务编程实例无连接的套接字通信比较简单,本节给出一个获取时间的例子,服务端程序time_serv.c负责创建socket并且绑定到本机9090端口,然后等待客户端发出请求,当收到客户端发送的请求时间命令”time”以后,生成当前时间的字符串发送给客户端。客户端建立socket以后,直接向指定的服务端发送请求时间命令,之后等待服务端返回,发送退出命令,关闭连接。9.3socket高级应用9.2节介绍了socket编程的基础知识,包括面向连接的流通信和无连接的数据报通信,并且给出了例子。由于网络通信过程中有许多不确定因素,数据的传输不可能每次都正确,需要对数据发送和接收做超时处理;对于一个服务器来说,需要同时管理多个客户端的连接。这些技术就是本节要介绍的。9.3.1socket超时处理实际的网络通信数据常会因为各种网络故障导致传输失败,在应用程序里需要对数据发送和接收做对应的超时处理,超时指的是预先假定一次数据传输需要的时间,如果超过这个时间没有得到反馈,认为数据传输失败,socket库提供了两个强大的函数setsockopt()和getsockopt()用来设置套接字和得到套接字参数,函数定义如下:#includesys/types.h#includesys/socket.hintgetsockopt(ints,intlevel,intoptname,void*optval,socklen_t*optlen);intsetsockopt(ints,intlevel,intoptname,constvoid*optval,socklen_toptlen);9.3.2使用select机制处理多连接当服务端根据客户端的请求创建多个连接以后,每个连接对应不同的套接字,因为recv()函数默认是阻塞的,会造成在等待一个客户端套接字返回数据的时候整个进程阻塞,而无法接收其他客户端套接字数据。这时候需要一个可以处理多个连接的方法,socket库提供了两个函数select()和poll()用来等待一组套接字句柄的读写操作。Linux系统提供了select()函数和poll()函数两个网络套接字复用的工具。使用select()函数和poll()函数可以向系统说明在什么时间需要安全的使用网络套接字描述符。如程序员可以通过这两个函数的返回结果知道哪个套接字描述符上有数据需要处理。使用select()函数和poll()函数后,程序可以省去不断的轮询网络套接字描述符的步骤。在后台运行网络程序,当被监听的网络套接字有数据的时候,系统会触发应用程序。因此,使用select()和poll()函数可以显著提高网络应用程序的工作效率。9.3.2使用select机制处理多连接select()函数是比较常用的,函数定义如下:/*AccordingtoPOSIX1003.1-2001*/#includesys/select.h/*Accordingtoearlierstandards*/#includesys/time.h#includesys/types.h#includeunistd.hintselect(intn,fd_set*readfds,fd_set*writefds,fd_set*exceptfds,structtimeval*timeout);FD_CLR(intfd,fd_set*set);FD_ISSET(intfd,fd_set*set);FD_SET(intfd,fd_set*set);FD_ZERO(fd_set*set);9.3.3使用poll机制处理多连接poll()函数提供与select()函数类似的功能,解决了select()函数存在的一些问题,并且函数调用方式也更加简单。函数定义如下:#includesys/poll.hintpoll(structpollfd*ufds,unsignedintnfds,inttimeout);事件名称解释POLLIN有数据可读POLLRDNORM有普通数据可读POLLRDBAND有优先数据可读POLLPRI有紧迫数据可读POLLOUT写数据不会导致阻塞POLLWRNORM写普通数据不会导致阻塞POLLWRBAND写优先数据不会导致阻塞POLLMSGSIGPOLL消息可用POLLER指定的文件描述符发生错误(仅出现在revents域)POLLHUP指定的文件描述符挂起事件(仅出现在revents域)POLLNVAL指定的文件描述符非法(仅出现在revents域)9.3.4多线程环境socket编程select()函数和poll()函数可以解决一个进程需要同时处理多个网连接的问题,但是使用select()函数和poll()函数监控文件句柄仍然需要等待,如果后面还有需要处理的工作仍然不能同步完成。可以利用第8章介绍的多线程技术在一个进程里处理不同的网络连接,为每个连接建立不同的线程,进程不会因为读写函数阻塞,多个连接操作数据也不会互相干扰。多线程处理连接的时候需要注意一个线程直接的全局变量是共享的,所以每个连接对应的套接字句柄应该保存在线程内部。9.4小结本章讲解socket编程。首先介绍了TCP/IP协议簇,在第2节讲解socket编程的基础知识,包括面向连接的流套接字和无连接的数据报编程,这部分是socket编程最基本的,初学者应该把这两种类型的套接口通信模式与TCP/IP协议的层次关系对比,可以更快的理解里面的含义。第3节介绍了socket编程的一些高级技术,主要是集中在如何对阻塞数据处理,包括超时机制,多线程机制,读者可以参考Linux的man手册得到更多的细节。第10章另一种常见的通信方式-串口通信编程。