1980年8月,紧随TCP/IP之后,UDP(User Datagram Protocol,用户数据报协议)备John Postel加入了核心网络协议套件。UDP经常被称为无协议。
. 数据报
一个完整、独立的数据实体,携带着从源节点到目的地节点的足够信息,对这些节点间之前的数据交换和传输网络没有任何依赖。
数据报和分组是两个机场被人混用的词,实际上他们还是有区别的。分组可以用来代任何格式化的数据块,而数据报则通常只用来描述那些通过不可靠的服务传输的分组,既不保证送达,也不发送失败通知。正因为如此,UDP就成了“不可靠数据报协议”;
3.1 无状态服务
要理解为什么UDP被人称作“无协议”,必须从作为TCP和UDP下一层的ip协议说起。IP层的主要任务就是按照地址从源主机向目标主机发送数据报。 为此,消息会被封装在一个IP分组内,其中载明了原地址和目标地址,以及其他一些路有参数。注意,数据报这个词暗示了一个重要的信息:IP层不保证信息可靠的支付,也不发送失败通知,实际上是把底层网络的不可靠性直接暴露给了上一层。如果某个路由节点因为网络拥塞、负载过高或其他原因而删除了IP分组,那么在必要的情况下,IP的上一层协议要负责检测、恢复和重发数据。
UDP协议会用自己的分组结构封装用户信息,他只增加了4个字段:源端口、目标端口、分组长度和校验和。这样当IP把分组送达目标主机时,该主机能够拆开UDP分组,根据目标端口找到目标应用程序,然后再把消息发送过去而已。
事实上,UDP数据报中的源端口和校验和字段都是可选的。IP分组的首部也有校验和,应用程序可以忽略UDP校验和。也就是说,所有错误检测和错误纠正工作都可以委托给上层的应用程序。说到底,UDP仅仅是在IP层之上通过嵌入应用程序的源端口和目标端口,提供了一个”应用多路复用“机制。明白了这一点,就可以总结一下UDP的无服务是怎么回事了。
. 不保证消息交付
不确认,不重传,无超时。
. 不保证交付顺序
不设置保序号,不重排,不会发生队首阻塞。
. 不跟踪连接状态
不必建立连接或重启状态机。
. 不需要拥塞控制
不内置客户端或网络反馈机制
TCP是一个面向字节流的协议,能够以多个分组形式发送应用程序消息,且对分组中的消息范围没有任何明确限制。因此,连接的两端存在一个连接状态,每个分组都有序号,丢失还要重发,并且要按顺序交付。相对来说,UDP数据报有明确的限制:数据报必须封装在IP分组中,应用程序必须读去读取完整的消息。换句话说,数据报不能分片。
UDP是一个简单、无状态的协议,适合作为其他上层应用协议的辅助。实际上,这个协议的所有决定都需要由上层的应用程序作出。不过,再急着去实现一个协议来扮演TCP的角色之前,你还应该认真想一想这里涉及的复杂细节,比如UDP要与很多中间设备打交道(NAT穿透),再想一想设计网络协议的那些最佳实践。如果没有周密的设计和规划,一流的构想也可能沦为二流的TCP实现。TCP中的算法和状态机已经经过了几十年的磨合与改进,而且吸收几十种并不那么容易重新实现的机制。
3.2 UDP与网络地址转换器
令人遗憾的是,IPv4地址只有32位长,因而最多只能提供42.9亿个唯一的IP地址。1990年代初,互联网上的主机数量呈指数增长,但不可能所有主机都分配一个唯一的IP地址。1994年作为解决IPv4地址即将耗尽的一个临时性的方案,IP网络地址转换器(NAT,Network Address Translator)规范出台了,这就是RFC1631.
建议的IP重用方案就是在网络边缘加入NAT设备,每个NAT设备负责维护一个表,表中包含本地IP和端口到全球唯一(外网)IP和端口的映射。这样NAT设备背后的IP地址空间就可以在各种不同的网络中得到重用,从而解决地址耗尽问题。
3.2.1 连接状态超时
NAT转换的问题(至少对于UDP而言)在于必须维护一份精确的路由表才能保证数据转发。NAT设备依赖连接状态,而UDP没有状态.这种根本 上的错配是很多UDP数据报传输问题的总根源。况且,客户端前面有很多个NAT设备的情况也不鲜见,问题由此进一步恶化了。
每个TCP连接都有一个设计周密的协议状态机,从握手开始,然后传输应用数据,最后通过明确的信号确认关闭连接。在这种设计下,路由设备可以监控连接状态,根据情况创建或删除路由表中的条目。而UDP呢,没有握手,么有连接终止,实际根本没有可监控的连接状态机。
发送出站UDP不费事,但路由响应却需要转化表中有一个条目能告诉我们本地目标主机的IP和端口。因此,转换器必须保存每个UDP流的状态,而UDP自身却没有状态。
更糟糕的是,NAT设备还被赋予了删除转换记录的责任,但由于UDP没有连接终止确认环节,任何一端随时都可以停止传输数据报,而不必发送通告。为解决这个问题,UDP路由记录会定时过期。定是多长?没有规定,完全取决于转换器的制造商、型号、版本和配置。因此,对于较长时间的UDP通信,有一个事实上的最佳做法,即引入一个双向keep-alive分组,周期性地重置传输路径上所有NAT设备中转换记录的计时器。
TCP超时和NAT
从技术角度讲,NAT设备不需要额外的TCP超时机制。TCP协议就遵循一个设计严密的握手与终止过程,通过这个过程就可以确定何时需要添加或删除转换记录。遗憾的是,实际应用中NAT设备给TCP和UDP会话应用了类似的超时逻辑。
这样就导致了TCP连接有时候也需要双向keep-alive分组。如果你的TCP连接突然断开,那很有可能就是中间NAT超时造成的。
3.2.2 NAT穿透
不可预测的连接状态处理是NAT设备带来的一个严重问题,但更为严重的则是很多应用程序根本就不能建立UDP连接。尤其是P2P应用程序,涉及VoIP、游戏和文件共享等,它们客户端与服务器经常需要角色互换,以实现端到端的双向通信。
NAT带来的第一个问题,就是内部客户端不知道外网IP地址,只知道内网IP地址。NAT负责重写每个UDP分组中的源端口、地址,以及IP分组中的源IP地址。如果客户端在应用数据中以其内网IP地址与外网主机通信,连接必然失败。所谓的“透明”转换因此也就成了一句空话,如果应用程序想与私有网络外部的主机通信,那么它首先必须知道自己的外网IP地址。
然而,知道外网IP地址还不是实现UDP传输的充分条件。任何到达NAT设备外网IP的分组还必须有一个目标端口,而且NAT转换表中也要有一个条目可以将其转换为内部主机的IP地址和端口号。如果没有这个条目(通常是从外网传数据进来),那到达的分组就会被删除。此时的NAT设备就像一个分组过滤器,除非用户通过端口转发(映射)或类似机制配置过,否则它无法确定将分组发送给那台主机。
需要注意的是,上述行为对客户端应用程序不是问题。客户端应用程序基于内部网络实现交互,会在交互期间建立必要的转换记录。不过,如果隔着NAT设备,那客户端(作为服务器)处理来这P2P应用程序(VoIP、游戏、文件共享)的入站连接时,就必须面向NAT穿透问题。
为解决UDP与NAT的这种不搭配,人们发明了很多穿透技术(TURN、STUN、ICE),用于在UDP主机之间建立端到端的连接。
3.2.3 STUN、TURN与ICE
STUN(Session Traversal Utilities for NAT)是一个协议(RFC 5389),可以让应用程序发现网络中的地址转换器,发现之后进一步取得为当前连接分配的内网IP地址和端口。为此,这个协议需要一个已知的第三方STUN服务支持,该服务器必须假设在公网上。
假设STUN服务器的IP地址已知(通过DNS查找或手工指定),应用程序首先向STUN服务器发送一个绑定请求。然后,STUN服务器返回一个响应,其中包含在外网中代表客户端的IP地址和端口号。这种简单的方式解决了前面讨论的一些问题。
. 应用程序可以获得外网IP和端口,并利用这些信息与对端通信;
. 发送到STUN服务器的出站绑定请求将在通信要经过的NAT中建立路由条目,使得到达该IP和端口的入站分组可以找到内网中的应用程序;
. STUN协议定义了一个简单keep-alive探测机制,可以保证NAT路由条目不超时。
有了这个机制,两台主机端需要通过UDP通信时,它们首先都会向各自的STUN服务器发送绑定请求,然后分别使用响应中的外网IP地址和端口号交换数据。
但在实际应用中,STUN并不能适应所有类型的NAT和网络配置。不仅如此,某些情况下UDP还会被防火墙或其他网络设备完全屏蔽。这种情况在很多企业网很常见。为解决这个问题,在STUN失败的情况下,我们还可以使用TURN协议作为后备。TURN可以在最坏的情况下跳过UDP而且换到TCP。
TURN中的关键词当然是中继。这个协议依赖于外网中继设备在两端间传递数据。
. 两端都要向同一台TURN服务器发送分配请求来建立连接,然后在进行权限协商。
. 协商完毕,两端都把数据发送到TURN服务器,再由TURN服务器转发,从而实现通信。
很明显,这就不再是端对端的数据交换了!TURN是任何网络中为两端提供连接的最可靠方式,但运维TURN服务器的投入也很大。至少,为满足传输数据的需要,中继设备的容量必须足够大,因此,最好在其他直连手段都失败的情况下,再使用TURN。
建立高效的NAT穿透方案可不容易。好在,我们还有ICE协议。ICE规定了一套方法,致力于在通信隔断之间建立一条有效的通道:能直连就直连,必要时STUN协商,再不行使用TURN。
3.3 针对UDP的优化建议
UDP是一个简单常用的协议,经常用于引导其他传输协议。事实上,UDP的特色在于她所省略的那些功能:连接状态、握手、重发、重组、重排、拥塞控制、拥塞预防、流量控制,甚至可选的错误检测,统统没有。这个面向消息的最简单的传输层在提供灵活性的同时,也给实现者带来了麻烦。
与内置流量和拥塞控制以及拥塞预防的TCP不同,UDP应用程序必须自己实现这些机制。拥塞处理做的不到位的UDP应用程序很容易堵塞网络,造成网路性能下降,严重时还会导致网络拥塞崩溃。如果你想在自己的应用程序中使用UDP,务必要认真研究和学习当下的最佳实践和建议。RFC 5405就是这么一份文档,它对设计单播UDP应用程序给出了很多设计建议,简述如下:
. 应用程序必须容忍各种因特网路径条件
. 应用程序应该控制传输速度
. 应用程序应该对所有流量进行拥塞控制
. 应用程序应该使用与TCP相近的带宽
. 应用程序应该准备基于丢包的重发计数器
. 应用程序应该不发送大于路径MTU的数据表
. 应用程序应该处理数据报丢失、重复和重排
. 应用程序应该足够稳定以支持2分钟以上的交付延迟
. 应用程序应该支持IPv4 UDP校验和,必须支持IPv6校验和
. 应用程序可以在需要时使用keep-alive(最小间隔15秒)
设计新传输协议必须经过周密的考虑、规划和研究,否则就是不负责任。要尽可能利用已有的库或框架,这个库或框架应该考虑了NAT穿透,而且能够与其他并发的网络流量和谐共存。
网友评论