Appearance
第三章:传输层
传输层位于应用层和网络层之间。网络层提供主机到主机的通信,而传输层把这个能力扩展为 进程到进程的逻辑通信。本章围绕四条主线展开:多路复用/多路分解、可靠数据传输、流量控制、拥塞控制,并讨论互联网中的 UDP、TCP 和 QUIC。
目录
- 3.1 传输层服务
- 3.2 多路复用和多路分解
- 3.3 无连接传输:UDP
- 3.4 可靠数据传输原则
- 3.5 面向连接的传输:TCP
- 3.6 拥塞控制原则
- 3.7 TCP 拥塞控制
- 3.8 传输层功能的演变
- 3.9 本章小结
3.1 传输层服务
传输层提供的是运行在不同主机上的应用进程之间的逻辑通信。应用进程不需要直接处理底层网络细节,只需要通过套接字把数据交给传输层。
| 层次 | 通信对象 | 典型单位 | 核心问题 |
|---|---|---|---|
| 网络层 | 主机到主机 | IP 数据报 | 如何跨网络到达目的主机 |
| 传输层 | 进程到进程 | 报文段 segment | 如何把数据交给正确进程,并提供可靠性、流量控制、拥塞控制等能力 |
发送端和接收端动作
发送端:
- 从应用层接收消息。
- 选择并填写传输层首部字段。
- 形成传输层报文段。
- 把报文段交给 IP 层封装和发送。
接收端:
- 从 IP 层接收报文段。
- 检查传输层首部字段。
- 提取应用层消息。
- 通过套接字把消息交给正确应用进程。
TCP 与 UDP
互联网主要提供两种传输层协议:
| 协议 | 服务 | 主要特性 |
|---|---|---|
| TCP | 可靠、有序、面向连接的字节流 | 可靠传输、流量控制、拥塞控制、连接管理 |
| UDP | 无连接、不可靠、无序的数据报服务 | 开销小、无握手、无拥塞控制、应用可自定义可靠性 |
传输层不直接提供两类保证:
- 延迟保证:不能保证某个报文段一定在指定时间内到达。
- 带宽保证:不能保证应用一定获得某个最小速率。
一个直观类比:网络层像邮政系统,把信从一栋房子送到另一栋房子;传输层像房子里的负责人,把信分发给正确的孩子。主机是房屋,进程是孩子,应用消息是信件。
3.2 多路复用和多路分解
多路复用和多路分解解决的是:同一台主机上有很多应用进程同时通信,传输层如何把数据从多个进程发出去,又如何把收到的数据交回正确进程。
| 方向 | 名称 | 作用 |
|---|---|---|
| 发送端 | 多路复用 multiplexing | 从多个套接字收集数据,加上传输层首部后交给网络层 |
| 接收端 | 多路分解 demultiplexing | 根据首部字段,把收到的报文段交给正确套接字 |
传输层依靠端口号完成分发。TCP/UDP 首部中都有源端口号和目的端口号。
UDP 的无连接多路分解
UDP 套接字主要由目的 IP 和目的端口标识。接收主机收到 UDP 报文段后,只看目的端口号,把数据交给绑定该端口的套接字。
因此,不同源 IP、不同源端口发来的 UDP 报文,只要目的 IP 和目的端口相同,就会进入同一个 UDP 套接字。
text
UDP demux key = (destination IP, destination port)TCP 的面向连接多路分解
TCP 连接由四元组标识:
text
(source IP, source port, destination IP, destination port)Web 服务器可以让很多客户端同时连接自己的 80 或 443 端口。虽然目的 IP 和目的端口相同,但源 IP 或源端口不同,因此系统可以把不同连接分发给不同 TCP 套接字。
| 协议 | 多路分解依据 | 结果 |
|---|---|---|
| UDP | 目的 IP、目的端口 | 多个发送方可进入同一个 socket |
| TCP | 源 IP、源端口、目的 IP、目的端口 | 每条连接对应一个 socket |
3.3 无连接传输:UDP
UDP (User Datagram Protocol) 是对 IP 的极薄封装。它提供端口号和校验和,但不提供连接建立、可靠传输、流量控制和拥塞控制。
UDP 的特点
| 特点 | 含义 |
|---|---|
| 无连接 | 发送前不握手,每个 UDP 报文段独立处理 |
| 不可靠 | 报文段可能丢失 |
| 无序 | 报文段可能乱序到达 |
| 首部小 | UDP 首部只有 8 字节 |
| 无拥塞控制 | 应用可以按自己的节奏发送,但也可能伤害网络公平性 |
UDP 适合对时延敏感、能容忍少量丢包,或者希望在应用层自行实现可靠性的场景。
常见用途:
- DNS 查询。
- SNMP。
- 实时音视频。
- QUIC / HTTP/3。
- 应用层自定义可靠传输和拥塞控制。
UDP 报文段格式
| 字段 | 长度 | 作用 |
|---|---|---|
| source port | 16 bit | 源端口号 |
| destination port | 16 bit | 目的端口号 |
| length | 16 bit | UDP 首部和数据总长度 |
| checksum | 16 bit | 检测首部和数据中的比特错误 |
UDP 校验和
UDP 校验和用于检测报文段传输中是否发生比特翻转。
发送方:
- 把 UDP 首部、UDP 数据和 IP 伪首部视为 16 bit 整数序列。
- 使用反码求和,把溢出的进位回绕加到低位。
- 对最终结果取反,放入 checksum 字段。
接收方重新计算:
| 检查结果 | 含义 |
|---|---|
| 不匹配 | 检测到错误 |
| 匹配 | 未检测到错误,但仍可能存在漏检 |
UDP 校验和能力弱于 CRC,但足够轻量,适合传输层端到端错误检测。
3.4 可靠数据传输原则
可靠数据传输 (reliable data transfer, rdt) 的目标是在不可靠信道上向上层提供“可靠、有序、不重复”的数据交付抽象。它是 TCP 可靠性的理论基础:TCP 的细节更复杂,但核心仍然是校验、序号、确认、定时器、重传和窗口。
接口可以抽象为:
| 函数 | 方向 | 含义 |
|---|---|---|
rdt_send(data) | 上层调用发送方 | 应用要求可靠发送数据 |
udt_send(packet) | rdt 调用下层 | 通过不可靠信道发送分组 |
rdt_rcv(packet) | 下层通知 rdt | 有分组到达 |
deliver_data(data) | rdt 交付上层 | 可靠地把数据交给应用 |
可以把 rdt 看作夹在应用层和不可靠信道之间的一层状态机:
text
application
|
rdt_send(data)
|
reliable data transfer protocol
|
udt_send(packet)
|
unreliable channel接收方向上交付数据时,必须保证三件事:
- 不错:损坏的数据不能交付。
- 不漏:丢失的数据最终要补回来。
- 不重:重传导致的重复分组不能重复交付。
rdt 协议的工具箱
| 机制 | 解决的问题 | 基本思想 |
|---|---|---|
| checksum | 比特错误 | 接收方检测分组是否损坏 |
| ACK | 正向确认 | 接收方告诉发送方“这个分组收到了” |
| NAK | 反向确认 | 接收方告诉发送方“这个分组坏了,请重发” |
| 序列号 | 重复分组 | 接收方区分新分组和重传旧分组 |
| 定时器 | 丢包 | 超时未收到确认时重传 |
| 窗口 | 低利用率 | 允许多个分组同时在途 |
| 缓冲区 | 乱序到达 | 保存未能立即按序交付的分组 |
rdt 的演进就是逐步放松底层信道假设,并为每个新问题加入一个机制。
rdt 1.0:完全可靠信道
rdt 1.0 假设底层信道不会出错、不会丢包、不会乱序。发送方只需要把数据打包后交给下层;接收方收到后直接交给上层。
| 角色 | 行为 |
|---|---|
| 发送方 | rdt_send(data) 后直接 udt_send(packet) |
| 接收方 | rdt_rcv(packet) 后直接 deliver_data(data) |
它没有 ACK、没有重传、没有序列号。这个版本本身不实用,但作为起点能说明:可靠性复杂度完全来自底层信道的不可靠。
rdt 2.0:信道可能有比特错误
rdt 2.0 假设分组可能被损坏,但不会丢失。为此需要三个机制:
| 机制 | 用途 |
|---|---|
| checksum | 接收方判断数据分组是否损坏 |
| ACK | 接收方确认正确收到 |
| NAK | 接收方要求发送方重传 |
发送方逻辑:
- 从上层收到数据,生成带 checksum 的分组并发送。
- 等待接收方反馈。
- 收到 ACK,说明本分组成功,准备发送下一个。
- 收到 NAK,说明分组损坏,重传当前分组。
接收方逻辑:
- 收到分组后检查 checksum。
- 若分组未损坏,交付数据并发送 ACK。
- 若分组损坏,丢弃数据并发送 NAK。
rdt 2.0 是 停止-等待协议:发送方每次只能有一个未确认分组在途。
rdt 2.0 的致命问题是:它默认 ACK/NAK 不会损坏。现实中控制报文也可能出错。如果发送方收到一个损坏的 ACK/NAK,它不知道接收方到底收到了还是没收到。直接重传可能导致重复交付;直接发送下一个又可能导致丢数据。
rdt 2.1:ACK/NAK 也可能损坏
rdt 2.1 引入 序列号。由于仍是停止-等待协议,发送方最多只有一个分组在途,所以只需要 1 bit 序列号:0 和 1 交替使用。
发送方维护两个主要状态:
| 状态 | 含义 |
|---|---|
| 等待上层数据,准备发送 seq=0 | 下一个新分组使用序号 0 |
| 等待 ACK/NAK for seq=0 | seq=0 分组已发出,等待反馈 |
| 等待上层数据,准备发送 seq=1 | 下一个新分组使用序号 1 |
| 等待 ACK/NAK for seq=1 | seq=1 分组已发出,等待反馈 |
接收方也维护期望序号:
| 收到的分组 | 接收方动作 |
|---|---|
| 未损坏,序号等于期望序号 | 交付数据,发送 ACK,切换期望序号 |
| 未损坏,但序号不是期望序号 | 说明是重复分组,不交付,重新 ACK 上次正确分组 |
| 损坏 | 发送 NAK |
序列号解决了重复分组问题。即使 ACK 损坏导致发送方重传,接收方也能通过序号发现这是旧分组,从而不重复交付。
rdt 2.2:无 NAK 协议
rdt 2.2 去掉 NAK,只使用 ACK。它的思想是:如果接收方收到损坏分组,就重新发送“最后一个正确收到分组”的 ACK。这个重复 ACK 对发送方来说等价于 NAK。
例如接收方期望 seq=1,但收到损坏分组,或者收到重复的 seq=0,则发送 ACK(0)。发送方如果正在等待 ACK(1),却收到 ACK(0),就知道 seq=1 没有被正确接收,于是重传 seq=1。
这很重要,因为 TCP 也采用“ACK-only”的思想:TCP 没有显式 NAK,而是通过重复 ACK 暗示缺失数据。
rdt 3.0:信道可能丢包
rdt 3.0 进一步假设:数据分组和 ACK 都可能丢失。仅靠 ACK 不够,因为如果分组丢了,接收方根本不会回 ACK,发送方会永远等待。
新机制是 定时器:
- 发送方发送分组后启动定时器。
- 如果定时器到期前收到正确 ACK,停止定时器,发送下一个分组。
- 如果超时还没收到 ACK,发送方假定分组或 ACK 丢失,重传当前分组。
超时重传可能产生重复分组。例如原分组没有丢,只是 ACK 回来得慢,发送方过早超时并重传。序列号仍然负责让接收方识别重复分组。
rdt 3.0 的典型场景:
| 场景 | 结果 | 如何处理 |
|---|---|---|
| 数据分组丢失 | 接收方无响应 | 发送方超时重传 |
| ACK 丢失 | 发送方不知道接收方已收到 | 发送方超时重传,接收方用序号丢弃重复数据 |
| ACK 延迟 | 可能触发不必要重传 | 序号防止重复交付 |
| 数据损坏 | 接收方不交付 | 接收方发送重复 ACK 或等价反馈,发送方重传 |
到 rdt 3.0 为止,可靠性已经成立:出错、丢包、重复都能处理。但它仍然一次只发一个分组,性能很差。
停止-等待协议的本质
rdt 2.x 和 rdt 3.0 都是停止-等待协议。其不变量是:
text
发送方最多只有 1 个未确认分组在网络中优点是状态简单,1 bit 序列号就够;缺点是 RTT 大时链路利用率极低。
rdt 3.0 的性能问题
停止-等待协议的发送方利用率近似为:
其中
课件中的例子得到:
这说明可靠性机制正确还不够,协议还必须能有效利用链路容量。
流水线协议
流水线协议放松停止-等待限制,允许发送方同时有多个“在途”但尚未确认的分组。
text
停止-等待:send pkt0 -> wait ACK0 -> send pkt1 -> wait ACK1
流水线: send pkt0, pkt1, pkt2, ... -> ACKs come back later它需要:
- 更大的序列号空间。
- 发送方缓冲未确认分组。
- 接收方可能需要缓冲乱序分组。
- 窗口机制控制最多有多少分组在途。
流水线协议的两个经典实现是 Go-Back-N 和 Selective Repeat。
| 协议 | 接收方是否缓存乱序分组 | ACK 方式 | 重传方式 |
|---|---|---|---|
| Go-Back-N | 通常不缓存 | 累积 ACK | 丢失点及其之后全部重传 |
| Selective Repeat | 缓存 | 单独 ACK | 只重传未确认分组 |
Go-Back-N
Go-Back-N (GBN) 的发送方允许最多
发送方:
- 维护大小为
的发送窗口。 - ACK 是累积确认,
ACK(n)表示序号的分组都已正确收到。 - 只为最老的未确认分组设置定时器。
- 若分组
超时,则重传 及其之后窗口内所有分组。
接收方:
- 只按序接收。
- 收到乱序分组时通常丢弃。
- 对已按序收到的最高序号发送重复 ACK。
- 只需记住
rcv_base或expectedseqnum。
GBN 的窗口可以理解成三个区域:
text
[ 已确认 ][ 已发送未确认 ][ 可发送但未发送 ][ 暂不可用 ]
base nextseqnum base+N例子:窗口大小
GBN 的接收方简单,但代价是:一个分组丢失会导致它后面许多已经到达过的分组被浪费。
Selective Repeat
Selective Repeat (SR) 只重传真正丢失或出错的分组。
发送方:
- 对每个未确认分组单独维护定时器。
- 收到某个分组的 ACK 后只标记该分组已确认。
- 窗口基址推进到最小未确认序号。
接收方:
- 对每个正确收到的分组单独 ACK。
- 可以缓存乱序分组。
- 当缺口补齐后,把连续有序数据交给上层。
SR 的窗口推进更细:
| 事件 | 发送方行为 | 接收方行为 |
|---|---|---|
| 上层给数据,窗口有空位 | 发送下一个序号分组,启动该分组计时器 | 无 |
| 收到 ACK(n) | 标记 n 已确认;若 n 是窗口基址,滑动到下一个未确认分组 | 无 |
| 分组 n 超时 | 只重传 n | 无 |
| 收到窗口内未损坏分组 n | 无 | 发送 ACK(n),若乱序则缓存 |
| 收到窗口左侧旧分组 n | 无 | 重新发送 ACK(n),避免发送方误以为 ACK 丢失 |
| 收到窗口外无关分组 | 无 | 忽略 |
SR 的效率更高,但接收方和发送方状态更复杂。尤其要注意循环序列号导致的旧包/新包混淆。
如果序列号只有 0,1,2,3,窗口大小却是 3,那么接收方在某些情况下无法判断一个新到达的 0 是“新一轮的 0”,还是“上一轮超时重传的旧 0”。为了避免这个歧义,通常要求:
若发送窗口和接收窗口大小相等,则常写作:
其中
小结:rdt 机制如何对应到 TCP
| rdt 概念 | TCP 中的体现 |
|---|---|
| checksum | TCP 首部校验和 |
| 序列号 | TCP 字节序列号 |
| ACK | TCP 累积确认 |
| 定时器 | TCP 超时重传 |
| 重复 ACK | TCP 快速重传信号 |
| 流水线 | TCP 发送窗口 |
| 接收缓存 | TCP 接收缓冲区和乱序缓存 |
| 窗口大小 | 同时受流量控制 rwnd 和拥塞控制 cwnd 限制 |
3.5 面向连接的传输:TCP
TCP 是面向连接的、可靠的、有序的字节流协议。它的服务不是“消息流”,而是 字节流:应用写入的边界不会天然保留。
TCP 概述
| 特性 | 含义 |
|---|---|
| 点对点 | 一个发送方,一个接收方 |
| 可靠有序 | 字节按序、无缺失、无重复地交给应用 |
| 全双工 | 同一连接中两个方向都可同时传输 |
| 面向连接 | 数据交换前通过握手初始化状态 |
| 流水线 | 允许多个未确认字节在途 |
| 累积 ACK | ACK 表示期望收到的下一个字节 |
| 流量控制 | 发送方不能淹没接收方缓冲区 |
| 拥塞控制 | 发送方根据网络拥塞调整速率 |
TCP 段结构
TCP 首部包含:
| 字段 | 作用 |
|---|---|
| source / destination port | 标识发送和接收进程 |
| sequence number | 本段数据第一个字节的编号 |
| acknowledgement number | 期望从对方收到的下一个字节 |
| header length | TCP 首部长度 |
| flags | ACK、RST、SYN、FIN、CWR、ECE、URG、PSH 等控制位 |
| receive window | 接收方愿意接收的字节数,用于流量控制 |
| checksum | 互联网校验和 |
| urgent pointer | 紧急数据指针 |
| options | MSS、窗口扩大、时间戳、SACK 等可选项 |
| data | 应用数据 |
TCP 的序列号按 字节 编号,不按段编号。
序列号与确认号
序列号是本段数据中第一个字节在字节流中的编号。ACK 号是接收方期望收到的下一个字节编号,因此 TCP ACK 是累积确认。
例如:
text
A -> B: Seq=42, ACK=79, data='C'
B -> A: Seq=79, ACK=43, data='C'
A -> B: Seq=43, ACK=80这表示 A 发出的字节 42 已被 B 收到,B 下一步期望字节 43;B 回显的字节 79 又被 A 确认,A 下一步期望字节 80。
TCP 标准没有强制规定接收方如何处理乱序段,具体实现可以选择缓存或丢弃,但实际 TCP 通常会缓存并配合 SACK 等机制。
RTT 估计与超时
TCP 超时值必须大于 RTT,但 RTT 会动态变化:
- 超时太短:会过早重传。
- 超时太长:对丢包反应太慢。
TCP 用 SampleRTT 测量某个段从发送到 ACK 返回的时间,通常忽略重传样本。EstimatedRTT 使用指数加权移动平均:
典型取值:
RTT 偏差也用 EWMA 估计:
典型取值:
超时间隔设置为:
TCP 发送方简化逻辑
收到应用数据:
- 创建 TCP 段,序列号为该段第一个字节编号。
- 把段交给 IP。
- 若当前没有计时器运行,启动计时器。
收到 ACK:
- 如果 ACK 确认了新的数据,更新
SendBase。 - 如果仍有未确认段,重新启动计时器。
- 如果没有未确认段,停止计时器。
超时:
- 重传最小序列号的未确认段。
- 重启计时器。
TCP 接收方 ACK 生成
| 接收事件 | TCP 接收方行为 |
|---|---|
| 按序段到达,且之前数据都已确认 | 延迟 ACK,最多等待约 500 ms |
| 按序段到达,且已有一个 ACK 等待发送 | 立即发送累积 ACK |
| 乱序段到达,检测到缺口 | 立即发送重复 ACK,指明下一个期望字节 |
| 到达段部分或完全填补缺口 | 若从缺口低端开始,立即发送 ACK |
重复 ACK 是快速重传的重要信号。
快速重传
如果发送方收到同一数据的 3 个重复 ACK,说明某个段之后的多个段已经到达,但那个段缺失。发送方不必等超时,而是立即重传最小未确认序列号的段。
text
3 duplicate ACKs -> fast retransmit missing segment快速重传可以显著缩短丢包恢复时间。
TCP 流量控制
流量控制解决的是:发送方不要把接收方缓冲区塞爆。它不同于拥塞控制,拥塞控制关注网络内部是否过载。
接收方在 TCP 首部的 receive window 字段中通告可用缓冲区空间,记为 rwnd。发送方限制未确认的在途数据量:
这样即使应用程序从接收缓冲区读取数据较慢,发送方也不会无限制地继续注入数据。
TCP 连接管理
连接建立前,双方需要:
- 确认彼此愿意建立连接。
- 协商和初始化连接状态,例如初始序列号。
- 避免旧的重复连接请求造成半打开连接或重复数据问题。
两次握手在存在延迟、丢失、重传和乱序的网络中不可靠,可能产生半打开连接或错误接收旧数据。因此 TCP 使用三次握手。
三次握手:
- 客户端选择初始序列号
,发送 SYN=1, Seq=x。 - 服务器选择初始序列号
,回复 SYN=1, Seq=y, ACK=x+1。 - 客户端回复
ACK=y+1,连接进入 established 状态。
连接关闭:
- 每个方向独立关闭。
- 一方发送
FIN=1表示自己不再发送。 - 对方 ACK 该 FIN。
- 对方也关闭时再发送自己的 FIN。
- 同时 FIN 交换也可以被正确处理。
3.6 拥塞控制原则
拥塞是指网络中太多发送方以太快速度注入太多数据,超过网络处理能力。表现为:
- 路由器队列变长,时延增加。
- 缓冲区溢出,分组丢失。
- 重传增加,进一步加重负载。
拥塞控制和流量控制不同:
| 机制 | 控制对象 | 问题 |
|---|---|---|
| 流量控制 | 一个发送方和一个接收方 | 发送方太快,接收方缓冲区吃不消 |
| 拥塞控制 | 多个发送方和网络内部 | 总注入速率太高,路由器和链路吃不消 |
拥塞的代价
课件用几个场景说明拥塞的代价:
- 无限缓冲单路由器:吞吐量有上限,随着输入速率接近容量,排队时延急剧增加。
- 有限缓冲和重传:丢包导致重传,重传消耗链路容量,有效吞吐下降。
- 过早超时:原分组和重传副本都可能成功到达,不必要副本浪费容量。
- 多跳路径:某个下游链路丢包时,已经在上游链路和缓冲区消耗的资源全部浪费。
拥塞带来的核心结论:
- 吞吐量不能超过链路容量。
- 接近容量时,时延会快速增加。
- 丢失和重传会降低有效吞吐量。
- 不必要的重传副本进一步浪费容量。
- 在多跳路径中,下游丢包会浪费上游已经投入的传输资源。
两类拥塞控制方法
| 方法 | 思想 | 例子 |
|---|---|---|
| 端到端拥塞控制 | 网络不显式反馈,发送方根据丢包、延迟推断拥塞 | 传统 TCP |
| 网络辅助拥塞控制 | 路由器向端系统显式反馈拥塞信息或速率建议 | ECN、ATM、DECbit |
TCP 主要采用端到端方法,同时现代网络也常部署 ECN 这样的网络辅助机制。
3.7 TCP 拥塞控制
TCP 拥塞控制的核心变量是拥塞窗口 cwnd。发送方同时受到 cwnd 和接收窗口 rwnd 限制。
简化地说:
TCP 发送速率近似为:
cwnd 根据网络反馈动态调整。
AIMD
经典 TCP 使用 AIMD:
| 阶段 | 行为 |
|---|---|
| Additive Increase | 每个 RTT 大约增加 1 MSS |
| Multiplicative Decrease | 检测到丢包时把发送速率减半 |
这种机制形成锯齿状速率曲线:不断探测可用带宽,发现拥塞后降低速率。
丢包信号的处理:
- 三次重复 ACK:TCP Reno 通常把窗口减半。
- 超时:TCP Tahoe 把窗口降到 1 MSS。
慢启动
连接刚开始时,TCP 不知道可用带宽,因此从较小窗口开始:
- 初始
cwnd = 1 MSS。 - 每收到一个 ACK,
cwnd增加 1 MSS。 - 因此每个 RTT 大约翻倍,呈指数增长。
慢启动名字叫“慢”,但增长速度其实很快。它只是相对直接发送大量数据而言从小窗口起步。
从慢启动到拥塞避免
TCP 使用阈值 ssthresh 决定何时从指数增长切换到线性增长。
规则:
- 当
cwnd < ssthresh:慢启动,指数增长。 - 当
cwnd >= ssthresh:拥塞避免,线性增长。 - 发生丢包时:
ssthresh = cwnd / 2。
典型状态包括:
| 状态 | 行为 |
|---|---|
| slow start | ACK 到来时快速增加 cwnd |
| congestion avoidance | 每 RTT 约增加 1 MSS |
| fast recovery | 三次重复 ACK 后快速重传并部分恢复窗口 |
TCP CUBIC
TCP CUBIC 是 Linux 默认并广泛部署的拥塞控制算法。它尝试比经典 AIMD 更高效地探测可用带宽。
直觉:
- 记录发生丢包时的窗口
。 - 丢包后窗口减小。
- 距离
远时增长更快。 - 接近
时增长变慢,谨慎探测。 - 超过
后再逐渐加速寻找新的可用带宽。
CUBIC 更适合高速长距离网络。
基于延迟的拥塞控制
丢包驱动的 TCP 往往会把瓶颈队列填满,导致高 RTT。基于延迟的方法试图让管道“刚好满,但不过满”。
核心思路:
- 记录最小 RTT,记为
,近似代表无拥塞时延。 - 根据当前
cwnd / RTTmin估计无拥塞吞吐。 - 根据实际测得吞吐和 RTT 判断是否已经出现排队。
- 若接近无拥塞状态,增加窗口。
- 若实际吞吐明显低于无拥塞估计,减少窗口。
BBR 等算法体现了这种思路。
TCP 吞吐量
忽略慢启动,假设窗口在
平均吞吐量近似为:
对于长距离高速链路,传统 TCP 要达到高吞吐需要极低丢包率。例如 1500 字节段、100 ms RTT、目标 10 Gbps,需要约 83,333 个在途段,能容忍的丢包率极低。
ECN
ECN (Explicit Congestion Notification) 是网络辅助拥塞控制。
过程:
- 路由器在 IP 首部 ECN 位中标记拥塞,而不是直接丢包。
- 接收端收到带拥塞标记的数据报。
- 接收端在 TCP ACK 中设置 ECE 位通知发送方。
- 发送方降低发送速率,并可用 CWR 位表示已响应拥塞。
ECN 让网络可以在丢包前发出拥塞信号。
TCP 公平性
公平性目标:如果
在理想条件下,AIMD 会推动连接趋向公平共享。这些理想条件包括相同 RTT、固定会话数、都处在拥塞避免阶段。
实际中公平性会被破坏:
- UDP 应用可能不遵守 TCP 式拥塞控制。
- 应用可以开启多个并行 TCP 连接,抢占更多带宽。
- 不同 RTT 的 TCP 连接获得的份额可能不同。
3.8 传输层功能的演变
TCP 和 UDP 是互联网长期使用的核心传输协议,但不同场景推动了传输层功能不断演进。
| 场景 | 挑战 |
|---|---|
| 长肥管道 | 在途数据很多,一次丢包会让管道明显变空 |
| 无线网络 | 丢包可能来自链路噪声和移动性,但 TCP 会误认为拥塞 |
| 长时延链路 | RTT 很大,反馈慢 |
| 数据中心网络 | 对低时延和高吞吐都敏感 |
| 后台流量 | 希望低优先级传输,不影响交互流量 |
一种重要趋势是:把一部分传输层功能移动到应用层,并基于 UDP 部署。这使新协议更容易穿过已有网络设备,也更容易快速迭代。
QUIC
QUIC (Quick UDP Internet Connections) 是基于 UDP 的应用层传输协议,是 HTTP/3 的基础。
协议栈对比:
| 传统 HTTP/2 | HTTP/3 |
|---|---|
| HTTP/2 -> TLS -> TCP -> IP | HTTP/3 -> QUIC -> UDP -> IP |
QUIC 在应用层实现了很多类似 TCP 的能力:
- 可靠数据传输。
- 丢失检测。
- 拥塞控制。
- 连接建立。
- 加密和认证。
- 多路复用应用级流。
QUIC 的优势
连接建立更快
TCP + TLS 往往需要串行握手;QUIC 把可靠性、拥塞控制、认证和加密状态整合在一起,通常一个 RTT 内建立状态。减少队头阻塞
在 TCP 中,一个丢失的字节会阻塞后续字节交付,导致同一连接上的多个 HTTP 流都受影响。QUIC 在单个连接上提供多个独立可靠流,一个流丢包不会阻塞其他流的应用层交付。更容易演进
QUIC 运行在 UDP 之上,很多中间设备只看到 UDP 流量。协议逻辑在应用层实现,升级速度比修改内核 TCP 和网络设备更快。
QUIC 说明:传输层的核心思想没有消失,而是被重新组合到了更容易部署的层次中。
3.9 本章小结
本章的核心问题是:如何在不可靠的网络层之上,为应用进程提供可用的端到端传输服务?
需要掌握的主线:
- 传输层提供进程到进程的逻辑通信,网络层提供主机到主机的逻辑通信。
- 多路复用和多路分解依靠端口号和套接字完成。
- UDP 是无连接、不可靠、低开销的传输协议,适合实时、简单查询和应用自定义传输逻辑。
- 可靠数据传输依靠校验和、ACK/NAK、序列号、定时器和重传。
- 停止-等待协议正确但效率低,流水线协议通过窗口提高链路利用率。
- GBN 使用累积 ACK 和批量回退重传;SR 使用单独 ACK、缓存乱序分组和选择性重传。
- TCP 提供可靠有序字节流,使用序列号、累积 ACK、RTT 估计、超时重传和快速重传。
- TCP 流量控制通过
rwnd防止接收方缓冲区溢出。 - TCP 三次握手建立连接,FIN/ACK 机制关闭连接。
- 拥塞控制关注网络内部过载,不同于只保护接收方的流量控制。
- TCP 拥塞控制使用
cwnd、慢启动、拥塞避免、快速恢复、AIMD 等机制调整发送速率。 - CUBIC、基于延迟的方法、ECN 和 QUIC 体现了传输层功能在现代网络中的持续演进。
下一章开始进入网络核心:网络层如何转发数据报,以及路由器内部和控制平面如何工作。