UDP协议精华总结

引入

本文面向有一定计网基础的读者。

TCP, UDP, IP 可以说是计网的常客了,它们三紧密联系,以至于经常看见“TCP/IP”这样的名词,但实际上 UDP 也是基于 IP 的,但似乎很少见“UDP/IP”。

也不奇怪,人人都说 UDP 与 TCP 相比过于简单,因此提的少。但是这并不意味着 UDP 用的少,相反它经常用于那些不要求数据可靠性的场景:音频传输、视频传输、在线游戏等。这些场景对数据可靠性、完整性要求不高,丢失少量数据包带来的影响并不大,比如音频传输中丢包的经典表现是听不清或听不到对方说的话;视频传输中丢包的经典表现是卡顿。

我认为先熟悉简单的 UDP 协议,能更好地理解复杂的 TCP 协议。

UDP协议简介

UDP, User Datagram Protocol 用户数据报协议。UDP 是传输层协议之一,应用程序可以调用该协议向其他计算机发送数据,只不过它不像它的兄弟 TCP 一样可靠,UDP 只是简单的将数据发送给指定目标,不提供任何保证(差错纠正、流量控制、拥塞控制,这些都是 TCP 才有的)。

与 TCP 相比,UDP 仅有的两个优点是:

  1. 保留消息边界:一般情况下,应用程序调用 UDP 发送数据时,只产生一个 UDP 数据报,从而也只发送一个 IP 数据报。从 socket 编程的角度来说就是,发送端调用 API 发送一次数据,接收端也只需要调用一次 API 便可接收到完整的数据。
    • 其实这里面有很多前提的,比如:(1)UDP 协议实现会限制一次发送 UDP 数据报的长度,尽量避免发生 IP 分片;(2)数据在传输过程中没有丢失、出错,接收端接收到的是完整无误的 UDP 数据报。
  2. 提供差错检测:虽然 UDP 不提供差错恢复,但是它却提供了差错检测(是的,做事只做一半,能检测到差错,却不能恢复)。

一句话总结:UDP 是无连接、面向消息、保留消息边界、具有差错检验功能的传输层协议。

UDP头部

一个典型 UDP 数据报的格式如下图所示:

image-20230323171445425

各个字段的解释如下:

  1. 源端口:占 2 字节,用于标识发送方进程(端口是抽象概念,没有物理实体)。可选字段,如果不要求对方回复,可以置 0。

  2. 目的端口:占 2 字节,用于标识接收方进程。

  3. 长度:占 2 字节,表示整个 UDP 数据报的长度,单位是字节。由于 16 比特能够表示的无符号数范围是 [ 0 , 2 16 − 1 ] = [ 0 , 65535 ] [0, 2^{16} - 1] = [0, 65535] [0,2161]=[0,65535],因此理论上 UDP 数据报最大长度为 65535 字节,即 64KB。

    • 实际上该字段最小值 8(没有 payload,只有 UDP 头)。
    • 该字段是冗余的,因为 IP 头里也有一个长度字段表示 IP 数据报长度,UDP 数据报的长度可以间接计算得到:[ UDP数据报长度 = IP数据报长度 - IP头部长度 ]。
  4. 检验和:占 2 字节,用于表示整个 UDP 数据报存在比特差错,通过某种算法计算得到。IPv4 可选但强烈推荐,IPv6 强制要求。

    • 一个端到端的检验和。由发送方计算,接收方检测,传输过程中不会被途经设备修改(除非经过了 NAT 地址转换)。
    • 若该字段为 0x0000 则表示发送方没有计算检验和。因此当计算结果恰好为 0x0000 时,会设置为反码 0xFFFF(那计算结果有没有可能恰好是 0xFFFF 呢?这冲突吗?我不知道= =)。
    • 两个“虚”的部分参与检验和的计算:虚 0 字节和虚 IP 头部,但这两个虚的部分不会真正发送出去,只用于计算检验和。检验和计算要求 UDP 数据报的长度为偶数,因此会在奇数长度的 UDP 数据报末尾填充字节 0。此外,它还使用了一个 12 字节的伪 IP 头部,包含源 IP 地址、目的 IP 地址、零填充、协议号、长度。
  5. 负载:应用程序要发送的数据。

image-20230324094041297

关于最大UDP数据报长度的讨论

上文介绍过,仅从 UDP 协议的角度来说,UDP 数据报的最大长度是 65535 字节。

但是 UDP 数据报是封装在 IP 数据报里的,而 IP 数据报的最大长度也是 65535 字节,也就限制了最大 UDP 数据报的最大长度要扣除一个 IP 头的长度。没有选项的 IP 头长度为 20 字节,因此一般 UDP 数据报的长度为 65515 字节,再扣除 8 字节的 UDP 头部长度,UDP 的最大负载为 65507 字节。

实现限制

不过,这一切都建立在 IP 头没有额外选项的前提下,这对于 UDP 协议的实现来说太过麻烦,毕竟实现者无法判断用户是否使用 IP 头的额外选项。

因此在实现中会提供 API 供应用程序查询最大 IP 数据报长度,典型值为 8192 或 65535 字节(当然也提供了 API 设置该值,但有没有范围限制呢?鬼知道这么实现者的心思= =)。

查询得知最大 IP 数据报长度后,便可自行决定最大 UDP 数据报长度。

UDP数据报截断

上文介绍过,UDP 是保留消息边界的传输协议,发送方调用发送 API 发送一次,接收方只需要调用一次接收 API 便可接收到完整的数据报。

然而在实现中,接收 API 允许指定一次读操作完成时返回的最大字节数,在不知道接收到的 UDP 数据报究竟多大的情况下,有可能并不能一次性全部接收完(虽然通过种种限制,一般情况下都是能一次接收完的)。当接收到的 UDP 数据报长度超过了指定的读取大小会发生什么呢?

通常做法有两个:截断和留到下个读操作。截断直接丢弃了超出的部分,导致接收方得到的数据不完整;留到下个读操作将超出部分保留,导致每次读操作都不能保证读到一个完整的数据报。因此截断是大多数实现者的做法。

路径MTU发现

MTU, Maximum Transmission Unit 最大传输单元,是链路层的最大有效载荷,即最大的 IP 数据报的大小,典型值为 1500 字节。

路径 MTU 是指:包含所有链路的整个网络上,最小的 MTU。

路径 MTU 发现是指:通过设置不发生 IP 分片,确定到达目的地的 MTU 大小。

UDP数据报与IP分片

IP 分片概念:当 IP 数据报大小超过 MTU 便要进行分片,将 IP 数据报拆分为多个片段,每个片的最终大小都不超过 MTU。

  • 若设置了 IP 头不分片标志(DF, Don’t Fragment),则转发设备直接丢弃该数据报。
  • IP 分片可能发生在多个设备上进行多次,因为各个局域网链路的 MTU 可能不同。
  • IP 分片只在目的地进行重组。因为分片经过路由转发的路径有可能不同,一个转发设备大概率不能“看到”所有分片,只能看到分片的一个子集,不具备重组分片的条件。

通过一个例子来理解 IP 分片的原理:以 MTU=1500 为例,对一个大小为 3020 字节、负载为 UDP 数据报的 IP 数据报进行分片的结果如下图所示。

image-20230324103534355

原理要点概括如下:

  • IP 分片针对的是 IP 数据报的负载。

  • 每个分片都有 IP 头部。

  • MF、标识和片偏移用于目的地进行分片重组,获得原始 IP 数据报,这三个字段在 IP 头部中,图中没有详细展示)。

    • 标识:用于标识分片属于同一个原始 IP 数据报,由进行分片的设备设置,来自同一个原始 IP 数据报的分片的标识相同。

    • MF:More Fragment,表示还有更多分片。最后一片的 MF=0,其余片的 MF=1。

    • 片偏移:分片负载的第一个字节在原始 IP 数据报中的偏移量,单位是 8 字节。

分片具体过程如下:

  1. 因此分片个数的计算公式为: 分片个数 = ⌈ I P 负载长度 M T U − I P 头长度 ⌉ 分片个数 = \lceil \frac{IP负载长度}{MTU -IP头长度} \rceil 分片个数=MTUIP头长度IP负载长度,此例子中为 ⌈ 3000 1500 − 20 ⌉ = 3 \lceil \frac{3000}{1500 - 20} \rceil = 3 1500203000=3
  2. 显然第一个分片能承担 1480 字节负载,剩余 3000-1480=1520 字节;
  3. 第二个分片继续承担 1480 字节负载,剩余 1520-1480=40 字节;
  4. 第三个分片承担剩余 40 字节负载。

IP 分片的其他要点:

  1. 每个分片都要有 IP 头部,会引入额外的传输开销。

  2. 每个分片的 IP 头部都要修改总长度字段。

  3. 任何一个分片在转发过程中丢失,都意味着整个数据报丢失。

  4. 通常转发设备优先转发片偏移更大的分片,这样做的好处是接收方可以提前确定所需缓存空间的最大值(最后一个分片的片偏移 * 8 + 最后一个分片的总长度 = 原始 IP 数据报的长度)。

重组超时和超时重传

重组超时:接收方 IP 层收到任何一个分片就启动一个计时器,随后到达的其他分片不会重置计时器,若计时结束还没有收到所有分片,则重组超时。

响应:根据 IP 协议实现的不同而略有差异,所有方案都丢弃所有分片,部分方案产生 ICMP 错误报文通知发送方,而部分方案不产生 ICMP 错误。

超时重传:UDP 本身没有超时重传机制,需要应用程序自行设置。

UDP-Lite

UDP-Lite 是 UDP 的改进,仅仅将 UDP 头部中的长度字段改成了检验和覆盖范围

出现原因:有些应用程序可以接受在发送和接收的数据里存在比特差错,那么检验和就没有必要覆盖整个数据报,只需要覆盖那些不允许存在比特差错的数据。

UDP-Lite 头部如下所示:

image-20230324095552969

检验和覆盖范围:占 2 字节,单位为字节,表示从 UDP-Lite 头部的第 1 个字节开始,被检验和覆盖的字节数。

  • 0 是特殊值,表示覆盖整个数据报,即 64KB。
  • 1-7 无效,因为头部都占 8 字节了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值