后面的数据到了,为什么应用还在等?
设想 TCP 连接已经建立,A 要向 S 发送 5000 字节。中间一段丢了,后面的三段却顺利到达。S 的网卡并非完全没有收到数据,应用读取字节流时仍可能停在缺口前。
TCP 按字节位置管理数据,向应用交付有序字节流;“到达接收端”与“可以按序交付”是两回事。 序列号、确认和重传把丢失、重复、乱序转化为可管理的传输状态。但连接仍可能失败,可靠传输不等于无条件成功。
本章沿用上一章数据从序列号 1001 开始的设定。下面没有真实抓包,也不模拟所有 TCP 选项和拥塞状态,只追踪一个方向的数据。
Seq 编的是字节位置,不是第几个包
假设 A 在窗口允许时发送五段,每段 1000 字节:
| 段 | Seq | 字节区间 | 本次结果 |
|---|---|---|---|
| P1 | 1001 | [1001, 2001) | 到达 |
| P2 | 2001 | [2001, 3001) | 丢失 |
| P3 | 3001 | [3001, 4001) | 到达 |
| P4 | 4001 | [4001, 5001) | 到达 |
| P5 | 5001 | [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:
SRTT = R = 400ms
RTTVAR = R / 2 = 200ms
RTO = SRTT + max(G, 4 × RTTVAR)
= 400ms + max(1ms, 800ms)
= 1200msSRTT 是平滑 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。注意“已到达”“连续可交付”与“业务完成”的区别。
五段各1000字节,P2在首次发送时丢失。逐步接收后续数据,再补齐缺口。
连续可交付:0 字节
乱序暂存:0 字节
无缺口后的暂存块
SACK不会让累计ACK越过缺口。下一步先接收P1;每个区间右边界不包含在内。
一个方向的教学轨迹,假设即时确认、ACK均到达发送方、乱序暂存成功、窗口不变,省略序列号回绕。SACK许可预先在握手中确定,切换仅比较两种设定;SACK报块不允许发送方永久丢弃未累计确认数据。可交付不代表应用已读取或事务提交;不运行真实TCP。
“可靠”究竟到哪一层为止?
TCP 通过序列空间、校验、确认、重复处理、重传与按序交付管理字节流。某个 ACK 表示对端 TCP 的接收状态,不等于对端应用已经读取,更不等于数据库提交成功。
路径持续失效、对端重启、超时放弃或连接复位都可能使传输无法完成,应用需要处理错误。TCP 也不保证延迟上界、不认证业务身份、不保存应用消息边界。
| 观察 | 不能直接推导的结论 |
|---|---|
| send 调用成功 | 对端已读取或执行业务 |
| 累计 ACK 前进 | 业务事务已提交 |
| 重复 ACK 增多 | 每份重复 ACK 都对应一次独立丢包 |
| SACK 报告后段到达 | 缺口消失或可以丢弃全部发送缓存 |
字节补齐之后,发送方仍不能无限发:对端可能来不及读取,路径也可能拥塞。下一章区分接收窗口与拥塞窗口,解释流量控制和拥塞控制。
面试时怎样回答?
30—60 秒参考回答
TCP 用序列号标识字节位置,接收方累计 ACK 表示下一步期望的序列号。后续数据乱序到达时可暂存,但累计确认与按序交付不能越过缺口。发送方保存未确认数据,通过 RTO 超时恢复;经典快速重传利用三个重复 ACK 提前怀疑丢失。SACK 进一步报告已到达的后续区间,帮助选择恢复目标,但不替代累计确认。重复传输的同一字节区间不会作为新数据重复交付。TCP 可靠性属于传输层,ACK 不证明应用提交成功,连接仍可能报错。
四个追问及解析
- P3—P5 到达,为什么 ACK 还是 2001?
[2001, 3001)仍有缺口;本例暂存后续区间,但没有形成连续到 6001 的范围。 - ACK 丢失为什么不一定重传? 后续更大的累计 ACK 可以覆盖此前已收到的数据;没有足够确认且恢复条件满足时才需要重传。
- 三份 Ack=2001 就一定快速重传吗? 不一定,要区分首次确认与三个重复确认,并满足相应规则;乱序也可能造成重复确认。
- SACK 到 6001,等于累计 ACK 到 6001 吗? 不等于。它可能只报告
[3001, 6001),前面的缺口仍未补齐。
两个易错说法
- “确认号每收一个包就加一。”数据阶段按字节位置前进,累计确认也可能一次跨过多个段。
- “TCP 去重,所以业务重试不用幂等。”TCP 处理传输字节区间的重复,无法替应用判断两份新消息是否为同一业务请求。
资料与核验日期
核验于 2026-10-08。五段发送、即时确认、乱序暂存与计时均为教学设定,不是实际系统抓包。拥塞恢复的具体窗口调整留到下一章。