计算机网络:TCP 如何保证传输可靠性
Category(分类): NET Protocol Status: 未知
原文参考:TCP协议如何保证传输可靠性
TCP 协议的主要特点是面向连接、面向字节流,并且为应用程序提供可靠、有序的传输服务。IP 层本身只负责尽力而为地传递数据,可能出现丢包、重复、乱序和比特错误等情况,因此 TCP 在 IP 之上增加了一系列机制来提高传输的可靠性。
TCP 保证可靠传输的主要机制包括:
- 校验和;
- 序列号与确认应答;
- 超时重传和快速重传;
- 连接管理;
- 流量控制;
- 拥塞控制。
其中,校验和主要用于发现传输过程中的数据损坏;序列号和 ACK 用于识别数据、处理乱序和重复;重传机制用于恢复丢失或未被确认的数据;流量控制用于保护接收方,拥塞控制用于保护网络。
1. 校验和
校验和用于检查报文在传输过程中是否发生了比特错误。发送方根据报文内容计算校验和值并填入报文,接收方收到报文后使用相同的算法重新计算并进行比较。
如果校验结果不符合要求,接收方通常会丢弃该报文。TCP 不需要单独发送“校验失败”的通知,发送方如果没有收到有效 ACK,之后可能通过超时重传或快速重传恢复数据。
需要注意,IPv4 首部校验和与 TCP 校验和不是同一个概念:
- IPv4 首部校验和只检查 IPv4 首部,不检查 TCP 首部和 TCP 数据;IPv6 没有 IPv4 这种首部校验和。
- TCP 校验和是 TCP 可靠传输的一部分,覆盖 TCP 首部、TCP 数据以及由 IP 地址、协议号和 TCP 长度组成的 IP 伪首部。IPv4 和 IPv6 的 TCP 报文都需要进行 TCP 校验和校验。
校验和能够发现大量传输错误,但它不是密码学完整性校验。由于校验和存在碰撞的可能,即使校验和相同,也不能从数学上保证数据绝对没有发生错误。
IPv4 首部校验和示例
下面保留一个 IPv4 首部校验和的计算示例。这个示例只用于说明 IPv4 首部校验和的算法,不能当作 TCP 数据校验和的计算示例。

计算 IPv4 首部校验和时,需要:
- 将校验和字段(16 位首部校验和)置为 0;
- 按 16 bit 为单位对 IPv4 首部进行反码加法求和;
- 将产生的进位加回低 16 位,重复进行,直到没有高 16 位进位;
- 将得到的 16 位结果按位取反,填入校验和字段。
示例 IPv4 首部如下:
45 00 00 31
89 F5 00 00
6E 06 00 00(校验和字段,计算时置为 0)
DE B7 45 5D -> 222.183.69.93
C0 A8 00 DC -> 192.168.0.220
计算过程:
4500 + 0031 + 89F5 + 0000 + 6E06 + 0000
+ DEB7 + 455D + C0A8 + 00DC
= 0x322C4
0x0003 + 0x22C4 = 0x22C7
~0x22C7 = 0xDD38
因此,该 IPv4 首部的校验和可以填为 DD38。接收方把 DD38 也加入计算:
4500 + 0031 + 89F5 + 0000 + 6E06 + DD38
+ DEB7 + 455D + C0A8 + 00DC
= 0x3FFFC
0x0003 + 0xFFFC = 0xFFFF
~0xFFFF = 0x0000
计算结果为 0,说明该首部通过了这次校验。
2. 序列号与确认应答
TCP 提供的是有序字节流,而不是带有消息边界的报文消息。应用程序写入 TCP 的数据会被 TCP 拆分成合适大小的多个 TCP 报文段,接收方再按照序列号重新排序,并将连续的字节流交给应用程序。
数据在经过路由器、网关等设备时,可能发生丢失、重复或乱序,因此 TCP 需要使用序列号和确认应答来识别数据。
2.1 MSS 与 TCP 分段
MSS 是 Maximum Segment Size 的缩写,表示单个 TCP 报文段允许携带的最大 TCP 数据长度,不包括 IP 首部和 TCP 首部。它不是“最大消息长度”,也不是整个 IP 数据包的最大长度。
在 IPv4、MTU 为 1500 字节、IP 首部和 TCP 首部都没有额外选项时,MSS 常见值为:
1500 - 20(IPv4 首部)- 20(TCP 首部)= 1460 字节
但 1460 不是固定值。IPv6、隧道、VPN、TCP 选项或其他网络环境都可能导致 MSS 不同。
MSS 选项只出现在 SYN 报文中,包括初始 SYN 和 SYN+ACK。它表达的是发送该选项的一方愿意接收的单个 TCP 报文段数据大小。双方的 MSS 可以不同,发送方应根据对端通告的 MSS 限制自己发送的 TCP 数据段大小,而不是简单地认为双方永远使用同一个较小值。
TCP 根据 MSS 把连续字节流拆分为多个 TCP 报文段,这个过程称为 TCP 分段,不应与 IP 分片混淆。现代网络通常会通过 MSS、路径 MTU 发现等方式尽量避免 IP 分片。

2.2 序列号
TCP 会对字节流中的每个字节进行编号。一个 TCP 数据段的序列号通常表示该段中第一个数据字节的序号。例如:
第一个数据段:序列号 1,携带字节 1~1000
第二个数据段:序列号 1001,携带字节 1001~2000
第三个数据段:序列号 2001,携带字节 2001~3000
TCP 序列号是 32 位,并且会按模 2^32 回绕。SYN 和 FIN 虽然不携带普通应用数据,但各自都会占用一个序列号;纯 ACK 不占用序列号空间。
序列号的作用包括:
- 按正确顺序重新组装数据;
- 识别重复数据并丢弃重复部分;
- 判断哪些数据已经确认,哪些数据仍然需要重传;
- 配合接收窗口限制可接受的数据范围。
2.3 确认应答
TCP ACK 通常是累计确认。ACK 号表示接收方期望收到的下一个序列号,也就是说,小于该 ACK 号的数据已经连续收到。
例如,接收方连续收到序列号为 1~1000 的数据后,可以回复:
ACK = 1001
这表示序列号小于 1001 的数据已经连续收到,接下来希望收到序列号 1001 开始的数据。
TCP 不要求每收到一个报文段就立即发送一个 ACK。接收方可能采用延迟确认,也可能把 ACK 捎带在自己发送的数据中;收到乱序数据时,还可能发送重复 ACK,提示发送方某个序列号之前的数据仍然缺失。因此,TCP 不是“发送一个数据包、等待一个 ACK、再发送下一个”的停等协议,而是可以利用滑动窗口连续发送多个未确认报文段。

3. 超时重传与快速重传
TCP 发送数据后,会记录尚未确认的数据。如果数据段或 ACK 在网络中丢失,发送方可能迟迟收不到确认。TCP 使用重传机制恢复这些数据。
3.1 超时重传
发送方会为未确认的数据维护重传计时器。如果计时器超时,发送方通常会重传最早尚未确认的数据段。
TCP 的重传超时时间(RTO)不是固定值,而是根据往返时间 RTT 和 RTT 的波动情况动态估算。常见计算会结合平滑往返时间 SRTT、往返时间方差 RTTVAR 和时钟粒度 G,形式类似:
RTO = SRTT + max(G, 4 × RTTVAR)
实际实现还会设置最小值、最大值和初始值。根据 RFC 6298,在还没有 RTT 样本时,初始 RTO 通常取 1 秒,但具体实现可能有所调整。
超时后,RTO 通常会进行指数退避,例如将等待时间加倍;重传次数达到系统配置的上限后,操作系统可能认为连接不可用并向应用程序报告错误。具体重传次数、计时粒度和连接清理策略由操作系统及配置决定,不能统一写成 500ms 的整数倍。
如果 ACK 丢失但数据实际上已经到达,发送方也可能重传相同的数据。接收方可以根据序列号识别重复数据并重新发送 ACK,因此重传不会导致应用程序收到重复的字节流。
3.2 快速重传
如果接收方收到后续数据,却发现中间某段缺失,通常会重复确认当前连续数据末尾的下一个序列号。发送方连续收到 3 个重复 ACK 时,经典 TCP Reno 通常会认为存在报文段丢失,在重传计时器超时前立即重传可能丢失的报文段,这就是快速重传。
快速重传并不一定能证明发生了丢包,严重的网络乱序也可能产生重复 ACK。现代 TCP 还可能使用 SACK 等选项更准确地描述已经收到的非连续数据。
4. 连接管理
TCP 的连接管理包括连接建立、数据传输状态维护和连接关闭。三次握手用于同步双方的初始序列号、确认连接两端已经准备好通信,并避免历史重复连接请求造成混乱;四次挥手用于分别关闭两个方向的数据传输。
连接管理本身不是“每个数据都可靠”的直接原因,但它为序列号、确认号、窗口和重传机制建立了必要的状态基础。
正常关闭时使用 FIN,异常终止时可能使用 RST。主动关闭的一方通常需要经历 TIME-WAIT,以便重新发送最后的 ACK,并避免旧连接的延迟报文干扰后续连接。
5. 流量控制
流量控制用于防止发送方发送速度过快,导致接收方的接收缓冲区被填满。
接收方会在 TCP 报文的窗口字段中通告自己当前还能接收多少数据,这个值通常称为接收窗口 rwnd。发送方根据对端通告的接收窗口限制尚未确认的数据量,从而避免接收缓冲区溢出。
5.1 接收窗口与窗口扩大
TCP 首部中的窗口字段本身是 16 位,未使用窗口扩大选项时最多表示 65535 字节。为了支持高带宽、高延迟网络,双方可以在 SYN 阶段协商 Window Scale 选项。
窗口扩大因子是一个移位值,实际接收窗口可以理解为:
实际接收窗口 = TCP 窗口字段值 << 窗口扩大因子
窗口扩大因子最大为 14。它不是“TCP 首部剩余 40 字节中的一个普通窗口字段”,而是 TCP 选项空间中的独立选项,并且必须在连接建立时协商;如果没有协商成功,就不能使用窗口扩大。
接收窗口表示接收方愿意接收的数据量,MSS 表示单个 TCP 段的最大数据长度,二者含义不同。
5.2 滑动窗口和零窗口
滑动窗口不是单纯“接收端使用的窗口”,而是 TCP 发送方和接收方共同参与的机制:
- 接收方通过
rwnd告知发送方自己还能接收多少数据; - 发送方维护已发送但未确认的数据范围,并在窗口允许的范围内连续发送;
- 当接收方应用程序读取数据后,缓冲区腾出空间,接收方会在后续 ACK 中通告更大的窗口。
发送方实际允许发送的数据量通常受以下两个窗口中较小者限制:
发送窗口 ≈ min(rwnd, cwnd)
其中 rwnd 是接收窗口,cwnd 是拥塞窗口。
如果接收方通告窗口为 0,发送方通常会停止发送新的应用数据,但仍需要通过持久计时器发送零窗口探测,以确认对方窗口是否已经重新打开。接收方窗口恢复后,发送方才能继续发送数据。
6. 拥塞控制
流量控制解决的是“接收方是否来得及处理”的问题,拥塞控制解决的是“网络是否承受得住”的问题。
通信双方通常无法直接知道网络中所有路由器和链路的剩余容量,因此 TCP 发送方需要根据 ACK、丢包、RTT 等反馈估计网络状况,并维护拥塞窗口 cwnd。拥塞窗口限制的是发送方在网络中可以保持的未确认数据量。
经典 TCP 拥塞控制通常包括:
- 慢启动(slow start);
- 拥塞避免(congestion avoidance);
- 快速重传(fast retransmit);
- 快速恢复(fast recovery)。
不同 TCP 实现和算法的具体行为可能不同。例如 Linux 常见实现使用 CUBIC,BBR 等算法也不完全遵循经典 Reno 的窗口变化公式。下面的快速恢复主要描述经典 Reno 的典型流程。
6.1 慢启动
当 TCP 刚开始发送数据时,并不清楚当前网络的可用容量。如果一开始就发送大量数据,可能立即造成拥塞,因此发送方通常从较小的拥塞窗口开始,并根据收到的 ACK 逐渐增大 cwnd。
现代 TCP 的初始拥塞窗口不一定是 1 个 MSS,通常可以是多个 MSS,具体取值取决于规范和实现。为了便于理解,如果使用早期教材中的简化模型,假设初始 cwnd = 1 MSS,并且每个报文段都及时得到确认,那么窗口可能近似表现为:
开始:1 MSS
经过约 1 个 RTT:2 MSS
经过约 2 个 RTT:4 MSS
经过约 3 个 RTT:8 MSS
这种“每个 RTT 近似翻倍”的现象来自慢启动阶段每收到一个有效 ACK,窗口最多增加约一个 SMSS。实际增长会受到延迟 ACK、ACK 丢失、初始窗口、实现策略和拥塞阈值 ssthresh 的影响。
慢启动中的“慢”不是指窗口增长得很慢,而是指开始发送时先使用较小窗口进行探测。达到 ssthresh 或检测到拥塞后,TCP 通常会转入拥塞避免或执行丢包恢复流程。
RTT(Round-Trip Time)表示往返时延,即报文从发送方到接收方再返回确认所经历的时间。一个传输轮次还强调了发送窗口允许的数据被连续发送,并等待相应确认的过程,二者并不完全等价。
6.2 拥塞避免
在拥塞避免阶段,拥塞窗口增长速度会比慢启动慢,经典 Reno 通常近似每经过一个 RTT 增加一个 SMSS,使 cwnd 按线性规律增长,而不是继续指数增长。
“拥塞避免”并不代表可以完全避免拥塞,而是通过降低增长速度,使网络不容易迅速进入拥塞状态。
当发送方通过重传超时判断发生严重拥塞时,经典 TCP 通常会执行类似以下操作:
ssthresh = max(FlightSize / 2, 2 × SMSS)
cwnd = 较小的初始值
然后重新进入慢启动。这里的 FlightSize 是已经发送但尚未被累计确认的数据量,不一定等于 cwnd。现代实现可能根据具体拥塞控制算法采用不同策略。
不能简单地把“没有马上收到 ACK”都视为拥塞。ACK 可能延迟或丢失,TCP 通常要结合重复 ACK、重传超时和 RTT 等多种信息进行判断。
6.3 快速重传
经典 TCP Reno 在收到 3 个重复 ACK 后,会认为某个报文段可能丢失,并在重传定时器超时前立即重传该报文段。
重复 ACK 通常包含相同的确认号,表示接收方仍在等待某个序列号开始的数据。例如,接收方已经收到后续报文段,但缺少序列号为 1001 的数据,它可能多次回复:
ACK = 1001
快速重传可以比等待 RTO 更快地恢复丢失数据,但也可能被网络乱序误触发。SACK 可以帮助发送方了解接收方已经收到哪些非连续数据。
6.4 快速恢复
在经典 Reno 中,收到第 3 个重复 ACK 后通常会:
- 根据当前
FlightSize计算新的ssthresh,通常约为其一半,但不能低于2 × SMSS; - 重传推测丢失的报文段;
- 临时将
cwnd设置为ssthresh + 3 × SMSS,因为 3 个重复 ACK 表明后续报文段仍有可能已经到达接收方; - 每收到一个额外的重复 ACK,临时将
cwnd增加约一个 SMSS,并在窗口允许时发送新的报文段; - 收到确认新数据的 ACK 后,将
cwnd调整为ssthresh,进入拥塞避免阶段。
这是一种经典 Reno 的描述。现代 TCP 的拥塞控制、SACK 恢复以及 CUBIC、BBR 等算法可能采用不同的窗口调整方式,不能将上述步骤当作所有 TCP 实现的固定行为。
7. TCP 可靠传输机制总结
| 机制 | 主要解决的问题 |
|---|---|
| TCP 校验和 | 发现 TCP 首部或数据在传输中发生的比特错误 |
| 序列号 | 标识字节流位置,处理乱序和重复数据 |
| 累计 ACK | 告诉发送方哪些连续数据已经收到,以及下一个期望的序列号 |
| 超时重传 | 在 ACK 长时间未到达时恢复可能丢失的数据 |
| 快速重传 | 根据重复 ACK 提前恢复可能丢失的数据,减少等待 RTO 的时间 |
| 连接管理 | 同步初始序列号,维护连接状态,并有序关闭连接 |
| 流量控制 | 防止发送方耗尽接收方缓冲区 |
| 拥塞控制 | 防止发送速率超过网络承载能力 |
这些机制共同作用,使 TCP 在不可靠的 IP 网络之上,为应用程序提供可靠、有序、无重复的字节流。TCP 并不能保证网络永远不丢包,也不能保证数据一定在某个时间内送达;它的可靠性是通过检测问题、确认数据和重新传输等方式实现的。