程云来 / 杭州
返回文章·计算机网络
·约 11 分钟

网络面试(十):丢包和乱序之后,TCP 怎样交付正确数据?

追踪五段字节区间中的一个缺口,理解累计 ACK、重复数据、RTO 与快速重传,再用 SACK 区分已到达的乱序数据和已经累计确认的数据。


后面的数据到了,为什么应用还在等?

设想 TCP 连接已经建立,A 要向 S 发送 5000 字节。中间一段丢了,后面的三段却顺利到达。S 的网卡并非完全没有收到数据,应用读取字节流时仍可能停在缺口前。

TCP 按字节位置管理数据,向应用交付有序字节流;“到达接收端”与“可以按序交付”是两回事。 序列号、确认和重传把丢失、重复、乱序转化为可管理的传输状态。但连接仍可能失败,可靠传输不等于无条件成功。

本章沿用上一章数据从序列号 1001 开始的设定。下面没有真实抓包,也不模拟所有 TCP 选项和拥塞状态,只追踪一个方向的数据。

Seq 编的是字节位置,不是第几个包

假设 A 在窗口允许时发送五段,每段 1000 字节:

段Seq字节区间本次结果
P11001[1001, 2001)到达
P22001[2001, 3001)丢失
P33001[3001, 4001)到达
P44001[4001, 5001)到达
P55001[5001, 6001)到达

[1001, 2001) 是左闭右开区间,包含 1001—2000,共 1000 个字节。报文边界可因发送、重传或路径调整而变化,因此 Seq 不能等同于“包编号”。同一连接反方向使用另一套编号,真实 32 位序列空间还涉及回绕。RFC 9293 §3.4

TCP 还使用校验和检测传输差错。检测到不符合校验或接收条件的报文,不会被当作正常的新数据直接交给应用。但校验和不是密码学认证,也不是绝对防篡改保证;安全通道仍需要 TLS 等机制。RFC 9293 §3.1

累计 ACK 为什么一直停在 2001?

Ack 表示接收方下一步期望的序列号。在本章无 SYN/FIN 的数据阶段,Ack=2001 表示此前连续的数据已收到,接下来仍需要从 2001 开始的字节。它不是“我收到了第 2001 个包”。

假设 S 暂存乱序数据,并为下面的到达事件发出相应确认:

接收事件连续收到的范围缺口后的暂存范围累计 ACK
P1 到达[1001, 2001)无2001,首次前进
P3 到达[1001, 2001)[3001, 4001)2001,重复 1
P4 到达[1001, 2001)[3001, 5001)2001,重复 2
P5 到达[1001, 2001)[3001, 6001)2001,重复 3
P2 补到[1001, 6001)缺口消失6001

后面三段已到达,却不能越过缺口让累计确认跳到 6001。P2 补到并连上暂存区间后,累计 ACK 才能一次前进到 6001。此前可交付的是已形成连续范围的数据,不需要等整份 5000 字节全部到齐才开始交付。

这是累计确认(Cumulative Acknowledgment)的作用:一份新的 ACK 可以覆盖许多字节。TCP 不要求每段各有一份独立应答,延迟确认或确认丢失会改变实际报文数量;表格特意选择即时确认与暂存成功的轨迹来解释缺口。

ACK 丢了,会不会把数据交付两遍?

另设一个分支:P1 到达,S 返回的确认丢失。A 因没有足够确认而重发相同字节区间时,S 根据序列位置识别已收到的部分,不把它作为新的 1000 字节再次插入应用字节流,并继续反馈接收状态。若后来更大的累计 ACK 已到达,A 也可能无需重发 P1。

这解决的是同一 TCP 字节区间的传输重复。应用如果把同一“创建订单”消息再次编码成新的字节并发送,那仍是新数据,TCP 不会因为业务内容相同就替应用去重。可靠传输与业务幂等在第 14 章继续区分。

没有足够确认时,发送方何时重传?

发送方保存尚未确认的数据及相关状态,以便恢复。不能一边发送,一边立即把所有数据当作已成功交付而丢弃。

RTO(Retransmission Timeout,重传超时)是发送方等待确认的时间估计,和 RTT(往返时间)不同。RTT 是一次观测,RTO 要给路径波动留余量;设置太短容易误重传,太长又会拖慢恢复。RFC 6298 §2

按 RFC 的首个有效 RTT 样本初始化规则,设样本 R 为 400ms,时钟粒度 G 为 1ms:

text
SRTT = R = 400ms
RTTVAR = R / 2 = 200ms
RTO = SRTT + max(G, 4 × RTTVAR)
    = 400ms + max(1ms, 800ms)
    = 1200ms

SRTT 是平滑 RTT,RTTVAR 是用于计时的变化估计,并非统计学意义上“方差”的单位平方。后续样本继续更新这两项。本例结果高于 RFC 建议的 1 秒计算下限;这里没有声称每个系统的最小 RTO、时钟或配置都相同。

若重传定时器到期,发送方重传最早未确认的数据,并对 RTO 退避;在没有新样本等其他更新的简化连续超时中,可由 1.2 秒加倍为 2.4 秒,再到 4.8 秒。RFC 6298 §5

这不是“每个数据包都开一个固定计时器”。有效新确认、尚未确认的数据与计时状态共同影响定时器。重传后收到的确认若无法区分对应原发送还是重传,不能直接拿来当可靠 RTT 样本;Karn 算法处理这种歧义,时间戳可提供额外辨别信息。RFC 6298 §3

有缺口证据,为什么还要一直等超时?

回到五段数据的表格。A 已收到一次首次前进到 2001 的 ACK,之后又收到三个符合规则的重复 ACK,确认号仍为 2001。后面的数据持续到达而缺口不消失,A 有理由提前怀疑 P2 丢失。

经典快速重传(Fast Retransmit)用三个重复 ACK 作为丢失指示,不必等 RTO 到期就重传看起来缺失的段。RFC 5681 §3.2给出了相应规则。

本例是首次 ACK 加三个重复 ACK,共四份 Ack=2001。 “抓到三份相同确认号”不必然就是三个重复 ACK;规范还检查是否有未确认数据、数据内容与窗口等条件,不能仅比对一个数字。

重复 ACK 也可能由乱序或复制产生,它提供的是丢失指示,而不是绝对证明。因此快速重传可能是多余的;不能把每次重复 ACK 都立即当作新的丢包。

若只剩尾部一段丢失,没有足够后续数据到达,三个重复 ACK 可能凑不齐。超时仍是恢复的一条路径,现代实现也可使用额外探测和基于时间的丢失判断。RFC 8985的 RACK-TLP 描述了这类机制。面试解释经典流程时要标明模型,不把“只能等三个重复 ACK 或 RTO”写成全部现行实现。

快速重传负责尽早补数据;快速恢复(Fast Recovery)还涉及发送节奏与拥塞窗口调整,不能把两个名称当作同义词,下一章再展开。

SACK 怎样告诉发送方“后面哪些已经到了”?

累计 ACK 停在 2001 时,A 只知道前面的连续区间,无法仅凭该字段确定 P3、P4、P5 哪些已暂存。

SACK(Selective Acknowledgment,选择性确认)允许接收方在 TCP 选项中报告额外已收到的字节块。接收方只有先收到对端 SYN 中的 SACK-Permitted 许可,才能发回 SACK;两个数据方向分别判断,报块仍与累计 ACK 一起使用。本例中,A 在 SYN 中声明可以接收 SACK,S 才能在确认 A 的数据时报告这些字节块。RFC 2018 §2—§4

接收事件累计 ACK本例报告的 SACK 块
P3 到达2001[3001, 4001)
P4 到达2001[3001, 5001)
P5 到达2001[3001, 6001)

SACK 块的右边界同样不包含在区间内。连续到达的后续数据可合成一个块;多处缺口则可能对应多个块,选项空间限制了单份报文能报告的数量。

A 据此维护已收到区间的记录,更有针对性地修补缺口,而不必把所有缺口后的数据都当作未到达。SACK 本身只提供信息,具体何时重传、如何恢复还需要算法;RFC 6675给出了基于 SACK 的保守丢失恢复方法。

SACK 不是累计确认已经越过缺口。 接收方在某些条件下可以丢弃已报告但尚未累计确认的乱序数据,发送方不能仅凭 SACK 就永久丢弃重传所需数据。RFC 2018 §8明确了这一边界。P2 补齐后的 Ack=6001,才把本例整个范围纳入累计确认。

动手补齐一个字节缺口

先让 P3—P5 到达,看累计 ACK 与 SACK 分别报告什么;再补到 P2,最后重复送达 P1。注意“已到达”“连续可交付”与“业务完成”的区别。

后面的字节到了,为什么 ACK 不前进?

五段各1000字节,P2在首次发送时丢失。逐步接收后续数据,再补齐缺口。

P1[1001, 2001)尚未收到
P2[2001, 3001)尚未收到
P3[3001, 4001)尚未收到
P4[4001, 5001)尚未收到
P5[5001, 6001)尚未收到
累计 ACK:1001

连续可交付:0 字节
乱序暂存:0 字节

首次 Ack=2001 后的重复ACK:0
SACK 报告

无缺口后的暂存块

SACK不会让累计ACK越过缺口。
1 / 7

一个方向的教学轨迹,假设即时确认、ACK均到达发送方、乱序暂存成功、窗口不变,省略序列号回绕。SACK许可预先在握手中确定,切换仅比较两种设定;SACK报块不允许发送方永久丢弃未累计确认数据。可交付不代表应用已读取或事务提交;不运行真实TCP。

“可靠”究竟到哪一层为止?

TCP 通过序列空间、校验、确认、重复处理、重传与按序交付管理字节流。某个 ACK 表示对端 TCP 的接收状态,不等于对端应用已经读取,更不等于数据库提交成功。

路径持续失效、对端重启、超时放弃或连接复位都可能使传输无法完成,应用需要处理错误。TCP 也不保证延迟上界、不认证业务身份、不保存应用消息边界。

观察不能直接推导的结论
send 调用成功对端已读取或执行业务
累计 ACK 前进业务事务已提交
重复 ACK 增多每份重复 ACK 都对应一次独立丢包
SACK 报告后段到达缺口消失或可以丢弃全部发送缓存

字节补齐之后,发送方仍不能无限发:对端可能来不及读取,路径也可能拥塞。下一章区分接收窗口与拥塞窗口,解释流量控制和拥塞控制。

面试时怎样回答?

30—60 秒参考回答

TCP 用序列号标识字节位置,接收方累计 ACK 表示下一步期望的序列号。后续数据乱序到达时可暂存,但累计确认与按序交付不能越过缺口。发送方保存未确认数据,通过 RTO 超时恢复;经典快速重传利用三个重复 ACK 提前怀疑丢失。SACK 进一步报告已到达的后续区间,帮助选择恢复目标,但不替代累计确认。重复传输的同一字节区间不会作为新数据重复交付。TCP 可靠性属于传输层,ACK 不证明应用提交成功,连接仍可能报错。

四个追问及解析

  1. P3—P5 到达,为什么 ACK 还是 2001? [2001, 3001) 仍有缺口;本例暂存后续区间,但没有形成连续到 6001 的范围。
  2. ACK 丢失为什么不一定重传? 后续更大的累计 ACK 可以覆盖此前已收到的数据;没有足够确认且恢复条件满足时才需要重传。
  3. 三份 Ack=2001 就一定快速重传吗? 不一定,要区分首次确认与三个重复确认,并满足相应规则;乱序也可能造成重复确认。
  4. SACK 到 6001,等于累计 ACK 到 6001 吗? 不等于。它可能只报告 [3001, 6001),前面的缺口仍未补齐。

两个易错说法

  • “确认号每收一个包就加一。”数据阶段按字节位置前进,累计确认也可能一次跨过多个段。
  • “TCP 去重,所以业务重试不用幂等。”TCP 处理传输字节区间的重复,无法替应用判断两份新消息是否为同一业务请求。

资料与核验日期

核验于 2026-10-08。五段发送、即时确认、乱序暂存与计时均为教学设定,不是实际系统抓包。拥塞恢复的具体窗口调整留到下一章。

  • RFC 9293:TCP 序列空间、校验、确认及交付语义。
  • RFC 6298:RTO、RTT 样本与退避。
  • RFC 5681:经典快速重传与重复 ACK 条件。
  • RFC 2018、RFC 6675:SACK 格式、保留数据的边界与恢复算法。
  • RFC 8985:RACK-TLP 丢失检测与探测。