一、传输层做什么

1.1 服务范围

网络层:  主机 ←—————————————→ 主机      (主机到主机的交付)
传输层:  进程 ←—————————————→ 进程      (进程到进程的交付)

传输层建在网络层之上,把「主机到主机」增强为「进程到进程」。

1.2 家庭信使类比(KR 经典)

东海岸的一家有 12 个孩子,西海岸的一家有 12 个孩子,两家的孩子每周互相写信。

应用层报文  =  信纸
进程       =  孩子
主机       =  房子
传输层协议  =  Ann 和 Bill(各家负责收发信的人)
网络层协议  =  邮政服务
  • Ann 把弟妹们的信收齐,装进一个大信封交给邮局 → 多路复用
  • Bill 收到大信封,拆开按收件人分发给各个弟妹 → 多路分解
  • 邮局(网络层)只管把信封从这栋房子送到那栋房子,不认识里面的孩子

📌 这个类比还能解释更多

  • 如果 Ann 记录了哪封信已经送达、没送达的重发 → 这是 TCP
  • 如果 Ann 只是转交,丢了不管 → 这是 UDP
  • 邮局本身不提供任何可靠性 → 这是 IP 的尽最大努力交付

1.3 传输层可以提供、和不可能提供的

能提供 不能提供
可靠数据传输(TCP) 带宽保证
有序交付(TCP) 时延保证
流量控制(TCP)
拥塞控制(TCP)
差错检测(TCP、UDP)

为什么不能提供带宽和时延保证?因为传输层只在端系统上运行,它无法控制网络内部的排队和路由。你不能靠端上的软件凭空创造出网络里不存在的容量。这是端到端原则的另一面:端能做的事有限,端做不到的事,网络不给,就没有。


二、多路复用与多路分解

多路复用(multiplexing):发送端从多个 socket 收集数据,加上头部,交给网络层。 多路分解(demultiplexing):接收端根据头部信息,把报文段交给正确的 socket。

关键问题是:接收端根据什么来分?

2.1 通用头部字段

每个传输层报文段都有:

┌────────────────┬────────────────┐
│   源端口号 (16)  │  目的端口号 (16) │
├────────────────┴────────────────┤
│          其他头部字段              │
├─────────────────────────────────┤
│          应用数据                  │
└─────────────────────────────────┘

2.2 无连接的多路分解(UDP)⭐

UDP socket 由二元组标识:

(目的 IP 地址, 目的端口号)

规则:两个源地址完全不同的数据报,只要目的 IP 和目的端口相同,就会被交给同一个 socket。

主机 A (10.0.0.1:9157) ──┐
                          ├──→ 服务器 B (198.51.100.2:6428)
主机 C (10.0.0.5:5775) ──┘        ↓
                              同一个 UDP socket

📌 源端口号在 UDP 里的用途:它是回信地址。服务器要回复时,把它当作目的端口。

2.3 面向连接的多路分解(TCP)⭐

TCP socket 由四元组标识:

(源 IP, 源端口, 目的 IP, 目的端口)

四个值中任何一个不同,就是不同的 socket。

主机 A (10.0.0.1:9157) ──→ 服务器 B:80  → socket #1
主机 A (10.0.0.1:9158) ──→ 服务器 B:80  → socket #2  ← 源端口不同
主机 C (10.0.0.5:9157) ──→ 服务器 B:80  → socket #3  ← 源 IP 不同

这正是第 9 讲例题里的结论。Web 服务器能在一个 80 端口上服务几十万连接,靠的就是这个四元组。

2.4 对比表(考试必背)

UDP TCP
socket 标识 二元组(目的IP, 目的端口) 四元组
一个 socket 能服务多少客户 任意多个 恰好一个连接
服务器需要几个 socket 1 个 1 欢迎 + N 连接

⚠️ 常见错误:以为 UDP 也用四元组。不是的——UDP 是无连接的,内核里没有「连接」这个概念可供匹配。(例外:应用调用了 connect() 的 UDP socket,会被内核过滤源地址,但那是应用层的便利功能,不改变协议本身。)


三、UDP

3.1 UDP 提供什么

几乎什么都不提供。它在 IP 之上只加了两样东西:

  1. 多路复用/分解(端口号)
  2. 差错检测(校验和)

仅此而已。KR 教材的说法很准确:UDP 是「除了复用/分解和轻量差错检测之外,什么都不加」的传输协议。

3.2 为什么还要有 UDP

四个理由,要能说全:

1️⃣ 对何时发什么数据有精确的应用级控制

TCP 会因为拥塞控制而限速、因为可靠传输而重传。对实时应用来说,迟到的数据等于无用的数据——宁可丢掉。

2️⃣ 无需建立连接

TCP 要三次握手(1 个 RTT)。DNS 用 UDP,正是因为一次查询本来就只有一来一回,为它做握手是纯粹的浪费。

3️⃣ 无连接状态

TCP 要维护接收/发送缓冲区、拥塞参数、序号、确认号。一台服务器用 UDP 能支持的活跃客户端数量,远多于用 TCP。

4️⃣ 头部开销小

UDP 头部 8 字节,TCP 头部 20 字节(还常有选项)。对小报文,这个差异不可忽略。

3.3 UDP 报文段结构

 0                   16                  31
┌───────────────────┬───────────────────┐
│      源端口号       │     目的端口号      │  4 字节
├───────────────────┼───────────────────┤
│    长度(字节)      │      校验和        │  4 字节
├───────────────────┴───────────────────┤
│                                        │
│              应用数据                    │
│                                        │
└────────────────────────────────────────┘
  • 长度头部 + 数据的总字节数(不是只有数据!)。最小值 8(无数据)
  • 校验和:见下节。IPv4 中可选(填 0 表示不校验),IPv6 中强制

3.4 用 UDP 的应用

应用 为什么用 UDP
DNS 一问一答,握手是浪费;丢了重问就行
DHCP 还没有 IP 地址,谈不上建立连接
SNMP 网络已经出问题时还要能工作,越简单越好
RTP / WebRTC 实时音视频,宁可丢帧不可卡顿
QUIC / HTTP3 在用户态自己实现可靠性与拥塞控制(第 6 讲)
游戏 迟到的位置信息不如丢弃
NTP 时间同步,重传会引入误差

⚠️ 一个重要警告UDP 没有拥塞控制。如果所有人都用不受控的 UDP 高速发送,网络会进入拥塞崩溃(congestion collapse,第 15 讲)。因此负责任的 UDP 应用必须自己实现某种速率控制——QUIC 做了,WebRTC 也做了(Google Congestion Control)。


四、UDP 校验和

4.1 目标

检测报文段在传输中是否发生了比特翻转

来源可能是:链路噪声、路由器内存错误、软件 bug。

⚠️ 注意端到端原则又出现了:链路层已经有 CRC,为什么传输层还要校验?因为链路层的 CRC 只覆盖一段链路,而分组在路由器内存中停留时发生的错误,没有任何链路层检查能覆盖。只有端到端的校验能覆盖全程。

4.2 计算方法

发送端

  1. 把报文段(含伪首部)看成一串 16 位整数
  2. 求和(进位要回卷到最低位,这叫「反码和」)
  3. 结果取反,放进校验和字段

接收端

  1. 把所有 16 位字(包括校验和字段)加起来
  2. 如果结果全是 1(0xFFFF),说明没检出错误;否则有错

4.3 手工计算示例

计算两个 16 位字的校验和:

第一个字:   0110011001100000
第二个字:   0101010101010101
           ─────────────────
和:         1011101110110101      ← 无进位

假设再加一个字: 1000111100001100
   1011101110110101
 + 1000111100001100
 ─────────────────
  10100101011000001              ← ⚠️ 产生了 17 位,最高位溢出

回卷(wraparound):把溢出的 1 加到最低位
   0100101011000001
 +                1
 ─────────────────
   0100101011000010              ← 反码和

取反得校验和:
   1011010100111101              ← 放进校验和字段

验证(接收端):

   0100101011000010     (数据的反码和)
 + 1011010100111101     (校验和)
 ─────────────────
   1111111111111111     ← 全 1 ✅ 无错误

4.4 它检得出什么、检不出什么

检得出:任何单个比特的翻转,绝大多数随机差错。

检不出

  • 偶数个比特同时翻转,且互相抵消。例如两个字中同一位置分别 0→1 和 1→0,和不变
  • 因此漏检概率约为 2⁻¹⁶ ≈ 1/65536

📌 为什么不用更强的 CRC?因为 UDP/TCP 校验和是在软件里、对每个字节计算的,必须极快。CRC 更强但更慢,被放在有硬件加速的链路层。这是一个明确的性能与强度权衡。

⚠️ 现实警告:在高速链路上,2⁻¹⁶ 的漏检率意味着每传输几十 GB 就可能有一个未被检出的错误。对数据完整性要求高的应用(文件同步、数据库复制),必须自己再做一层强校验(SHA-256)——这又是端到端原则。

4.5 伪首部

TCP/UDP 校验和实际上还覆盖了一个伪首部(pseudo-header),包含:

源 IP 地址 (32)  |  目的 IP 地址 (32)  |  0 (8)  |  协议号 (8)  |  长度 (16)

**为什么?**为了检测「报文段被送错了主机」这类错误——如果 IP 地址在传输中损坏,校验和会失败。

⚠️ 但这带来了一个分层污染传输层的校验和依赖了网络层的地址。这是分层原则的一个著名破例,也正是 NAT 必须重算 TCP/UDP 校验和(而不能只改 IP 头)的原因(第 18 讲)。


五、例题(Worked Example)

题目:某 Web 服务器 IP 为 198.51.100.7,监听 TCP 80 端口和 UDP 53 端口(同时跑 DNS)。以下四个报文段先后到达:

① TCP, 源 (203.0.113.5, 40001), 目的 (198.51.100.7, 80)
② TCP, 源 (203.0.113.5, 40002), 目的 (198.51.100.7, 80)
③ UDP, 源 (203.0.113.9, 33333), 目的 (198.51.100.7, 53)
④ UDP, 源 (198.18.0.4, 44444),  目的 (198.51.100.7, 53)

(a) 这些报文段分别被交给几个不同的 socket? (b) 若 ① 和 ② 来自同一台机器,服务器还能区分它们吗? (c) 若服务器的 DNS 服务想知道该把响应发回给谁,靠什么?

解答

(a) 三个 socket

① → TCP socket A,四元组 (203.0.113.5, 40001, 198.51.100.7, 80)
② → TCP socket B,四元组 (203.0.113.5, 40002, 198.51.100.7, 80)
③ ④ → 同一个 UDP socket,二元组 (198.51.100.7, 53)

关键点:③ 和 ④ 源地址完全不同,但因为 UDP 只看目的二元组,它们共享同一个 socket

(b) 能。虽然源 IP 相同,但源端口不同(40001 vs 40002),四元组因而不同。这正是浏览器能对同一站点并行开多条连接的原因(第 6 讲)。

(c) 靠 recvfrom() 返回的源地址与源端口(也就是 UDP 头部里的源端口字段 + IP 头部的源 IP)。应用需要自己保存这个地址,在发送响应时显式指定——这就是第 9 讲 UDP 服务器代码里 sendto(response, client_address) 的由来。

📌 一个推论UDP 服务器的「会话状态」必须由应用自己维护,因为传输层不帮你记住任何东西。这正是 QUIC 要在 UDP 之上重建连接概念的原因。


六、随堂自测

  1. 传输层把网络层的什么服务增强成了什么服务?
  2. 为什么传输层不可能提供带宽和时延保证?
  3. UDP socket 用几元组标识?TCP 呢?给出理由而不是死记。
  4. 一台 UDP 服务器同时被 1000 个客户端访问,需要几个 socket?TCP 呢?
  5. UDP 长度字段包含头部吗?最小值是多少?
  6. 计算下面两个 16 位字的反码和并给出校验和:11110000111100001111000011110001
  7. UDP 校验和检不出什么样的错误?漏检概率量级是多少?
  8. 为什么说伪首部破坏了分层?它导致了什么实际后果?

七、本讲要点回顾

  • 传输层把网络层的主机到主机增强为进程到进程
  • UDP 用二元组分解,TCP 用四元组分解。
  • UDP 只提供两样东西:多路复用/分解 + 差错检测
  • 用 UDP 的四个理由:应用级控制、无握手、无连接状态、头部小
  • UDP 头部 8 字节:源端口、目的端口、长度、校验和。
  • 反码校验和:求和(进位回卷)→ 取反;接收端验证为全 1
  • 检不出偶数个互相抵消的比特翻转,漏检率约 2⁻¹⁶。
  • UDP 没有拥塞控制,用它的应用必须自己负责,否则会导致拥塞崩溃。

八、自测答案

1. 把网络层的主机到主机的尽最大努力交付,增强为进程到进程的交付(UDP),并可进一步增强为可靠、有序、有流量控制与拥塞控制的字节流(TCP)。

2. 因为传输层协议只在端系统上运行,它对网络内部的排队、路由和其他流的行为没有任何控制权。带宽和时延由路径上的链路容量与竞争流量决定,端上的软件无法凭空改变它们。要提供这类保证,必须由网络内部(资源预留、准入控制)来实现。

3. UDP 用二元组(目的IP, 目的端口),因为 UDP 是无连接的——内核不维护任何「这是哪条连接」的状态,只能按目的地分发。TCP 用四元组,因为 TCP 需要为每条连接维护独立的序号、窗口和缓冲区状态,必须能把不同连接区分开。

4. UDP:1 个。TCP:1001 个(1 个欢迎套接字 + 1000 个连接套接字)。

5. **包含。**UDP 长度字段是「头部 + 数据」的总字节数,最小值为 8(只有头部、没有数据时)。

6.

  1111000011110000
+ 1111000011110001
 ──────────────────
 11110000111100001    ← 17 位,有溢出
回卷:1110000111100001 + 1 = 1110000111100010
取反(校验和):0001111000011101

验证:1110000111100010 + 0001111000011101 = 1111111111111111

7. 检不出偶数个比特翻转且效果互相抵消的情况——例如两个不同的 16 位字在同一位置分别发生 0→1 和 1→0,总和不变。漏检概率量级约为 2⁻¹⁶ ≈ 1.5×10⁻⁵

8. 伪首部让传输层的校验和覆盖了网络层的 IP 地址,即传输层的正确性依赖于下层的具体字段,违反了「每层只处理自己的头部」的分层原则。实际后果:任何改写 IP 地址或端口的设备(NAT、负载均衡器)都必须重新计算 TCP/UDP 校验和,否则报文会被接收端丢弃。这显著增加了 NAT 的实现复杂度,也是 NAT 无法对某些加密协议(如 IPsec AH)透明工作的原因之一。