← 网络基础学习第 19 / 55 章进阶45 分钟

模块二 · 运输层

TCP 可靠传输

IP 协议什么都不保证 —— 可能丢、可能乱序、可能重复。那么 TCP 凭什么说自己"可靠"?本章讲序号、确认、超时重传这三件事,并且用本机的真实统计算出你机器上的重传率。

本章要解决的问题:IP 协议什么都不保证 —— 可能丢、可能乱序、可能重复。那么 TCP 凭什么说自己"可靠"?本章讲序号、确认、超时重传这三件事,并且用本机的真实统计算出你机器上的重传率。

本章的证据来源:历史实测记录(netstat -s 的 TCP 段统计、Get-NetTCPSetting 的重传超时参数)。机制出自 RFC 9293(TCP)。

先从一个例子理解概念

你把三份内容发出,对方收到了第一份和第三份。怎样知道中间缺了什么,又怎样补回来?先认识编号、确认和等待。

发送什么位置:序号

TCP按字节编号;序号指出这一段数据的位置,而不是简单给每个包排个第几号。连接开始时选的初始序号叫ISN。

连续收到了哪里:累计确认

累计确认报告下一个期待序号,意思是前面连续的内容已收到。后来的确认可覆盖先前确认,但不表示有业务人员阅读了数据。

等多久再补发:重传与RTO

没按预期收到确认时,发送端可能重传相关字节。用于超时恢复的定时值叫RTO(重传超时),不是所有连接都共用一个固定秒数。

补充不连续的已收范围:SACK

SACK(选择性确认)告诉发送端哪些不连续字节范围也已到达,使恢复更有依据。它报告已收内容,不是要求把剩余全部重传。

下面再用这些概念看原理、命令和结果。规范与官方资料列在本章末尾,类比只帮助理解,不代替协议规则。

TCP 要在一个不可靠的通道上做事

先把矛盾摆清楚:

保证
IP 层提供的尽力而为。可能丢包、可能乱序、可能重复、可能出错
TCP提供的可靠有序字节流;连接可能失败,不承诺故障后必达或业务已处理

TCP 无法让底层变得可靠 —— 它只能在不可靠之上重建可靠。手段就是三件事:

  1. 序号 —— 给每个字节编号,这样能发现丢和乱序
  2. 确认 —— 让对方告诉我收到了哪些
  3. 重传 —— 没被确认的,我重新发

序号:每个字节都有编号

这是 TCP 设计里最关键的一个决定:

假设你要发 3000 字节的数据:

第 1 个段:序号 1     ~ 1000
第 2 个段:序号 1001  ~ 2000
第 3 个段:序号 2001  ~ 3000

为什么按字节编号? 因为这样能精确定位"到底丢了哪几百个字节",重传时只补这一段。按字节编号使重传边界可以独立于原来的分段;其他协议按包编号也可以进行选择性恢复,不宜据此说所有包编号机制低效。

ISN不是每次固定从1开始。 规范结合时间与连接相关的安全生成函数等要求,以降低旧报文混淆和序号预测风险,而非简单随机抽一个数。原因和上一章讲 ARP 缓存类似:防止上一次连接延迟到达的报文被误认为本次连接的数据。

确认:累积确认

TCP 的确认是累积的:

ACK = 2001   的意思是:"2001 之前的每一个字节我都收到了"

累积确认有个很好的副作用:如果中间的某个 ACK 报文丢了,不要紧 —— 后面的 ACK 会把它覆盖掉。比如:

ACK 1001 → 丢了
ACK 2001 → 收到了    ← 这一条同时确认了前面 2000 个字节

代价是:如果只丢了中间一小段,接收方无法准确告诉发送方"我缺的是哪一段"。所以后来加了 SACK 机制(本章后面讲)。

超时重传:RTO 该设多长

发送方发出一个段,要等多久没收到确认才认为"丢了,重传"?这个等待时间叫 RTO(Retransmission Timeout)。

这是一个两难:

RTO 设得后果
太短明明只是网络慢了一点,你却重传了 → 浪费带宽,还可能加剧拥塞
太长真的丢了却要等很久才重传 → 传输变慢

所以 RTO 不能是固定值,必须动态算。 TCP 的做法是:

  1. 持续测量每一个连接的往返时间(RTT)
  2. 同时测量 RTT 的波动程度(有的包 10ms,有的 200ms —— 波动大的话 RTO 要更保守)
  3. 用"平均 RTT + 若干倍的波动"算出 RTO

实测:本机的 RTO 参数

示例powershell
Get-NetTCPSetting

实测结果(只列关键字段):

设置档拥塞算法InitialRtoMinRtoMaxSynRetransmissions
InternetCustomCUBIC1000 ms300 ms4
DatacenterCustomCUBIC1000 ms60 ms4
CompatNewReno1000 ms300 ms4

两个关键值:

  • InitialRto = 1000 ms —— 第一次重传前先等 1 秒。这是初始值,之后连接会根据自己的 RTT 测量动态调整。
  • MinRto = 300 ms —— 这份Internet模板报告的MinRto为300ms,不证明所有连接或其他恢复机制都等待至少300ms。这是个下限保护:如果算出来的 RTO 太小,稍微一点抖动就会触发无谓的重传。
  • 注意 DatacenterCustom 档的 MinRto 只有 60 ms —— 因为数据中心内部网络延迟极低且稳定,可以用更激进的参数。这说明 RTO 的取值是跟环境相关的。

指数退避:重传的等待时间会翻倍

如果重传之后还是没确认,TCP 不会一直用同一个 RTO,而是每次都翻倍:

第 1 次重传:等 1 秒   (InitialRto)
第 2 次重传:等 2 秒
第 3 次重传:等 4 秒
第 4 次重传:等 8 秒
……

这叫指数退避。目的很明确:如果对方真的不可达,不要一直用密集的重传把网络拖垮。

MaxSynRetransmissions = 4 的意思就是:建立连接时的 SYN 最多重传 4 次,之后放弃,connect() 返回失败。这解释了一个日常现象 —— 连一个不可达的地址,要等十几秒才报错。

实测:算出本机的真实重传率

示例powershell
netstat -s -p tcp

历史输出为Segments Sent=549576029,Segments Retransmitted=1946592。直接比值约0.354%,可作为该统计口径下的重传指标,不是测得的IP丢包概率,也没有“低于1%就健康”的通用标准。

Windows的MIB_TCPSTATS将输出段计数与重传段计数分别定义;应核对所用工具对应口径,不能说重传必然是Sent的子集。诊断时采集相关时间窗口的差值,并结合目标连接、RTT、吞吐、乱序和应用影响。Microsoft MIB_TCPSTATS

快速重传:不必等到超时

基于RTO的恢复要等相应定时器到期;具体值取决于连接、模板和版本,其他恢复机制可能更早行动。太慢了。

有没有办法更快发现丢了?有 —— 靠"重复的 ACK"。

发送方发:  段 1    段 2    段 3    段 4    段 5
接收方收到: 段 1    段 3    段 4    段 5        (段 2 丢了)

接收方每次都回:"我还在等 段 2"(因为累积确认停在段 2 之前)
  → 收到段 3 时回 ACK 2
  → 收到段 4 时回 ACK 2   ← 重复
  → 收到段 5 时回 ACK 2   ← 重复

发送方看到 3 个重复的 ACK → 不等超时,立刻重传段 2

为什么是 3 个? 因为 1~2 个重复 ACK 可能只是乱序(段 2 还在路上,后面几个先到了),不一定是丢。连续 3 个重复 ACK 强烈暗示段 2 真的丢了,这时立即重传比等超时划算得多。

这套机制叫快速重传(Fast Retransmit)。

SACK:只重传缺的那一段

SACK告诉发送端哪些字节区间已经到达。 接收端的累计ACK仍报告连续收到的边界,SACK块补充不连续的已收区间,尤其帮助一个窗口内多个丢失段的恢复。

没有SACK时,经典快速重传也可先重传推定缺失的段,接收端可以缓存乱序数据;不是必须把丢失点之后全部重发。SACK不直接“请求重传”,最终选择由发送端恢复算法决定。RFC 2018 §3–4、RFC 5681 §3.2

注册表未设置SackOpts不证明某条连接已成功协商SACK;确认需要看握手里的SACK-Permitted及之后报文。

实测:一个偏高但我没查清的数字

上面那张统计表里有一行需要单独说:

Failed Connection Attempts = 2870804        建立失败的连接
Active Opens               = 11501824       主动发起的连接

两个历史计数的比值约25%,但尚未证明它就是主动连接失败率。 Failed Connection Attempts可包含不同状态转换,需核对计数语义和统计窗口,再选择对应分母。

不能由这个累计比值推出“主动发起的连接四分之一失败”,也不能给出未经验证的健康结论。

但我没有查明原因。 有几个可能的解释,都不能确认:

可能的原因(都是推测)能否验证
本教程自己做的实验(连接保留地址、不存在的端口)量级远远不够,只占极小一部分
代理软件在后台探测大量不可达的节点有可能,但没有证据
某些应用反复重试失败的连接有可能,但没有证据
应用程序先尝试 IPv6 失败再回退 IPv4有可能(本机 IPv6 不可用,《网络层》模块实测过)

所以我把这一条标成"观察到但未解释",而不是给它编一个说法。

常见误解

误解一:"TCP 序号是给每个包编号的"

是给每个字节编号。 这样才能精确定位丢失的范围,只重传缺的那几百字节,而不是整包重来。

误解二:"TCP 重传说明网络坏了"

重传是正常机制的一部分。 底层 IP 本来就不保证送达,TCP 的重传就是在处理这件事。不能仅靠固定百分比判定健康,需结合统计口径、RTT、业务和时间趋势。 真正要担心的是重传率持续偏高 —— 那说明链路或拥塞有问题。

误解三:"RTO 是个固定值"

是动态计算的,基于每个连接实测的 RTT 和波动。历史实测记录的 InitialRto = 1000ms 只是初始值,MinRto = 300ms 是下限保护。而且这两个值还随网络类型变化(数据中心档的 MinRto 只有 60ms)。

误解四:"丢包就等超时重传"

大部分丢包靠快速重传解决,不用等超时。 收到 3 个重复 ACK 就立即重传了。超时重传是兜底手段,用于那些"连重复 ACK 都收不到"的情况(比如连续丢包、或者最后一个包丢失)。

本章小结

结论性质
TCP按字节序号、确认和恢复机制提供可靠有序字节流RFC 9293
ACK报告下一期待序号,累计确认可覆盖前面确认协议机制
RTO根据RTT和变化估计,超时退避RFC 6298
经典快速重传可先补缺失段,不要求启用SACKRFC 5681
SACK补充已经收到的区间,帮助多个丢失段恢复RFC 2018
累计计数器比值不直接等于丢包概率或主动连接失败率计数边界
健康判断需要对应连接、时间窗口和业务指标诊断方法

自测题

7 题 · 先自己答,再展开对照

  1. 为什么 TCP 的序号是给字节编号,而不是给报文段编号?
    字节编号让确认、重分段和恢复不依赖最初包边界;不能因此说所有按包编号的协议都只能无选择地重传。
  2. TCP 的初始序号为什么不用固定的 1,而是随机选?
    ISN生成需防止旧连接报文混淆并降低序号可预测性;不是固定从1开始,也不应简化成唯一的纯随机生成规则。
  3. 收到 ACK = 5001 是什么意思?如果上一个 ACK = 4001 的报文丢了,会有什么后果?
    意思是"5001 之前的每一个字节我都收到了"(累积确认)。如果 ACK = 4001 那条报文丢了,不会造成问题 —— 因为 ACK = 5001 已经隐含确认了前面所有字节,后面的确认会覆盖前面的。这正是累积确认的好处。
  4. RTO 设得太短或太长分别有什么问题?为什么它必须是动态的?
    太短:网络只是稍微抖动一下就触发重传,白白浪费带宽,还可能加剧拥塞。太长:真的丢包时要等很久才重传,传输变慢。
    必须动态的原因:不同链路、不同时刻的 RTT 差别很大(局域网 1ms、跨国 200ms),而且波动程度也不同。固定值不可能在所有情况下都合适,所以要根据实测的 RTT 和它的波动动态算。历史实测记录的 InitialRto=1000ms、MinRto=300ms 就是这套机制的参数。
  5. 你在本机执行 netstat -s -p tcp,看到 Segments Sent = 1000000、Segments Retransmitted = 25000。重传率是多少?这个网络健康吗?
    该计数比值为2.5%。须核对Sent是否包含重传、统计窗口和连接范围;它不是通用健康阈值,不能单独确认拥塞或链路损坏。
  6. 接收方连续回了 3 个相同的 ACK。发送方会怎么做?为什么要等到 3 个而不是 1 个?
    发送方会立即重传那个被跳过的段,不等 RTO 超时 —— 这叫快速重传。等到 3 个是因为 1~2 个重复 ACK 可能只是乱序(丢失的那个段还在路上,后面的先到了),这时重传是多余的。连续 3 个重复 ACK 强烈暗示真的丢了,此时立即重传的收益远大于误判的代价。
  7. SACK 解决了什么问题?
    SACK报告非连续的已收字节区间,提高发送端恢复决策的信息量。无SACK也能快速重传缺失段,不是必须重发丢失点之后的全部数据。

参考资料与适用范围

修订核对日期:2026-10-13。规则按所列协议与实现范围理解;文中的原始命令输出是历史观察,不表示本次重新实测。跨版本、代理/隧道及不同捕获位置需要另行核对。

正文源自 m2-04-TCP可靠传输.md · 共 13 节 · 7 道自测题