TCP 可靠传输
基于 HTTP 现代标准 · 核于 2026-06
速查
- 可靠性的根基:序列号 + 确认号 + 重传 + 校验和 + 排序去重,五件事缺一不可。
- 序列号(Sequence Number):TCP 给字节流里每一个字节编号,而非给报文段编号;段头里的序列号是本段第一个字节的编号。
- 确认号(ACK Number)= 累积确认:ACK 值 X 表示「X 之前的所有字节都已正确收到,下一个我期待第 X 字节」(RFC 9293)。
- 超时重传(RTO):每段发出后启动定时器,超时未确认就重传;RTO 由 RTT 动态估算(RFC 6298)。
- RTT 估算:
SRTT(平滑 RTT)+RTTVAR(偏差),RTO = SRTT + max(G, 4·RTTVAR),α=1/8、β=1/4,初始/最小 RTO = 1 秒。 - Karn 算法:重传段的 RTT 样本不采用(无法分辨 ACK 应给原段还是重传段),且重传时 RTO 翻倍退避。
- 快速重传:收到 3 个重复 ACK 即判定丢段,立刻重传,不等 RTO 超时(RFC 5681)。
- 选择确认 SACK:可选项(RFC 2018 / 2883),告知发送方「哪些不连续的段已收到」,使其只重传真正丢失的段。
- 滑动窗口是载体:发送窗口里区分「已发已确认 / 已发未确认 / 可发未发 / 不可发」,已发未确认的部分由重传机制兜底。
- 有序交付与去重:接收方按序列号重排乱序段、丢弃重复字节,再交给应用层。
- 本页只讲可靠性:窗口大小受
rwnd(流量控制)和cwnd(拥塞控制)共同限制,细节见下一页。
IP 只负责「尽力而为」地把数据报送到目的地:它不保证送达、不保证顺序、不保证不重复、不保证不出错。可靠传输的全部担子,落在它上面的 TCP 身上。本页讲清楚 TCP 是用哪几样机制,把一条不可靠的 IP 信道改造成「字节流原样、有序、不丢、不重」的可靠管道。
一、不可靠的 IP,可靠的 TCP
Cloudflare 用一个比喻说得很形象:把消息写在一张拼图上寄出,拼图被拆成碎片,每片走不同的邮路、有快有慢,到达时顺序是乱的,还可能丢片。IP 只保证「把碎片送到这个地址」;而 TCP 就是收件那一端的拼图组装工——把碎片按正确顺序拼好、发现缺片就要求重寄、拼完后告诉寄件人「收到了」。
原文:IP "does not handle packet ordering or error checking";TCP "puts the pieces together in the right order, asks for missing pieces to be resent, and lets the sender know the puzzle has been received."
MDN 的定义同样直接:"TCP guarantees the delivery of data and packets in the same order as they were sent",且 "TCP's role is to ensure the packets are reliably delivered, and error-free"。
IP 给了什么、没给什么
IP 提供:寻址、路由、分片。IP 不提供:送达保证、顺序保证、去重、(除头部外的)数据校验。TCP 补齐的正是后面这一整组。
二、序列号:给每个字节编号
TCP 把应用层数据看成一条连续的字节流,并给流中每一个字节分配一个序列号(而不是给每个报文段编一个号)。一个报文段头部里的「序列号」字段,填的是本段携带的第一个字节的编号。
举例:从字节 1000 开始发送,每段 500 字节:
段 A: seq=1000, len=500 → 覆盖字节 [1000, 1499]
段 B: seq=1500, len=500 → 覆盖字节 [1500, 1999]
段 C: seq=2000, len=500 → 覆盖字节 [2000, 2499]「按字节编号」带来三个好处:① 重传时可以只重发缺失的字节区间;② 接收方能精确判断哪些字节重复(重叠区间直接丢弃);③ 乱序到达时可按序列号重新排序。初始序列号(ISN)并非从 0 开始,而是在三次握手时随机协商,以防旧连接报文串扰与序号被猜测——握手细节见上一页。
三、确认号 ACK:累积确认
接收方通过 ACK 报文回告「我收到了多少」。TCP 采用累积确认(cumulative acknowledgment):
RFC 9293:"the acknowledgment of sequence number X indicates that all octets up to but not including X have been received."
也就是说,ACK = X 表示「X 之前的字节我全收到了,下一个期待 X」。它确认的是「连续无空洞的最高位置」,而非「最近收到的那个段」。
发送方发出: seq=1000(500B), seq=1500(500B), seq=2000(500B)
接收方收齐 → 回 ACK=2500 (含义:2500 之前全收到,下一个期待 2500)
若中间段 seq=1500 丢失:
收到 1000 → ACK=1500
收到 2000(乱序,但 1500 缺)→ 仍回 ACK=1500(重复 ACK!)
收到 2500(仍缺 1500)→ 再回 ACK=1500(又一个重复 ACK)累积确认的代价:单个 ACK 丢失通常无害(后一个更高的 ACK 会覆盖它),但一旦中间有空洞,后续即便收到也只能反复确认空洞前的位置——这正是「重复 ACK」的来源,也是快速重传与 SACK 要解决的问题。
ACK 是「下一个期待」,不是「已收到的最后一个」
新手常把 ACK=2500 误读成「收到了 2500 号字节」。正确读法是「2499 及之前全收到,请从 2500 开始发」。
四、超时重传 RTO:兜底的安全网
发送方每发出一个段就启动一个重传定时器。若在 RTO(Retransmission TimeOut) 内没等到对该段的确认,就认定它丢失并重传。RTO 不能写死:太短会把只是「慢」的段误判为丢失而无谓重传,太长则丢包后恢复迟缓。
RFC 9293 明确要求按 RFC 6298 计算:"The RTO MUST be computed according to the algorithm in [RFC 6298], including Karn's algorithm for taking RTT samples."
RTT 估算公式(RFC 6298)
维护两个变量:SRTT(平滑往返时间)与 RTTVAR(往返时间偏差)。设新测得的样本为 R':
首次测得 R:
SRTT = R
RTTVAR = R / 2
后续每次测得 R':
RTTVAR = (1 - β) · RTTVAR + β · |SRTT - R'| // β = 1/4
SRTT = (1 - α) · SRTT + α · R' // α = 1/8
RTO = SRTT + max(G, K · RTTVAR) // K = 4,G 为时钟粒度约束:初始 RTO = 1 秒;算出的 RTO 不得低于 1 秒(向上取整);上限可设为 ≥ 60 秒。加入 4·RTTVAR 是为了让 RTO 随网络抖动自适应——网络越不稳,RTO 留的余量越大。
Karn 算法与指数退避
Karn 算法解决一个二义性:当一个段被重传后收到 ACK,无法判断这个 ACK 是回应原始段还是重传段,用它算 RTT 会得到错误样本。规则是:重传过的段,其 RTT 样本一律不采用。同时,每次定时器超时触发重传,RTO = RTO · 2(指数退避),避免在已经拥塞的网络上火上浇油。
五、快速重传:3 个重复 ACK 不等超时
只靠 RTO 兜底太慢——要白等一整个超时周期。**快速重传(Fast Retransmit)**利用「重复 ACK」提前发现丢包:
RFC 5681:"the arrival of 3 duplicate ACKs ... as an indication that a segment has been lost. After receiving 3 duplicate ACKs, TCP performs a retransmission of what appears to be the missing segment, without waiting for the retransmission timer to expire."
当某段丢失,其后到达的乱序段会让接收方反复回送同一个 ACK(指向空洞位置)。发送方收到第 3 个重复 ACK(即原 ACK + 3 个相同的),就立即重传那个「看起来丢了」的段,无需等 RTO。为何是「3 个」而非「1 个」?因为单个重复 ACK 也可能仅由网络轻微乱序引起;等到 3 个,丢包的概率才足够高,兼顾灵敏度与误判率。
超时重传 vs 快速重传
| 维度 | 超时重传(RTO) | 快速重传 |
|---|---|---|
| 触发条件 | 定时器超时仍未收到 ACK | 收到 3 个重复 ACK |
| 触发速度 | 慢(要等一个 RTO,至少 1 秒级) | 快(约 1 个 RTT 内即可) |
| 适用场景 | 严重丢包、连 ACK 都收不到 | 单段丢失但后续段仍能到达 |
| 角色 | 最终兜底安全网 | 常见丢包的快速恢复路径 |
| 相关 RFC | RFC 9293 / 6298 | RFC 5681 |
快速重传不等于「重传了就完事」
收到 3 个重复 ACK,除了重传丢失段,TCP 还会触发快速恢复调整拥塞窗口——那属于拥塞控制范畴,本页不展开,见下一页。
六、选择确认 SACK:只补丢的那一段
累积确认有个浪费:若发出 10 个段、只丢了第 3 个,发送方从重复 ACK 只知道「第 3 段之前没问题」,却不知道第 4~10 段其实已经到了——保守实现可能把第 3 段及之后全部重传,做了无用功。
选择确认 SACK(Selective Acknowledgment)通过 TCP 选项解决这一点(RFC 2018,扩展 RFC 2883,在 RFC 9293 中列为可选项)。握手时双方用 SACK-Permitted 选项协商启用后,接收方可在 ACK 中额外携带「已收到的不连续字节块」:
丢了 seq=1500 那一段,其余都到了:
ACK = 1500 // 累积确认仍停在空洞前
SACK 块: [2000–2499], [2500–2999], ... // 但这些不连续块我已收到
→ 发送方据此只重传 [1500–1999],已被 SACK 的块不再重发SACK 把「累积确认(连续到哪)」与「选择确认(哪些零散块已收)」结合,使重传精确到真正丢失的区间,在高时延、多段同时丢失的链路上收益显著。
七、滑动窗口:可靠传输的载体
序列号、确认、重传都需要一个「记账本」来管理「发了什么、确认到哪、还能发多少」——这就是滑动窗口。发送方视角,发送缓冲区按序列号分成四段:
|=== 已发送已确认 ===|=== 已发送未确认 ===|=== 可发送未发送 ===|=== 不可发送 ===|
↑ ↑ ↑
SND.UNA SND.NXT 窗口右边界(UNA+窗口)- 已发送已确认:ACK 已覆盖,可从缓冲区释放。
- 已发送未确认(飞行中数据,in-flight):放在重传队列里,等 ACK;超时或 3 个重复 ACK 就从这里取出重传——可靠性机制作用的核心区域。
- 可发送未发送:窗口内仍有空间,可以继续发。
- 不可发送:超出窗口右边界,须等窗口滑动(左边界因收到 ACK 而右移)才能发。
每收到一个有效 ACK,SND.UNA 右移、窗口随之「滑动」,腾出空间发送新数据——这就是「滑动」的含义。窗口的大小由 min(rwnd, cwnd) 决定(接收方通告窗口与拥塞窗口取小),但窗口的工作机制本身是可靠传输的承载结构。窗口大小的两套控制详见下一页。
八、有序交付与去重
可靠传输的「最后一公里」在接收方:
- 重排序:乱序到达的段先按序列号缓存,凑齐连续区间后再按序交付应用层——应用看到的永远是原始字节流顺序。
- 去重:重传或网络复制可能导致同一字节到达两次;接收方按序列号判断重叠区间一律丢弃,保证交付给应用的数据「不重不漏」。
- 校验和:每个段头含 16 位校验和,覆盖头部 + 数据 + 伪头部;校验失败的段被静默丢弃,等同丢包,由重传机制补救。
这三步与前面的序列号 / 确认 / 重传闭环,共同兑现了 MDN 那句承诺:数据按发送顺序、可靠且无错地交付。
小结
TCP 在不可靠的 IP 之上实现可靠传输,靠的是一套环环相扣的机制:用序列号给每个字节编号,用累积确认 ACK回告「连续收到哪」,用校验和剔除损坏段,用超时重传(RTO,按 RTT 动态估算 + Karn 算法)作兜底安全网,用快速重传(3 个重复 ACK)加速常见丢包恢复,用选择确认 SACK做到只补真正丢失的段;这一切都跑在滑动窗口这个记账结构上,最终在接收方完成重排序与去重,交付有序无重的字节流。记住一条主线:编号 → 确认 → 重传 → 排序去重,可靠性就是这四步的合奏。
至于「能发多少、何时该慢下来」——那是窗口大小的故事,属于流量控制与拥塞控制。
- 上一页:TCP 三次握手与四次挥手
- 下一页:流量控制与拥塞控制
参考:MDN · TCP、Cloudflare · TCP/IP、RFC 9293(TCP)、RFC 6298(RTO 计算)、RFC 5681(快速重传)、RFC 2018(SACK)。