传输层之UDP协议

学习传输层的两大协议之一:UDP。

UDPUser Datagram Protocol的简称, 中文名是用户数据报协议,是OSIOpen System Interconnection,开放式系统互联) 参考模型中一种无连接的传输层协议传输可靠性没有保证UDP只是在IP的数据报服务之上增加了最基本的服务:复用和分用以及差错检测。

✏️ 1、UDP的特点

UDP提供不可靠服务,具有TCP所没有的优势:

  • UDP无连接,时间上不存在建立连接需要的时延。空间上,TCP需要在端系统中维护连接状态,需要一定的开销。此连接装入包括接收和发送缓存,拥塞控制参数和序号与确认号的参数。UCP不维护连接状态,也不跟踪这些参数,开销小。空间和时间上都具有优势。

    DNS如果运行在TCP之上而不是UDP,那么DNS的速度将会慢很多。 HTTP使用TCP而不是UDP,是因为对于基于文本数据的Web网页来说,可靠性很重要。 同一种专用应用服务器在支持UDP时,一定能支持更多的活动客户机。

  • 分组首部开销小TCP首部20字节,UDP首部8字节。

  • UDP没有拥塞控制,应用层能够更好的控制要发送的数据和发送时间,网络中的拥塞控制也不会影响主机的发送速率。某些实时应用要求以稳定的速度发送,能容忍一些数据的丢失,但是不能允许有较大的时延(比如实时视频,直播等)。

  • UDP提供尽最大努力的交付,不保证可靠交付。所有维护传输可靠性的工作需要用户在应用层来完成。没有TCP的确认机制、重传机制。如果因为网络原因没有传送到对端,UDP也不会给应用层返回错误信息。

  • UDP面向报文的,对应用层交下来的报文,添加首部后直接乡下交付为IP层,既不合并,也不拆分,保留这些报文的边界。对IP层交上来UDP用户数据报,在去除首部后就原封不动地交付给上层应用进程,报文不可分割,是UDP数据报处理的最小单位。

  • 正是因为这样,UDP显得不够灵活,不能控制读写数据的次数和数量。比如我们要发送100个字节的报文,我们调用一次sendto函数就会发送100字节,对端也需要用recvfrom函数一次性接收100字节,不能使用循环每次获取10个字节,获取十次这样的做法。

  • UDP常用一次性传输比较少量数据的网络应用,如DNSSNMP等,因为对于这些应用,若是采用TCP,连接的创建,维护和拆除带来不小的开销。UDP也常用于多媒体应用(如IP电话,实时视频会议,流媒体等)数据的可靠传输对他们而言并不重要,TCP的拥塞控制会使他们有较大的延迟,也是不可容忍的。

  • UDP支持一对一、一对多、多对一和多对多。

✏️ 2UDP报文头部

UDP头部的标识如下:

  1. 16位源端口号:源主机的应用程序使用的端口号。

  2. 16位目的端口号:目的主机的应用程序使用的端口号。

  3. 16位UDP长度:是指UDP头部和UDP数据的字节长度。因为UDP头部长度为8字节,所以该字段的最小值为8。

  4. 16位UDP校验和:该字段提供了与TCP校验字段同样的功能;该字段是可选的,当源主机不想计算校验和,则直接令该字段全为0。

🖋️ 2.1、包大小

🐹 2.1.1、 UDP 报文大小的影响因素

  • UDP协议本身,UDP协议中有16位的UDP报文长度,那么UDP报文长度不能超过 2161=655352^{16}-1=65535

  • 以太网(Ethernet)数据帧的长度,数据链路层的MTU(最大传输单元);

  • socket的UDP发送缓存区大小。

🐹 2.1.2、 UDP数据包最大长度

根据 UDP 协议,从 UDP 数据包的包头可以看出,UDP 的最大包长度是65535的个字节。由于UDP包头占8个字节,而在IP层进行封装后的IP包头占去20字节,所以这个是UDP数据包的最大理论长度是65507字节。如果发送的数据包超过65507字节,sendsendto函数会错误码1(Operation not permitted, Message too long),当然,一个数据包能否发送65507字节,还和UDP发送缓冲区大小(linuxUDP发送缓冲区大小为:cat /proc/sys/net/core/wmem_default)相关,如果发送缓冲区小于65507字节,在发送一个数据包为65507字节的时候,sendsendto函数会错误码1(Operation not permitted, No buffer space available)。

🐹 2.1.3、 UDP数据包理想长度

理论上 UDP 报文最大长度是65507字节,实际上发送这么大的数据包效果最好吗?我们知道UDP是不可靠的传输协议,为了减少 UDP 包丢失的风险,我们最好能控制 UDP 包在下层协议的传输过程中不要被切割。

局域网环境下,建议将UDP数据控制在1472字节以下。

以太网(Ethernet)数据帧的长度必须在46-1500字节之间,这是由以太网的物理特性决定的,这个1500字节被称为链路层的MTU(最大传输单元)。但这并不是指链路层的长度被限制在1500字节,其实这这个MTU指的是链路层的数据区,并不包括链路层的首部和尾部的18个字节。所以,事实上这个1500字节就是网络层IP数据报的长度限制。因为IP数据报的首部为20字节,所以IP数据报的数据区长度最大为1480字节。而这个1480字节就是用来放TCP传来的TCP报文段或UDP传来的UDP数据报的。又因为UDP数据报的首部8字节,所以UDP数据报的数据区最大长度为1472字节。这个1472字节就是我们可以使用的字节数。

当我们发送的UDP数据大于1472的时候会怎样呢? 这也就是说IP数据报大于1500字节,大于MTU,这个时候发送方IP层就需要分片(fragmentation)。把数据报分成若干片,使每一片都小于MTU,而接收方IP层则需要进行数据报的重组。这样就会多做许多事情,而更严重的是,由于UDP的特性,当某一片数据传送中丢失时,接收方无法重组数据报,将导致丢弃整个UDP数据报。因此,在普通的局域网环境下,我建议将UDP的数据控制在1472字节以下为好。

Internet编程时,建议将UDP数据控制在548字节以下。

进行Internet编程时则不同,因为Internet上的路由器可能会将MTU设为不同的值。如果我们假定MTU为1500来发送数据,而途经的某个网络的MTU值小于1500字节,那么系统将会使用一系列的机制来调整MTU值,使数据报能够顺利到达目的地,这样就会做许多不必要的操作。鉴于Internet上的标准MTU值为576字节,所以我建议在进行Internet的UDP编程时, 最好将UDP的数据长度控件在548字节(576-8-20)以内。 这句话貌似有问题,unix网络编程第一卷里说:ipv4协议规定ip层的最小重组缓冲区大小为576!所以,建议UDP包不要超过这个大小,而不是因为internet的标准MTU是576!

🖋️ 2.2、UDP校验

在计算校验和的时候,需要在UDP数据报之前增加12字节的伪首部,伪首部并不是UDP真正的首部。只是在计算校验和,临时添加在UDP数据报的前面,得到一个临时的UDP数据报。校验和就是按照这个临时的UDP数据报计算的。伪首部既不向下传送也不向上递交,而仅仅是为了计算校验和。这样的校验和,既检查了UDP数据报,又对IP数据报的源IP地址和目的IP地址进行了检验。

UDP校验和的计算方法和IP数据报首部校验和的计算方法相似,都使用二进制反码运算求和再取反。

发送方,首先是把全零放入校验和字段并且添加伪首部,然后把UDP数据报看成是由许多16位的子串连接起来,若UDP数据报的数据部分不是偶数个字节,则要在数据部分末尾增加一个全零字节(此字节不发送),接下来就按照二进制反码计算出这些16位字的和。将此和的二进制反码写入校验和字段。在接收方,把收到得UDP数据报加上伪首部(如果不为偶数个字节,还需要补上全零字节)后,按二进制反码计算出这些16位字的和,当无差错时其结果全为1,否则就表明有差错出现,接收方应该丢弃这个UDP数据报。

注意:

1.校验时,若UDP数据报部分的长度不是偶数个字节,则需要填入一个全0字节,但是次字节和伪首部一样,是不发送的。

2.如果UDP校验和校验出UDP数据报是错误的,可以丢弃,也可以交付上层,但是要附上错误报告,告诉上层这是错误的数据报。

3.通过伪首部,不仅可以检查源端口号,目的端口号和UDP用户数据报的数据部分,还可以检查IP数据报的源IP地址和目的地址。 这种差错检验的检错能力不强,但是简单,速度快。

✏️ 3、发送和接收

🖋️ 3.1、UDP的通信有界性:

在阻塞模式下,UDP的通信是以数据包作为界限的,即使server端的缓冲区再大也要按照client发包的次数来一次一次地接收,client发送多少次,server就需接收多少次。

🖋️ 3.2、UDP数据包的无序性和非可靠性:

client依次发送1、2、3三个UDP数据包,server端先后调用3次接收函数,可能会依次收到3、2、1次序的数据包,收包可能是1、2、3的任意排列组合,也可能丢失一个或多个数据包。

🖋️ 3.3、UDP数据包的接收:

client发送两次UDP数据,第一次 500字节,第二次300字节,server端阻塞模式下接包,第一次recvfrom(1000),收到是 1000,还是500,还是300,还是其他?

由于UDP通信的有界性,接收到只能是500或300,又由于UDP的无序性和非可靠性,接收到可能是300,也可能是500,也可能一直阻塞在recvfrom调用上,直到超时返回(也就是什么也收不到)。

在假定数据包是不丢失并且是按照发送顺序按序到达的情况下,server端阻塞模式下接包,先后三次调用:recvfrom(200)recvfrom(1000)recvfrom(1000),接收情况如何呢?

由于UDP通信的有界性,第一次recvfrom(200)将接收第一个500字节的数据包,但是因为用户空间buf只有200字节,于是只会返回前面200字节,剩下300字节将丢弃。第二次recvfrom(1000)将返回300字节,第三次recvfrom(1000)将会阻塞。

🖋️ 3.4、UDP包分片问题:

如果MTU是1500,Client发送一个8000字节大小的UDP包,那么Server端阻塞模式下接包,在不丢包的情况下,recvfrom(9000)是收到1500,还是8000。如果某个IP分片丢失了,recvfrom(9000)又返回什么呢?

根据UDP通信的有界性,在buf足够大的情况下,接收到的一定是一个完整的数据包,UDP数据在下层的分片和组片问题由IP层来处理,提交到UDP传输层一定是一个完整的UDP包,那么recvfrom(9000)将返回8000。如果某个IP分片丢失,UDP里有个CRC检验,如果包不完整就会丢弃,也不会通知是否接收成功,所以UDP是不可靠的传输协议,那么recvfrom(9000)将阻塞。

🖋️ 3.5、UDP丢包问题

在不考虑UDP下层IP层的分片丢失,CRC检验包不完整的情况下,造成UDP丢包的因素有哪些呢?

3.5.1、UDP socket缓冲区满造成的UDP丢包:

通过 cat /proc/sys/net/core/rmem_defaultcat /proc/sys/net/core/rmem_max可以查看socket缓冲区的缺省值和最大值。如果socket缓冲区满了,应用程序没来得及处理在缓冲区中的UDP包,那么后续来的UDP包会被内核丢弃,造成丢包。在socket缓冲区满造成丢包的情况下,可以通过增大缓冲区的方法来缓解UDP丢包问题。但是,如果服务已经过载了,简单的增大缓冲区并不能解决问题,反而会造成滚雪球效应,造成请求全部超时,服务不可用。

3.5.2、UDP socket缓冲区过小造成的UDP丢包:

如果Client发送的UDP报文很大,而socket缓冲区过小无法容下该UDP报文,那么该报文就会丢失。

3.5.3、ARP缓存过期导致UDP丢包:

ARP 的缓存时间约10分钟,APR 缓存列表没有对方的 MAC 地址或缓存过期的时候,会发送 ARP 请求获取 MAC 地址,在没有获取到 MAC 地址之前,用户发送出去的 UDP 数据包会被内核缓存到 arp_queue 这个队列中,默认最多缓存3个包,多余的 UDP 包会被丢弃。被丢弃的 UDP 包可以从 /proc/net/stat/arp_cache 的最后一列的 unresolved_discards 看到。当然我们可以通过 echo 30 > /proc/sys/net/ipv4/neigh/eth1/unres_qlen 来增大可以缓存的 UDP 包。

UDP 的丢包信息可以从 cat /proc/net/udp 的最后一列drops中得到,而倒数第四列 inode 是丢失 UDP 数据包的 socket 的全局唯一的虚拟i节点号,可以通过这个 inode 号结合 lsof (lsof -P -n | grep 25445445)来查到具体的进程。

🖋️ 3.6、UDP的冗余传输方案

在外网通信链路不稳定的情况下,有什么办法可以降低UDP的丢包率呢?一个简单的办法来采用冗余传输的方式。如下图,一般采用较多的是延时双发,双发指的是将原本单发的前后连续的两个包合并成一个大包发送,这样发送的数据量是原来的两倍。这种方式提高丢包率的原理比较简单,例如本例的冗余发包方式,在偶数包全丢的情况下,依然能够还原出完整的数据,也就是在这种情况下,50%的丢包率,依然能够达到100%的数据接收。

✏️ 4、使用UDP协议

🖋️ 4.1、为什么需要UDP

UDP(User Datagram Protocol)传输IP传输非常类似,它的传输方式也是"Best Effort"的,所以UDP协议也是不可靠的。TCP就是为了解决IP层不可靠的传输层协议,既然UDP是不可靠的,为什么不直接使用IP协议而要额外增加一个UDP协议呢

1、一个重要的原因是IP协议中并没有端口(port)的概念。IP协议进行的是IP地址到IP地址的传输,这意味者两台计算机之间的对话。但每台计算机中需要有多个通信通道,并将多个通信通道分配给不同的进程使用。一个端口就代表了这样的一个通信通道UDP协议实现了端口,从而让数据包可以在送到IP地址的基础上,进一步可以送到某个端口。

2、对于一些简单的通信,我们只需要“Best Effort”式的IP传输就可以了,而不需要TCP协议复杂的建立连接的方式(特别是在早期网络环境中,如果过多的建立TCP连接,会造成很大的网络负担,而UDP协议可以相对快速的处理这些简单通信)

3、在使用TCP协议传输数据时,如果一个数据段丢失或者接收端对某个数据段没有确认,发送端会重新发送该数据段。TCP重新发送数据会带来传输延迟和重复数据,降低了用户的体验。对于迟延敏感的应用,少量的数据丢失一般可以被忽略,这时使用UDP传输将能够提升用户的体验

🖋️ 4.2、UDP应用场景

4.2.1、高通信实时性要求和低持续性要求的场景

当应用程序对传输的可靠性要求不高,但是对传输速度和延迟要求较高时,可以用UDP协议来替代TCP协议在传输层控制数据的转发。

UDP适合于实时数据传输,如语音视频通信,因为它们即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。

常用的使用UDP的协议包括(括号中是默认的端口号):TFTP(69)SNMP(161)NFSDNS(53)BOOTP

通信的持续性有两种通信类型:

  • 短连接通信;

  • 长连接通信。

对于短连接通信,一方面如果业务只需要发一两个包并且对丢包有一定的容忍度,同时业务自己有简单的轮询或重复机制,那么采用UDP会较为好些。在这样的场景下,如果用TCP,仅仅握手就需要3个包,这样显然有点不划算,一个典型的例子是DNS查询。另一方面,如果业务实时性要求非常高,并且不能忍受重传,那么只能用UDP了,例如NTP协议,重传NTP消息纯属添乱(为什么呢?重传一个过期的时间包过来,还不如发一个新的UDP包同步新的时间过来)。如果NTP协议采用TCP,撇开握手消耗较多数据包交互的问题,由于TCPNagel算法等影响,用户数据会在一定情况下会被内核缓存延后发送出去,这样时间同步就会出现比较大的偏差,协议将不可用。

4.2.2、多点通信的场景

对于一些多点通信的场景,如果采用有连接的TCP,那么就需要和多个通信节点建立其双向连接,然后有时在NAT环境下,两个通信节点建立其直接的TCP连接不是一个容易的事情,在涉及NAT穿越的时候,UDP协议的无连接性使得穿透成功率更高,由于UDP的无连接性,其完全可以向一个组播地址发送数据或者轮转地向多个目的地持续发送相同的数据,从而更为容易实现多点通信。

🖋️ 4.3、可靠性

UDP协议不可靠,可靠性由谁保障?

UDP将数据从源端发送到目的端时,无需事先建立连接,没有使用TCP中的确认技术或滑动窗口机制,因此UDP不能保证数据传输的可靠性,也无法避免接收到重复数据的情况。UDP传输的可靠性由应用层负责,由应用程序根据需要提供报文到达确认、排序、流量控制等功能。

要使用UDP来构建可靠的面向连接的数据传输,就要实现类似于TCP协议的:

  • 超时重传(定时器)【解决报文丢失问题】;

  • 有序接受 (添加包序号)【解决包乱序问题】;

  • 应答确认 (Seq/Ack应答机制)【保证可靠性】;

  • 滑动窗口流量控制等机制 (滑动窗口协议)【解决流量控制问题】。

目前已经有一些实现UDP可靠传输的机制,比如UDTUDP-based Data Transfer Protocol): 基于UDP的数据传输协议,UDT是一种互联网数据传输协议。UDT的主要目的是支持高速广域网上的海量数据传输,而互联网上的标准数据传输协议TCP在高带宽长距离网络上性能很差。 顾名思义,UDT建于UDP之上,并引入新的拥塞控制和数据可靠性控制机制。UDT是面向连接的双向的应用层协议。它同时支持可靠的数据流传输和部分可靠的数据报传输。 由于UDT完全在UDP上实现,它也可以应用在除了高速数据传输之外的其它应用领域,例如点到点技术(P2P),防火墙穿透,多媒体数据传输等等。

🖋️ 4.4、工作原理

主机A发送数据包时,这些数据包是以有序的方式发送到网络中的,每个数据包独立地在网络中被发送,所以不同的数据包可能会通过不同的网络路径到达主机B。这样的情况下,先发送的数据包不一定先到达主机B。因为UDP数据包没有序号,主机B将无法通过UDP协议将数据包按照原来的顺序重新组合,所以此时需要应用程序提供报文的到达确认、排序和流量控制等功能。

通常情况下,UDP采用实时传输机制时间戳来传输语音和视频数据。

🖋️ 4.5、影响UDP高效因素

4.5.1、无法智能利用空闲带宽导致资源利用率低:

一个简单的事实是UDP并不会受到MTU的影响,MTU只会影响下层的IP分片,对此UDP一无所知。在极端情况下,UDP每次都是发小包,包是MTU的几百分之一,这样就造成UDP包的有效数据占比较小(UDP头的封装成本);或者,UDP每次都是发巨大的UDP包,包大小MTU的几百倍,这样会造成下层IP层的大量分片,大量分片的情况下,其中某个分片丢失了,就会导致整个UDP包的无效。由于网络情况是动态变化的,UDP无法根据变化进行调整,发包过大或过小,从而导致带宽利用率低下,有效吞吐量较低。而TCP有一套智能算法,当发现数据必须积攒的时候,就说明此时不积攒也不行,TCP的复杂算法会在延迟和吞吐量之间达到一个很好的平衡。

4.5.2、无法动态调整发包:

由于UDP没有确认机制,没有流量控制和拥塞控制,这样在网络出现拥塞或通信两端处理能力不匹配的时候,UDP并不会进行调整发送速率,从而导致大量丢包。在丢包的时候,不合理的简单重传策略会导致重传风暴,进一步加剧网络的拥塞,从而导致丢包率雪上加霜。更加严重的是,UDP的无秩序性和自私性,一个疯狂的UDP程序可能会导致这个网络的拥塞,挤压其他程序的流量带宽,导致所有业务质量都下降。

4.5.3、改进UDP的成本较高:

可能有同学想到针对UDP的一些缺点,在用户态做些调整改进,添加上简单的重传和动态发包大小优化。然而,这样的改进并比简单的,UDP编程可是比TCP要难不少的,考虑到改造成本,为什么不直接用TCP呢?当然可以拿开源的一些实现来抄一下(例如:libjingle),或者拥抱一下Google的QUIC协议,然而,这些都需要不少成本的。

✏️ 5UDPTCP的区别

  1. TCP面向连接的传输控制协议,而UDP 提供了无连接的数据报服务;

  2. TCP 具有高可靠性,确保传输数据的正确性,不出现丢失或乱序;UDP 在传输数据前不建立连接,不对数据报进行检查与修改,无须等待对方的应答,所以会出现分组丢失、重复、乱序,应用程序需要负责传输可靠性方面的所有工作;

  3. UDP 具有较好的实时性,工作效率较 TCP 协议高;

  4. UDP 首部结构比 TCP 的首部结构简单,因此网络开销也小

UDP(User Datagram Protocol)

TCP(Transmission Control Protocol)

无连接

面向连接

支持一对一、一对多、多对一和多对多交互通信

每一条TCP连接只能有两个端点EP,只能一对一通信

对应用层交付的报文直接打包

面向字节流

尽最大努力交付,也就是不可靠;不使用流量控制和拥塞控制

可靠传输,使用流量控制和拥塞控制

首部开销小,仅8字节

首部最小20字节,最大60字节

Last updated