12.TCPIP 套接字编程的分析与实现
12.TCPIP 套接字编程的分析与实现,12.TCPIP,套接字编程的分析与实现,12.,TCPIP,套接,编程,分析,实现
江苏大学本科毕业论文毕 业 论 文TCP/IP 套接字编程的分析与实现学生姓名 沈志贤 班级学号 通信001班13号 专 业 通信工程 所在院系 计算机科学与通信工程学院 指导教师 谢建菲 论文提交时间 2004年6月 论文答辩时间 2004年6月 答辩委员会主席 _评阅人 _2004年 月 日摘要TCP/IP参考模型是目前最为成熟的网络参考模型。它为今天网络技术的迅速发展立下了汗马功劳。20世纪80年代早期,远程规划局(ARPA)资助加利福尼亚大学的一个研究组,将TCP/IP第一次实现在UNIX系统上,这就是广为人知的套接字socket接口。网络编程的基石是套接字,一个套接字是通信的一端。在这一端上你可以找到与其对应的一个名字,一个正在被使用的套接字都有它的类型和与其相关的任务。关键字TCP/IP 套接字 客户端/服务器 防火墙AbstractTCP/IP model is the most mature refernced model in these days. It make the great contribution to the nettech today. In the eatly days of 20 Cen. 80 days, with the satke of ARPA, a workgroup in California University realize the TCP/IP on UNIX system the first time, which is the famous Socket interface. Socket is the headstone of the networkware programme, and a socket is a side of the communication. A opposite name could be found at the side, and a socket which is used has its style and its opposite mission.Keywords TCP/IP Socket Client/Server Firewall目 录摘要2Keywords2前言4第一章NAT/防火墙网络环境下的VoIP应用41.1 概述41.2 技术背景41.2.1 H.323 VoIP51.2.2 防火墙简介111.2.3 NAT简介121.2.4 如何在H323中穿越防火墙和NAT13第二章socket编程151.1 Socket基础151.2 TCP/IP协议族151.3 Socket常用的数据结构151.4 客户机/服务器模型171.5 Socket 编程接口的系统调用19第三章 为实现代理功能底层socket编程的详细设计26第四章 程序调试及心得404.1 程序调试404.2 个人心得42致 谢42参考书目42前言VoIP是指利用IP网络传送话音的技术,也称网络电话或IP电话。而VoIP网关是整个VoIP系统中的关键设备,它负责在传统的电话交换网络和Internet之间架起一坐桥梁,它可提供包括NO1信令、SS7信令、H.323、ISDN、CO环路等多种接入方式。本文主要介绍VoIP网关软件系统中的低层socket模块的设计过程,并对VoIP网关进行了详尽的描述,由于本文的意图是讨论在H323协议下的通过代理功能来实现打ip电话,所以H.323协议栈也是本文介绍的重点。本文的第一章是对VoIP技术和VoIP网关进行相应的描述和介绍;第二章是对sock的基础理论知识和具体的信令流程进行描述。第三章是对代理底层socket的详细设计(系统的实现是依赖于其它的模块支持的基础上的,所以在这也对其它相关的模块进行了简单的描述,阐述了本模块在系统中的地位和与其它模块的关系);第四章是在调试过程中所遇到的具体问题和整个系统设计后的一些个人心得。所有的代码都包含在附录中,整个模块的代码在KVP6033的硬件系统基础上运行得以通过。本文所编写的代码均采用C语言描述,因为经过编译后的代码是运行在vxworks操作系统下的,所以它其中调用了vxworks操作系统里的一些库函数,vxworks操作系统是一种多任务的实时嵌入式系统,而采用的编译环境是在Tornado下进行编译的,有关vxworks操作系统和Tornado编译环境在这很少提到,读者想了解其详细细节,可以查阅其它的一些相关资料。由于时间和精力有限,本文主要只涉及到上层软件部分,有关一些芯片的底层驱动只是略有涉及。也由于作者的知识和阅历非常有限,所以文章中肯定有不少错误和遗漏之处,肯请读者批评指正。第一章NAT/防火墙网络环境下的VoIP应用1.1 概述随着H.323、SIP、MGCP等相关协议体系的不断发展和逐渐完善,VoIP以其固有的多方讨好的姿态,正在获得越来越多的用户、服务供应商、以及设备制造商的青睐。VoIP是在无QoS保证的IP网络上传输语音信息、以实现端到端语音通信的一系列技术、协议及应用的统称。这里所说的IP网络可以是LAN、MAN,也可以是基于多项复杂技术由多个LAN、MAN互联而形成的WAN,甚至可以是Internet。任意两个端点之间只要可以通过IP协议进行互联,就应该可以实现VoIP的应用。下面主要描述在带有NAT或防火墙的IP网络环境下实现VoIP应用的相关问题及技术细节,并提出相关建议和解决方法。1.2 技术背景近年来,出于安全性和IPv4地址空间缺乏的考虑,大多数服务供应商和企业都在自己的网络边缘部署了防火墙并使用了NAT技术。在这样的网络环境下实现VoIP应用,需要考虑很多方面的问题。下面简单介绍VoIP、防火墙以及NAT的技术原理,并指出在这样的环境下VoIP面临的难题。1.2.1 H.323 VoIPVoice over IP,即在IP网络上的语音系统。通俗的讲,就是通过IP网络打电话目前,在IP网络上实现语音通信主要有以下几个协议体系:H.323、SIP、MGCP。其中SIP和MGCP的体系结构还不够完善,所以在VoIP中的应用还不是很广泛。但是SIP的开放、简单、易实现等特性使得该体系有着强大的生命力,并有取代H.323的趋势。H.323体系由ITU-T提出,并发表了一系列建议。H.323系列建议定义了在无业务质量保证的Internet或其它分组网络上实现多媒体通信的协议及规程。H.323体系的完善以及ITU-T浓厚的传统语音通信的技术背景使得该体系成为目前最为广泛部署的VoIP技术体系。本文档主要讨论H.323体系。在带有NAT或防火墙的IP网络环境下实现VoIP应用的讨论上,SIP、MGCP与H.323面临同样的问题。)H.323系统组件H.323系统的组件主要包括终端Terminals、网关Gateways、网守Gatekeepers、多点控制器Multipoint Controllers、多点处理器Multipoint Processors、以及多点控制单元Multipoint Control Units。H.323终端提供了在点到点或多点会议中进行语音和可选视频和数据通信的能力,与其它H系列终端、GSTN或ISDN语音终端、GSTN或ISDN数据终端的通信通过网关来实现。网守主要完成许可控制和地址翻译功能。多点控制器、多点处理器以及多点控制单元提供多点会议支持。H.323终端设备互连如下图所示:H323终端设备互连示意图)H.323协议栈H.323很大程度上基于ITU-T以前有关多媒体通信的建议,其编码机制、协议范围和基本操作类似于ISDN的Q.931信令协议的简化版本,并采用了比较传统的电路交换的方式。相关的协议包括用于建立连接的H.225.0、用于呼叫控制的H.245、用于大型会议的H.332、用于补充业务的H.450.1、H.450.2和H.450.3、用于安全控制的H.235、用于与电路交换业务互操作的H.246、用于控制各类多媒体网关的H.248等。一个典型的H.323协议栈如下图所示H.323协议是一个庞大的协议族,包括许多相关的协议,它们形成了一个协议栈。- 在H.323协议栈结构中,下三层是分组网的底层协议,其中的网络层在IP网络中就是IP协议。 - 语音压缩编码采用ITU-T的G系列建议。一般情况下,人的话音在采样编码(模数转换)后未压缩前生成的是64Kbps的比特流,为了提高网络带宽的利用率,需要对编码后的语音进行压缩。早期的语音压缩采用波形编码的方式,如G.711、G.726、G.727和G.722等,最多只能将语音压缩至16Kbps。现在则主要采用参数编码的方式,如G.729A和G.723.1等。这种方式的原理是模拟讲话者的发声声道,构造话音生成模型,再加上静音压缩机制(即在不讲话的情况下仅发送很少的比特流),至少能将语音压缩至8Kbps。一般情况下,该方式能将比特流的速率降至24Kbps,而且话音质量仍保持良好水平(可以分辨出讲话者)。 - 视频压缩编码和数据通信分别采用ITU-T的G.260系列建议和T.120系列建议,不再赘述。 - 实时传输协议/实时传输控制协议(Real-time Transport Protocol/ Real-time Transport Control Protocol,RTP/RTCP)是IETF建议的,它并非专为IP电话设计的,而是适用于一般的多媒体通信的技术。RTP协议为音频、视频等实时数据提供端到端的传递服务,可以向接收端点传送恢复实时信号必需的定时和顺序信息,RTCP协议能向收发双方和网络运营者提供QoS的监测手段。 - H.225.0和H.245是H.323系统的核心协议,前者主要用于呼叫控制,后者用于媒体信道控制。一次呼叫可以包含多种媒体信息,如可视电话就包含了音频和视频流。H.225.0还定义了注册、呼叫接纳和状态协议(Registration Admission and Status,RAS),RAS协议的作用是为网守(GateKeeper),即网络管理点提供确定端点地址和状态、进行呼叫接纳控制等服务。H.323协议栈示意图)H.323典型呼叫过程一次典型的H.323呼叫涉及多个交互的过程,下图显示了H.323信令交互过程:根据H.323系列建议,一次典型的呼叫过程(通过网守)由下图所示: 呼叫建立过程 控制通道过程 媒体传输过程 会话结束过程上图四个示意图显示了一次H.323 VoIP呼叫所经历的典型过程,对照下图Telephone1呼叫H323 Terminal1的情形,全部过程描述如下: a) 为Telephone1连接在Gateway1上的端口配置一个E.164号码1,而H323 Terminal1的E.164号码为2;b) 在Telephone1上拨号2时,Gateway1在发起呼叫前首先需要得到Gatekeeper1的许可,因此向Gatekeeper1发送ARQ消息;c) Gateway1得到ACF回应后再向H323 Terminal1的H.225侦听端口(缺省为1720TCP端口)发送Setup消息;d) H323 Terminal1得到Setup消息后,立刻向对方发送Call Proceeding消息通知本方正在处理对方的请求;e) H323 Terminal1在本地对呼叫验证通过,准备接受请求前也需要得到Gatekeeper的许可,所以向Gatekeeper1发送ARQ消息;f) H323 Terminal1在得到ACF消息后向对方发送Alerting消息;g) H323 Terminal的用户接听该呼叫后,向Gateway1发送Connect消息。这时,在呼叫信令上,一次呼叫就已接通。被叫方收到Setup消息后,在回应消息中需要带有H.245控制通道的传输地址,主叫方以TCP连接该传输地址后即可开始控制通道协商过程。最简单的协商过程主要包含能力交换和打开逻辑通道。能力交换主要用来协商通话的各方所采用的媒体编码方式如G.711、G.729、H.261等;打开逻辑通道是在进行语音或视频传输时建立传输通道用的。在此以后就可以开始正常的媒体传输。在语音或视频的媒体传输过程中,使用RTP协议承载数据,RCTP来监测传输质量。当需要结束会话过程时,可以由建立了逻辑通道的各方之间互相传输EndSession的H.245消息,提示关闭逻辑通道。随后由主动结束会话过程的一方向对方发送H.225的ReleaseComplete消息。EndSession消息是可选的。最后如果一个H323设备希望断开与Gatekeeper的连接,则可以向Gatekeeper发送DRQ消息,得到回应后,断开,Gatekeeper可以认为该设备已关机。Cisco语音设备一次典型的H.323 VoIP呼叫如下图所示(图9):)H.323呼叫通道如上文所述,一次典型的H.323呼叫流程需要建立四种通道:RAS信令及RAS信令通道、H.225.0呼叫信令及H.225.0呼叫信令通道、H.245呼叫控制及H.245呼叫控制通道、逻辑通道(语音通道)。RAS信令通道用于传递RAS信令,以完成终端与网守之间的登记注册、授权许可、带宽改变、状态和脱离等过程。H.225.0呼叫信令通道用于传递H.225.0呼叫信令,以建立两个终端之间的连接。该信令使用Q.931消息来控制呼叫的建立和拆除。当系统中没有网守时,呼叫信令信道在呼叫涉及的两个终端之间打开;当系统中包括一个网守时,由网守决定在终端与网守之间还是在两个终端之间建立呼叫信令信道。H.245控制信令通道用于传递H.245控制信令,以传送终端到终端的控制消息,包括主从判别、能力交换、打开和关闭逻辑通道、模式参数请求、流控消息和通用命令与指令等。H.245控制信令信道建立于两个终端或一个终端与一个网守之间。H.323信令结构如下图所示: H.323信令及通道结构如上图所示,信令消息、控制消息、以及语音流分别在各自的通道中传递,这些通道的建立依赖于TCP或UDP协议。H.225.0呼叫信令使用TCP连接,端口号1720;H.245呼叫控制使用TCP连接,端口号动态;RAS信令使用TCP连接,端口号1718、1719;RTP语音流使用UDP,端口号动态。如上图所示,信令消息、控制消息、以及语音流分别在各自的通道中传递,这些通道的建立依赖于TCP或UDP协议。H.225.0呼叫信令使用TCP连接,端口号1720;H.245呼叫控制使用TCP连接,端口号动态;RAS信令使用TCP连接,端口号1718、1719;RTP语音流使用UDP,端口号动态。比如说要传递H.245呼叫控制消息,首先必须由高层实体(如会话层、应用层)在两个端点间建立呼叫控制通道,而该通道的建立依赖于TCP连接的建立,要建立TCP连接必须要知道对方端点的传输层地址(IP地址和端口号),对方传输层地址的获得则依赖于前一次对方回应的H.225.0消息中包含的H.245传输地址(消息段格式如h245Address = ipAddress ip = 4 octets 0b 01 01 01port = 20014),该地址实际上就是对端的IP地址和守护进程端口号。而这一段H.225.0消息是在对端TCP/IP模型的会话层产生的,要在IP网上传递则必须封装相应的TCP层、IP层、链路层的头然后传递到本端。所以,IP网络上的一个VoIP报文不仅仅在IP报文头部包含了用于路由的源、目的IP地址,而且还有可能在IP报文的数据部分即高层(如5到7层)也包含了用于建立连接的IP地址,而该地址一般不会被互联网上的设备所识别(互联网设备一般工作于第三层)。1.2.2 防火墙简介出于安全考虑,很多企业或运营商会在自己内部网与外部网的交界处部署防火墙,以防止外部用户非法入侵内部网,从而避免可能的攻击,同时提供安全的Internet和远程访问连接。绝大多数防火墙还可以保护一个和多个边界网络,即非军事区(DMZs)。内部网、外部网以及边界网之间的连接由防火墙控制。为了有效地利用防火墙来保护内部网,用户必须规划一个安全策略,以确保所有内部网与外部网之间的数据流都通过该防火墙。目前,根据应用,防火墙的实现主要有以下几种:基于包过滤、基于代理服务器、基于状态。基于包过滤的防火墙一般集成在路由器当中,通过手工设置访问列表来限制特定的数据包流向,工作于OSI模型的第3、4层,缺点是暴露用户的IP地址、无状态、基于规则容易犯错。基于代理服务器的防火墙一般通过运行于Unix平台的代理软件来实现,工作在OSI模型的第37层,缺点是高成本、低效率。基于状态的防火墙可以动态反映用户的连接状态,可以通过多种技术实现安全目的,如ACL、NAT、ALG、VPN、IPSEC、AAA、IDS等,同时可以实现OSI高层的安全特性。 防火墙典型应用 如上图,当外出的数据包到达防火墙的内部接口时,防火墙检查先前是否有来自这一内部主机的数据包。如果没有,防火墙则在它的状态表中为此新连接创建一个传输通道,其中包括了一个内部IP地址、一个外部全局唯一IP地址。然后,防火墙将数据包的源地址替换为全局唯一的地址、修改校验和及其它相应字段,并将修改后的数据包发往相应的外部接口。当外部进入内部网络的数据包到达的外部接口时,它必须首先经过防火墙的安全验证。如果数据包通过了安全验证,则防火墙移去该数据包的目标IP地址,插入相应的内部IP地址,并在修改过相应字段后将该数据包发往内部接口。一般来说,防火墙会检查从外部进来的每个数据包的IP地址和目的端口号。防火墙的一个典型配置是:如果防火墙内部的一台计算机A向防火墙外部的一台计算机B主动发出请求,防火墙会让外部计算机B的响应数据包通过,当且仅当该响应数据包的目的地址和端口号与防火墙内发起请求的计算机A的地址和端口号相同时;如果计算机B发来的数据包仅仅目的地址是防火墙内发起请求的计算机A的地址,而端口号不是计算机A发出请求的那个端口号,防火墙也将会丢弃那个外来的数据包。防火墙总是被配置为过滤掉所有不请自到的数据包,有一个例外是在防火墙内提供Web Server供外部访问。在这种情况下,公司会配置防火墙允许目的地址是Web Server的IP地址且目的端口号为80的数据包通过,这就使得外部用户可以主动向内部的Web Server发起请求,以获得其上的数据。1.2.3 NAT简介NAT主要用于解决互联网IP地址缺乏的问题,它是一种允许一个组织的IP网络从外部看上去使用很少的全局IP地址空间而不是它实际使用的地址空间的特性。所谓全局IP地址就是在公网中被承认的地址,即公网路由器有路由指向的地址。所谓实际使用的IP地址就是设置在路由器接口或内部主机的地址,也叫局部地址。这样,路由器通过将这些局部地址转换为全局可路由的地址,就可以允许一个具有非全局可路由地址的组织连接到Internet。NAT允许企业内部网使用私有地址,并通过设置合法地址集使内部网与Internet进行通信,从而可以达到节省合法注册地址的目的。同时,这些私有地址可以被多个公司或组织使用而不会产生冲突。NAT隐藏了一个公司或组织的实际IP地址,这使得该公司或组织的内部网免于暴露自己真实的IP地址于外部互联网空间,减少了被黑客攻击的机会。NAT的缺点是使得网络时延增大、无法实现IP的端到端跟踪、可能会使某些要使用内嵌IP地址的应用不能正常工作。NAT往往集成在路由器或防火墙中,其典型应用及工作原理如下图所示: NAT典型应用如上图所示,Internal Client1要与External Client1建立一个telnet会话,首先Internal Client1向Router发送一个源地址为10.3.3.1、目的地址为172.69.3.1、TCP源端口号为1723、TCP目的端口号为23的IP数据包。Router从E1接口收到该数据包,检查IP头,根据路由表以及预先设定的ACL规则判断该通信需要启用NAT(先路由后NAT),然后该路由器检查NAT翻译表看是否表中已有相应条目,如果没有,路由器将用NAT地址池里的地址172.69.89.1替换该数据包的源IP地址,TCP端口号保持不变,目的地址与目的端口号不作改动,并将该地址翻译条目添加到NAT翻译表中。路由器将修改后的IP包从E0口发出。External Client1收到该数据包后经由上层处理返回一个响应数据包,该响应数据包的源地址是172.69.3.1,目的地址是172.69.89.1,源端口任意,目的端口是1024。该数据包经由互联网路由最终到达Router的E0接口。Router从E0口收到该响应报文后首先检查IP头,发现目的地址是172.69.89.1、目的端口是1024,该路由器检查NAT表(先NAT后路由),发现有该Socket的NAT地址转换条目,于是开始NAT地址替换,将172.69.89.1替换为10.2.2.1,最后根据路由表将修改后的报文发到E1接口。这样,一次报文交互就完成了。从上面的报文交互过程可以看出,NAT仅仅工作在IP层,它只完成IP报文IP头的源、目的地址转换功能,而不关心IP数据字段的内容。尽管它也使用了类似于TCP/UDP中的Socket端口功能,但该端口功能只是为了用于在NAT转换表中建立相应条目,以实现多个内部终端同时使用一个全局地址与外部终端通信的目的。1.2.4 如何在H323中穿越防火墙和NAT事实上,要彻底解决上述VoIP所面临的问题,最好的办法就是避免使用防火墙和NAT。当然这是一种消极的方法,对大多数企业或运营商来说,这样太冒险,网络安全没有保证,而且要得到足够多的可路由的IP地址需要付出非常昂贵的代价,甚至不太可能。从上文分析的情况来看,一种比较直观的积极的解决方法就是:a) VoIP设备必须知道NAT的全局地址;b) NAT设备必须配置成使用静态地址转换,即一个全局地址对应一个局部地址;c) VoIP设备必须将其H.225.0信令消息、H.245控制通道消息中包含的地址转换成NAT所使用的全局地址;d) 为了实现外部主动与内部建立通话,NAT或防火墙设备必须设置静态映射,同时要结合VoIP设备的H.323源地址绑定(防止外部网关或H.323终端随便选取自己任意一个接口的地址)。上述方法需要VoIP、NAT或防火墙同时支持,并且牺牲了一些安全特性。目前比较多的解决方法主要有修改NAT或防火墙设备、借助于第三方软件等,单纯修改VoIP设备无法解决问题。下面列举了一些方法。)让NAT或防火墙设备集成网关功能这种方法将VoIP网关功能直接集成在NAT/防火墙设备上,典型的设备如一个支持VoIP、NAT、防火墙的路由器。该路由器由NAT或防火墙将内部网与外部网隔离,一般的数据通信由NAT或防火墙功能实现,而语音通信则直接由路由器上所带的语音模块以及支持的网关功能来实现。这样,VoIP就避免了NAT或防火墙的限制。但是该方法只能够支持普通电话或PBX设备与外部通信,而无法实现内部H.323终端与外部的通信。)让NAT或防火墙设备支持高层应用这种方法也叫应用层网关(ALG)。具有该功能的NAT或防火墙设备不再只是简单地察看IP包头信息来决定数据包是否可以通过,它还会分析和处理高层数据字段的内容。如前面分析的,H.323和SIP协议都在高层数据字段中放了重要的控制信息,如地址和端口号。通过分析哪一个端口需要打开,防火墙动态地打开那些被应用的端口,而所有别的端口依然安全地保持关闭状态。而NAT则需要修改这些地址或端口号。)使用H.323代理H.323代理用于解决NAT或者同时解决NAT和防火墙问题,这取决于代理如何配置。代理其实是一种特殊类型的网关,在代理两边使用的是相同的协议。代理使终端到终端的呼叫过程看起来像两个分离的呼叫:一个是从私有网上的终端到代理,另一个是从代理到公众网上的终端,代理通过对这个呼叫进行中转解决了NAT问题。H.323代理一般结合标准的网守功能和RTP/RTCP多媒体流代理功能。这种方法典型应用是在防火墙后放一个H.323代理,代理需要被分配公有IP地址。防火墙被配置允许代理和外部进行多媒体通讯。有时候沿着网络路径在许多位置都应用了NAT设备,这时就需要在每一个使用NAT的地方放置代理。)建立VPNVPN是一种在公网上建立两个机构之间的专用隧道的技术,该两个机构部署自己私有IP地址空间,互相直接使用对方的私有地址进行通信。这样正好解决了VoIP的问题。但是该方法的缺点主要是:一、只能实现VPN隧道两端之间的VoIP通信,无法与位于公网的终端用户进行通信;二、当使用一些加密技术如IPSEC时,VoIP通信面临挑战。)客户/服务器代理方式一般企业网用户都不想升级或者改动他们的防火墙和NAT设备的配置,也不想让内外的通信绕过这些设备,客户/服务器代理方式也许是一个很好的选择。目前有Ridgeway公司、亚软公司eProxy系统均基于该方法。该方法由两个组件构成:Server软件和Client软件。Client放在防火墙内的私有网,它同时具有网守功能和代理功能,私有网内的终端注册到Client上,它和防火墙外的Server创建一个信令和控制通道,可以把所有的注册和呼叫控制信令转发到Server,也把音视频数据转发到Server,在转发时它把内部终端发送的和外部发往终端的数据包的地址和端口号替换为自己的。Server放在防火墙外的公众空间,可以位于服务提供商网络或者位于企业网的DMZ区域,Server扮演网守代理的角色,从Client收到的所有注册和呼叫信令都被Server转发到中心网守。Server和Client之间的通讯主要通过两个固定的端口来传输数据,这两个端口是2776和2777端口,被IANA机构分配给Ridgeway的系统。当私网内Client启动时:a) 它与Server上的2776端口建立一个固定连接用来传送控制和状态信息;b) 它监听私网内H.323网守注册和请求信息。当一个终端启动时:a) 终端通过Client/Server之间的连接发送注册信息到中心网守;Server分配给每一个注册的终端一个唯一的端口号(与Server的IP地址对应)。当一个终端呼叫防火墙外的另一个终端时,所有的数据包都通过Client路由到Server,返回的数据也从Server通过Client路由回到终端。当呼叫被建立后,Client确保所有必需的经过防火墙的音视频通道保持开放,这样音视频数据可以通过这些防火墙上开放的通道进行传输。使用这种方法IP地址信息被很好的屏蔽,因为所有的数据包通过Server来路由转发,每个终端好像看来在直接地和Server进行通信,而不是和别的终端,这保证了终端的IP地址在网络外不可得到。而且这种方法在大多数情况下不用对防火墙配置进行任何修改。对于那些防火墙设置限制打开向外的端口的情况,管理员可以创建简单的原则来允许从Client到Server上两个固定的端口2776和2777的向外的连接。这个方法不仅仅局限于企业应用,服务提供商可以把Server放在ISP网络的中枢向小企业和用户提供防火墙和NAT穿越服务。不管是企业用户还是服务提供商应用,呼叫终端变得如此简单,仅需下载一个Client软件装在PC上,而且不用关心在呼叫建立的路径上存在多少个防火墙或NAT设备。这个方法最大的缺点是所有经过防火墙的通讯都必须经由Server来进行中转,这会引起潜在的瓶颈,这个经由Client和Server的过程会增加少于5ms的延迟。但是这又是必须的,因为Server是防火墙唯一信任的设备。第二章socket编程TCP/IP参考模型是目前最为成熟的网络参考模型。它为今天网络技术的迅速发展立下了汗马功劳。20世纪80年代早期,远程规划局(ARPA)资助加利福尼亚大学的一个研究组,将TCP/IP第一次实现在UNIX系统上,这就是广为人知的套接字socket接口。网络编程的基石是套接字,一个套接字是通信的一端。在这一端上你可以找到与其对应的一个名字,一个正在被使用的套接字都有它的类型和与其相关的任务。1.1 Socket基础1.流(Stream)、连接(Connection)、阻塞(Block)、非阻塞(Non-block)2.两种字节顺序:NBO与HBO1)网络字节顺序NBO(Network Byte Order):按从高到低的顺序存储,在网络上使用统一的网络字节顺序,可以避免兼容性问题。2)主机字节顺序(HBO,Host Byte Order):不同的机器HBO不相同,与CPU设计有关例如:Motorola 68k系列,HBO与NBO相同,Intel x86系列,HBO与NBO相反1.2 TCP/IP协议族TCP(Transmission Control Protocol)传输控制协议,基于连接的服务 UDP(User Datagram Protocol)用户数据报协议,无连接的服务 IP(Internet Protocol)Internet协议,信息传递机制 1.3 Socket常用的数据结构struct sockaddr unsigned short sa_family; /* address family, AF_xxx */char sa_data14; /* 14 bytes of protocol address */;此数据结构用做bind、connect、recvfrom、sendto等函数的参数,指明地址信息。但一般编程中并不直接针对此数据结构操作,而是使用另一个与sockaddr等价的数据结构sockaddr_in(在netinet/in.h中定义):struct sockaddr_in short int sin_family; /* Address family */unsigned short int sin_port; /* Port number */struct in_addr sin_addr; /* Internet address */unsigned char sin_zero8; /* Same size as struct sockaddr */;在编程中大多数是使用sockaddr_in这个结构来设置/获取地址信息。sin_family指代协议族,在socket编程中只能是AF_INETsin_port存储端口号(使用网络字节顺序)sin_addr存储IP地址,使用in_addr这个数据结构struct in_addr unsigned long s_addr;这个数据结构是由于历史原因保留下来的,主要用作与以前的格式兼容。s_addr按照网络字节顺序存储IP地址,sin_zero是为了让sockaddr与sockaddr两个数据结构保持大小相同而保留的空字节。设置地址信息的示例:struct sockaddr_in sa;sa.sin_family = AF_INET;sa.sin_port = htons(3490); /* short, NBO*/sa.sin_addr.s_addr = inet_addr(132.241.5.10); bzero(&(sa.sin_zero), 8);注意:如果sa.sin_addr.s_addr INADDR_ANY,则不指定IP地址(用于Server程序)1.4 客户机/服务器模型网络编程中最常见的是客户/服务器模式。以该模式编程时,服务端有一个任务(或多个任务)在指定的端口等待客户来连接,服务程序等待客户的连接信息,一旦连接上之后,就可以按设计的数据交换方法和格式进行数据传输,客户端在需要的时刻发出向服务端的连接请求。在使用socket调用后,仅产生了一个可以使用的socket描述符,然后使用bind调用将一个名字与socket描述符连接起来,对于internet域就是将internet地址编联到socket。之后,服务端使用listen调用指出等待服务请求队列的长度。然后就可以使用accept返回客户的地址消息,并返回一个新的socket描述符,该描述符与原先的socket有相同的特性,这时服务端就可以使用一个新的socket进行读写操作了。 一般服务端可能在accept返回后创建一个新的任务进行与客户的通信,父任务则再在accept调用处等待另一个连接。客户端任务一般先使用socket调用得到一个socket描述符,然后使用connect向指定的端口发起连接,一旦连接成功后返回,就说明已经建立了与服务器的连接,这时就可以通过socket描述符进行读写操作了。使用无连接的udp协议时,服务端任务创建一个socket,接着调用recvfrom接收客户端的数据报,然后调用sendto将要返回客户端的消息发送给客户任务。客户端也要先创建一个socket,再使用sendto向服务端任务发出请求,使用recvfrom得到返回的消息。面向连接的TCP流方式服务器程序流程(多进程):1. 程序初始化 2. 填写本机地址信息 3. 绑定并监听一个固定的端口 4. 收到Client的连接后建立一个socket连接 5. 产生一个新的进程与Client进行通信和信息处理 6. 子通信结束后中断与Client的连接客户端程序流程:1. 程序初始化 2. 填写服务器地址信息 3. 连接服务器 4. 与服务器通信和信息处理 5. 通信结束后断开连接 面向无连接的UDP方式:TCP与UDP的区别:1. 基于连接与无连接 2. 对系统资源的要求(TCP较多,UDP少) 3. UDP程序结构较简单 4. 流模式与数据报模式 具体编程时的区别:1. socket()的参数不同 2. UDP Server不需要调用listen和accept 3. UDP收发数据用sendto/recvfrom函数 4. TCP:地址信息在connect/accept时确定5. UDP:在sendto/recvfrom函数中每次均 需指定地址信息 6. UDP:shutdown函数无效1.5 Socket 编程接口的系统调用Socket 系统调用:调用socket用来建立一个通信的端点Int socket ( Int domain, /*地址蔟,例如 AF_INET */ Int type, /*通信的类型 如SOCK_STREAM,SOCK_DGRAM,SOCK_RAW */ Int protocol /* socket 上使用的协议,通常为0 */ ) socket调用建立一个通信端点并返回一个socket描述符。参数type用来指明通信的类型,现在使用以下的几种定义: SOCK_STREAM:提供顺序,可靠和基于字节流的双向连接。SOCK_DGRAM: 支持数据报通信(无连接,不可靠,固定的消息最大长度)SOCK_SEQPACKET: 提供顺序的,可靠的,基于固定最大长度数据报的有传输路径的双向连接。该调用成功时返回值是socket描述符,不成功为-1。Connect调用: 调用connect的作用是在一个指定的socket上建立一个连接 STATUS connect ( Int s, /*socket 描述符 */ Struct sockaddr *name, /*连接的套接字地址 */ Int namelen /* 名字的字节长度 */ )第一个参数s是一个打开的socket描述符。如果该socket的类型是SOCK_DGRAM,该调用指出与该socket绑定的节点,指定的地址是数据报发送到地址。这时,connect调用并不是真正的建立连接,仅仅是让系统知道写到该socket的数据报将发送到何地,而且只有该地址发来的数据才被该socket接收。在使用UDP时,使用connect的好处是不必为每次发送和接收指定地址,可以使用send和recv调用。如果socket是SOCK_STREAM类型,该调用将试图与另一个指定名字的socket进行连接,一般来说指定的名字是由该通信域解释的一个地址。 第二个参数是一个地址结构,指出了服务器的名字,该结构如下: struct sockaddr u_char sa_len; /*总长度 */u_char sa_family; /*地址簇,如AF_INET,internet地址 */char sa_data14; /*实际长度,地址值 */对于internet域的地址表示,常用如下:struct sockaddr_in u_char sin_len ; u_char sin_family; u_short sin_port; /*远程要连接的服务器的端口 */ struct in_addr sin_addr; /*存放ip 地址的结构 */ char sin_zero8;struct in_addr u_long s_addr; ;整形变量的字节存放是高位还是低位依赖于机器以及系统的实现,为了程序的可移植性,使用以下的宏定义函数进行格式转换,以保证程序的可移植性:htonl(); /*长整形的主机字节顺序的hostlong转换为网络字节顺序 */htons(); /*短整形的主机字节顺序short int 转换为网络字节顺序 */ntohl(); /* 长整形的网络字节顺序转换为主机字节顺序 */ntohs(); /*短整形的网络字节顺序转换为主机字节顺序 */Close 调用: STATUS close( Int fd /* socket描述符 */ )在使用TCP协议的程序中,当客户端或服务端使用完一个socket时,可以使用close来关闭该socket。Close调用的参数为socket描述符。在TCP协议下,发出该调用将断开连接,如果该描述符有多个引用(与打开文件相同),则将该socket的引用数减1,如果引用数减为0,则释放该socket。如果被关闭的socket具有保证可靠性的类型(如 SOCK_STREAM),那么核心将继续试图传送缓冲中的数据,直到超时为止。如果要求不在发送缓冲中的数据,那么可以使用shutdown调用。send 和 sendto 调用: 系统调用send和sendto用来从一个socket发送一个消息 int send ( int s, /*一个用于标识已连接套接字的描述符 */ char buf, /* 包含待发数据的缓冲区 */ int bufLen, /*缓冲区中数据的长度 */ int flag /*调用执行方式 */ )int sendto( int s, caddr_t buf, /*包含待发送的数据的缓冲区 * int bufLen, int flags, struct sockaddr *to, /*指向目的的套接字的地址 */ int tolen /*to 所指地址的长度 */ )调用send和sendto用来发送一个消息到另一个socket。Send调用只用与处于连接状态的socket,而sendto不需要socket处于连接状态,在sendto调用中要指出消息的目的地址。参数s是一个socket描述符,参数msg指向用户空间的一片缓冲空间,该处存放要发送的消息,消息的长度由参数len指出。在sendto调用中,参数to为sockaddr_in结构,用于指出在AF_INET域中目的地址的地址,地址结构的大小由参数tolen指出。当要传送的消息长度大于该socket的发送缓冲的剩余空间大小时,白森的一般会被阻塞,除非socket被设置为非阻塞模式。在socket被设置为非阻塞模式,且传送的消息的长度大于发送缓冲的剩余空间大小时,该调用返回EAGAIN错误代码。recv 和 recvfrom 调用: 系统调用recv 和 recvfrom 用来从一个socket接收消息。 Int recv( Int s, /* 接收数据的socket 描述符 */ Char * buf, /*指向接收数据后存放的用户缓冲的地址 */ Int bufLen, /*缓冲区的大小 */ Int flags /*协议的标志 */ ) int recvfrom( int s, char * buf, int bufLen, int flags, struct sockaddr *from, /*从哪个socket地址接收 int * pFromLen /接收的缓存大小 */ )这两个系统调用中,参数s为要接收数据的socket描述符。参数buf指向接受数据后存放的用户缓冲的地址。参数len指出用户缓冲的大小。系统调用recvfrom来从一个socket接收数据,不管这个socket是否面向连接。如果参数from非空,并且该socket不是面向连接的,则recvform调用返回时,参数from中存放的消息的源地址。参数fromlen也是一个返回值的参数,调用前fromlen的初值为参数from分配的缓存的大小,调用后指出from的实际大小。调用recv一般仅用于一个已经连接的socket,该调用等同于recvfrom中from为空指针NULL。这两个调用成功时,返回消息的长度。如果消息太长,而缓冲区不能完全放下,则剩余部分可能被丢弃(这要根据接受数据的socket的类型而定)。如果在指定socket没有消息到达,这两个调用会阻塞等待消息。但如果socket被设置为非阻塞方式,则调用返回-1。一般这两个调用接收到数据就返回,而不等待到满足要求接受的数据数量len才返回。这两个调用成功时,返回接收到的字符数,错误时返回-1。shutdown 调用: 该调用将关闭一个全双工连接的一端。 Status shutdown( Int s, Int how /*how为0 禁止接受数据,how为1禁止继续发,送数据,how为2 禁止在该socket上接受或发送数据 )shutdown调用将导致socket描述符s所指的全双工socket连接被部分关闭或完全关闭。如果参数how指定为0,则被该调用关闭的socket将禁止接受数据。如果参数how为1,则禁止继续发送数据。如果how为2,则禁止在该socket上接受或发送数据。该调用成功时返回0,否则返回-1。bind 调用: 将名字绑定到一个socket上。 Status bind ( Int s, Struct sockaddr * name, /*name to be bound */ Int nameLen /length of name )bind调用为一个未命名的socket分配一个名字。也就是,bind调用是给套接字s赋予本地的地址name,这个地址的长度是nameLen。如果想让任务侦听任何一个本地接口的报文,那么将地址结构sockaddr-in中的成员sin-addr设置为INADDR_ANY。该调用成功时,返回值为0,否则为-1。listen 调用:该调用于一个socket 侦听连接,listen调用声明的格式入下: status listen( int s, int backlog /number of connect to queue );为了接受一个连接请求,首先由socket调用创建一个socket,而listen调用指定接受的队列的长度限制,然后由accept调用来接受连接请求。参数backlog指定队列的最大长度,该队列用来放置等待处理的连接请求。如果服务端的等待队列已满,客户的连接请求将导致客户端收到一个ECONNREFUSED错误,或者当下层协议支持重新发送请求,该请求没忽略。
收藏