🌐 第三章 网络间的通用语言TCP/IP
📄 一句话概括

TCP 用 20 字节的头把"可靠"做成全套服务,UDP 只留 8 字节,做一件事:把数据交给正确的应用程序。它不建连接、不确认、不重传、不排序、不控速——不是做不到,是故意不做。代价是丢包乱序全归应用自己管;回报是极低的延迟与开销,外加一项 TCP 永远给不了的能力:广播和组播。标题里那个"只"字,就是它的全部人设。

📝 本节和 3.3 的分工(别混)

3.3 只在 TCP/UDP 对比表里给 UDP 留了一格"同城闪送",讲的是怎么选。 本节是 UDP 的正面特写——它的头长什么样、为什么这么薄、砍掉了什么、这些取舍在真实世界里怎么用。

📝 本节在第三章里的位置

3.18 和 3.19 拆的是 TCP——一个把事做到极致的传输层协议。 本节是它的反面对照:同一个楼层、同一批端口、同一层 IP 之上,另一个协议几乎什么都不做。

一、先正名:UDP 是谁

定位一句话:

UDP = IP + 端口 + 校验和。

它几乎是"把 IP 的能力原样端给应用程序",只在上面薄薄加了两样东西:

加的东西 干什么
端口 让数据找到正确的程序——这就是标题说的"分发"
校验和 让明显损坏的数据别被交上去

除了这两样,它什么都没做。这既是它被嫌弃的地方,也是它被需要的地方。

二、首部只有 8 字节(本节第一张图)

四行两列,一共就四个字段:

字段 位宽 干什么
源端口 16 位 谁发的(不用回信时可以填 0 省略)
目的端口 16 位 交给哪个程序——"分发"全靠它
长度 16 位 UDP 头 + 数据的总长(最小 8,最大 65535)
校验和 16 位 检验数据有没有损坏

对比 3.18 那 20 字节:UDP 砍掉了 序号、确认号、窗口、标志位。

❗ 砍掉这四个字段,就等于砍掉了"可靠传输"的全部机制

回看 3.18 的一句话:那四个字段就是"可靠"的全部零件。

UDP 一个都不留——所以它不可能做到可靠、不可能做到排序、不可能做到流量控制。这不是"简化",是从根上不做。

💡 反倒是"长度"这个字段值得琢磨

3.18 特别指出过:TCP 头里没有"总长度"字段,因为它外面套了 IP 包,可以从 IP 头的总长度倒推。

那 UDP 为什么必须自己带一个长度?

因为 IP 头里的总长度是整个 IP 包的长度,还包含了 IP 头自己;更麻烦的是——一旦发生分片,每个分片都会带上一个 IP 头,它的"总长度"只是那个分片的长度,靠它根本算不出原来的 UDP 数据报有多长。

也就是说:IP 的长度字段没法替 UDP 表达"我这个数据报有多大"。这个字段,是"数据报有边界"的第一层证据。

⚠️ 校验和:只查不修

UDP 的校验和覆盖 伪首部(源 IP + 目的 IP + 协议号 + UDP 长度)+ UDP 头 + 数据——它连"这个包是不是送错机器了"都在查。为什么?因为如果包被投递到错误的机器,而那里恰好有程序守着同一个端口号,就出大乱子了。

但它是只查不修:校验不过就悄悄丢掉,谁也不通知。这和 TCP 完全不同——TCP 也会丢,但丢了会靠重传补回来。

两个细节:IPv4 里 UDP 校验和可以关掉(填 0 表示不算);IPv6 里是强制的,不许关。

三、四个"不管",就是"不可靠"的全部含义

不管什么 表现 后果
不建连接 没有握手,拿起来就发 想发就发,但对方在不在完全不知道
不确认、不重传 发出去就忘 丢了就丢了,发送方不会知道
不排序 每个数据报独立走路 后发的可能先到(收 1.4 的逐跳接力:每一跳都可能出岔子)
不控速 没有窗口(收 3.18),没有拥塞控制 接收方缓冲区满了直接丢;发送方也不会自己收敛

一句话:

TCP 把网络的不确定性替应用挡掉了;UDP 把不确定性原样交给应用。

❗ 核心认知:"不可靠"是**设计选择**,不是缺陷

别把 UDP 当成"简化版/劣质版 TCP"。它面向的是另一类需求——那些丢掉一小块也完全不影响使用的数据。

视频通话里丢一帧:画面糊 1/30 秒,人根本察觉不到。 但如果为这一帧重传:画面卡住等一下,后面的帧也跟着延迟——这时候"补"是负作用。

所以 UDP 的逻辑不是"我做不到",而是"这类数据不需要,做了反而添乱"。

四、数据报 vs 字节流:最本质的差别(本节第二张图)

同样的动作交给两个协议,接收方拿到的东西根本不一样。

TCP UDP
交给应用的是什么 一条连续的字节流 一个一个独立的数据报
边界 没有(你写 3 次,对面可能一次全读完) 保留(一次 receive 正好收到一个完整数据报)
少写一次会怎样 可能读到两次 write 的内容粘在一起 不可能——没发就没有
一句口诀 字节流没有消息边界 数据报边界天然保留

第二张图画的就是这件事:同样由程序写三次 A、B、C——TCP 那边过来是连成一片的 ABC(这就是 3.18 那个"粘包"),UDP 那边过来是三个各自完整的包裹。

收 Java 13.3 的四笔账

这一节正好把 UDP通信 里那几条当时只说了"就这么写"的规矩,全部补上原因:

Java 13.3 的规矩 协议层的原因
"地址在包裹上,不在连线上" 无连接的工程体现。TCP 的地址定在"连线"上(new Socket(ip, 端口)),UDP 的地址贴在每一个包裹上(DatagramPacket 构造时要传 IP 和端口)
"两边用同一个类,没有服务端客户端之分" 也是无连接的后果。TCP 的角色分工来自"谁开门谁敲门",UDP 谁都能寄、谁都能收(接收方只是指定了端口)
"不报错 ≠ 发到了" 这就是不可靠的定义
"整进整出,箱子小了多出来的直接丢" 因为收发单位是"数据报",一个数据报是一整个整体——不可能像字节流那样"分两次装"
💡 补一条:Java 13.3 说"一声不吭",其实可以再精确一点

严格说,UDP 协议本身不报错。但 ICMP 会替它报一部分错——如果接收方主机活着、只是那个端口没人守,对方会回一条 ICMP 类型 3 / 代码 3「端口不可达」(收 3.15)。

这正是 3.15 那句"UDP 唯一的反馈来自 ICMP"的另一半含义。编程层面通常把这条消息静默处理掉了,所以你"看不到",但它在网络上确实存在。

五、UDP 的五个优点(不只是"快")

很多人以为 UDP 的优点只有一个"快"。其实"快"是结果,根子在这五条:

  1. 没有握手延迟 — TCP 要先三次握手(约 1.5 个往返)才能发第一个字节,UDP 直接就发。DNS 查询是典型:一次一问一答,全靠 UDP 才能做到毫秒级
  2. 头小 — 8 字节 vs 20 字节。小包场景(游戏、语音,每包几十字节)里,这 12 字节的差距在开销占比上很可观
  3. 不维护连接状态 — 服务器不用为每个客户端记一份状态表。同样扛十万并发,TCP 要维护十万份连接状态,UDP 什么都不用存
  4. 能广播、能组播(收 3.9)— 这是 TCP 根本做不到的
  5. 应用层可以自己定制可靠性 — 把 UDP 当"毛坯房",想要什么自己搭。HTTP/3(QUIC)就是这么干的
❗ 第 4 条最容易被忽略,但它是一道分水岭

TCP 只能单播,UDP 才能广播/组播。

这不是"哪个更好"的问题,而是能力有无的问题:一个 TCP 连接只能连一台机器,"用 TCP 做局域网设备发现"从根上就不成立。

所以 3.9 讲的广播与组播,在传输层找到的唯一合法出口就是 UDP。这也顺手解释了为什么不长的一段话里要反复出现 UDP:DHCP 的 DORA 四步(收 3.12)全是广播,而凡是广播,传输层只可能是 UDP。

六、四个代价(别只看好处)

  1. 丢包、乱序全归应用自己管 — 要么认(音视频),要么自己补(应用层编号、超时、重传)
  2. 没有 MSS 协商,大包会被 IP 分片(收 3.19 + 3.4)— TCP 靠 MSS 在源头切好、让 IP 永不分片;UDP 没有这个机制,应用塞多大就发多大,一旦超过 MTU 就触发 IP 分片,而丢任一片整包报废
  3. 没有拥塞控制 → 会挤占带宽 — 这是 UDP 被运营商限速、被某些网络封禁的原因;也是"用 UDP 打游戏很流畅、用 UDP 传文件却把全家网速拖垮"的根源
  4. 源地址可以随便伪造 → 会被拿来做攻击 — UDP 不用握手,攻击者可以伪造源 IP 发大量请求,让服务器把响应全砸向受害者(放大攻击 / 反射攻击,DNS、NTP、Memcached 都是著名弹药)。这是"无连接"带来的安全副作用
⚠️ 关于第 2 条,一个常见误区

"UDP 一次能发 64KB"——65507 字节只是理论上限(65535 − IP 头 20 − UDP 头 8),而且它只是"IP 包的上限",不是建议值。

超过 MTU(1500)就得分片,丢一片整个数据报报废——而 UDP 不重传,就是彻底没了。

实用建议:单次发送控制在 1400 字节左右(给头留出安全余量),要传大的自己在应用层切。这就是 13.3 那句"实际建议远小于它"的完整原因。

七、选型:什么时候用哪个

场景 选谁 为什么
网页、文件、邮件、数据库 TCP 一个字节都不能少
直播、视频通话、在线游戏 UDP 丢一帧无感,延迟才是命
DNS 查询 UDP(响应大时转 TCP) 一问一答、短小,省掉握手(收 3.17 的 53/UDP)
DHCP UDP 一开始还没有 IP,只能广播(收 3.12)
SNMP、TFTP、日志上报 UDP 短小高频,丢一条无所谓
视频会议、IPTV、设备发现 UDP 必须用组播(收 3.9)
HTTP/3 UDP 应用层自己实现可靠传输(QUIC)

口诀沿用 13.3 的原话:丢得起选 UDP,丢不起选 TCP。 再补一句更实用的判据——看"延迟"和"完整"哪个更要命。

八、动手 Demo34:把 UDP 看个实在

  1. 看本机 UDP 账本 netstat -s -p udp 找"收到的数据报""发出的数据报""接收错误"等项。刷新几次网页再看,数字会涨。

  2. 看本机在守哪些 UDP 端口(信息量很大) netstat -ano -p udp 常见的几条:123(系统时间同步 NTP)、137/138(NetBIOS 名称服务)、1900(SSDP 即插即用设备发现)、5353(mDNS)、5355(LLMNR 名称解析)、3702(WSD 设备发现)——这些全是 UDP。

为什么不用 TCP?因为它们要么需要广播/组播,要么只是简短的一问一答。

顺手验证一件事 你在这份清单里一般看不到 53——因为 DNS 客户端是发起方,用的是临时端口;只有本机自己当 DNS 服务器时才会守 53。这正好复习 3.17 的"服务器守固定号,客户端借临时号"。

  1. 亲眼看一次 DNS 查询(本节最直接的一步) nslookup www.baidu.com 这一问一答默认走 UDP 53。留意它有多快——如果每次 DNS 查询都要先三次握手、再四次挥手,上网体验会明显变慢。这就是 UDP 在真实世界的价值。

  2. 用 Java 13.3 的 Demo24 做正反对照(收 13.3 的账)

把 13.3 的 Demo24Sender 改两行,连续 send 两个包裹: - UDP 这边:接收方改成 while(true) 循环,会收到两个各自完整的数据报——边界清清楚楚 - TCP 那边(3.18 的粘包实验):连写两次,一次 read 可能读到两个拼在一起

同一个动作,两种协议给出来的结果完全不同——这组对照比任何文字都清楚。

  1. 30 秒实验:数一数 UDP 的"一次问答" netstat -s -p udp 记下"发出的数据报"数字 → 执行 nslookup www.qq.com → 再看数字。

大概只涨了个位数(一次查询,或许加一次重试)。对比 3.19 那个实验——访问一个网页要几十上百个 TCP 报文段。开销差距一眼可见。

❓ 自测一下
  1. UDP 一共做几件事?标题里那个"只"字指的是什么?
  2. UDP 首部几个字节?四个字段分别是什么?
  3. UDP 头里有"长度"字段,TCP 头里没有——为什么?
  4. 为什么说 UDP 的"不可靠"是设计选择而不是缺陷?
  5. 同样由程序写三次数据,TCP 和 UDP 交给对方的东西有什么本质不同?
  6. UDP 有哪一件事是 TCP 根本做不到的?
  7. UDP 单包建议控制在多大?为什么不能发满 64KB?
  8. UDP 丢了包,发送方真的一点消息都收不到吗?

答案: ① 一件事:把数据交给正确的应用程序(靠目的端口号)。"只"字就是指它除了分发之外,什么可靠性、排序、流量控制一概不管。 ② 8 字节:源端口、目的端口、长度、校验和(各 16 位)。 ③ 因为 IP 头的总长度包含 IP 头自己,而且分片后每个分片的 IP 头长度只是该分片的长度——算不出原始 UDP 数据报有多长,所以 UDP 必须自带一个长度来界定边界。TCP 是字节流,不需要边界(3.18)。 ④ 因为它面向的是丢掉一小块也不影响使用的数据(音视频、游戏)。这类场景里,重传带来的延迟比丢包本身更伤——"补"是负作用。 ⑤ 边界:TCP 交过去的是一条连续字节流(原来分几次写的分不出来,会粘包);UDP 交过去的是一个一个独立数据报(一次 receive 收到一个完整包裹,边界天然保留)。 ⑥ 广播和组播。TCP 只能单播,一个连接只能连一台机器——所以凡是广播场景(DHCP、设备发现、IPTV),传输层只能是 UDP。 ⑦ 1400 字节左右。65507 只是理论上限(IP 包上限减两个头),超过 MTU 会被 IP 分片,丢一片整个数据报报废,而 UDP 不重传。 ⑧ 不完全是。UDP 自己不发任何差错消息,但 ICMP 会替它报——比如目标端口没人守时会回"端口不可达"(类型 3 / 代码 3,收 3.15)。编程层面通常静默处理,所以你"看不到"。

九、几条实战规矩

1. 用 UDP,就要自己把 TCP 干过的活补上

应用层至少要自己定这几样(每一样都是 TCP 免费提供的):

要补的 做法
消息编号 每个数据报里自己带一个序号字段
超时重传 自己设超时、自己补发(QUIC 就是这么干的)
去重 收到重复序号就丢弃
排序 按自带序号在缓冲区里排好
包大小控制 自己切到 1400 字节以内

想清楚了这几样,才算真的会用 UDP。

2. 分清"卡"是延迟还是丢包

UDP 应用对丢包容忍、对延迟敏感。用户抱怨"卡"时先分清是哪一种:延迟高要优化路径和发包节奏,丢包高才要查链路质量。

3. 局域网设备发现,只能用 UDP 广播/组播

想写一个"自动找到同网段的其他设备"的功能,别用 TCP——一个 TCP 连接只能连一台,你不知道该连谁。用 UDP 广播(255.255.255.255 或定向广播)或者组播。

4. 别在生产环境拿 UDP 传大块数据

除非你自己在应用层做了完整的可靠传输(那就等于重写了半个 TCP)。大文件老老实实用 TCP(收 13.2)。

十、常见误区

  1. ❌ "UDP 是劣质版 TCP" → 面向不同需求的设计选择:一个保完整,一个保实时
  2. ❌ "UDP 一定比 TCP 快" → 不是"传输速度"快,是开销小、延迟低;没有拥塞控制反而可能把线路搞乱、被限速
  3. ❌ "UDP 没有校验" → 有校验和,而且连伪首部都查(能发现"送错机器");只是只查不修
  4. ❌ "UDP 也有连接" → 无连接。只有"对端地址"这个参数,没有连接状态、没有握手
  5. ❌ "广播/组播 TCP 也能做" → 只有 UDP 能做,这是能力的差别不是优劣的差别
  6. ❌ "UDP 和 TCP 一样是字节流" → 数据报有边界,整进整出;一次 receive 正好一个
  7. ❌ "一次能发 64KB" → 理论上限 65507,实用要远小于(建议 ≈1400)
  8. ❌ "UDP 丢包完全没人管" → 应用不管就真没了;但 ICMP 会替你报"端口不可达"这类错(收 3.15)
  9. ❌ "UDP 不用管乱序" → 协议层确实不管,但应用层自己要处理(每个数据报自带序号)
  10. ❌ "UDP 的端口和 TCP 是一回事" → 两套独立编号空间(收 3.17),53/UDP ≠ 53/TCP

十一、下一步学什么

相关笔记