Loading... # TCP RTT 测量困境与技术分析 # 一、核心问题 ## 1. 问题本质 TCP 传输算法(包括拥塞控制算法、传输加速算法等)非常依赖 RTT(Round Trip Time,往返时间)进行决策,但 RTT 的测量准确性却与网络质量正向相关。 ## 2. 核心矛盾 - 网络质量好时,不过度依赖 RTT 的精确测量 - 网络质量不好,需要精确 RTT 测量时,RTT 却测不准 这种矛盾源于 TCP 协议本身的设计缺陷,包括 Delayed ACK 机制和重传歧义问题。 # 二、RTT 测量的技术挑战 ## 1. Delayed ACK 影响 ### A. 机制说明 Delayed ACK 是 TCP 接收端的一种优化机制,接收端收到数据后不立即发送 ACK,而是等待一段时间(通常 200ms)或等到收到第二个数据段时再发送 ACK,目的是减少 ACK 数量,提高网络效率。 ### B. 测量偏差 接收端收到 2 个段后回复一个 ACK,该 ACK 包含对两个段的确认。在这种情况下: - 可用第一个段计算 srtt(用于 RTO 计算的 RTT) - 可用第二个段计算 ca_rtt(用于拥塞控制的 RTT) 原因在于: - 第一个段包含了主机 Delayed 时间 - 第二个段直接触发了 ACK,没有主机时间影响 ### C. 测量示意图 ```mermaid sequenceDiagram participant S as 发送端 participant R as 接收端 S->>R: 数据段1 (时间t1) Note over R: Delayed ACK 等待 S->>R: 数据段2 (时间t2) R->>S: ACK (确认段1和段2) Note over S: srtt ≈ ACK时间 - t1<br/>包含 Delayed 时间<br/>ca_rtt ≈ ACK时间 - t2<br/>更接近纯网络时延 ```   ## 2. 重传歧义问题 ### A. 问题描述 当网络质量不好、频繁丢包时,如果发送端无法区分 ACK 或 SACK 是针对原始数据还是重传数据,时间信息将被丢弃,不再用于 RTT 计算。 ### B. 困境加剧 - 丢包重传时,没有 Delayed ACK 影响(接收端会立即确认) - 但遇到重传歧义,无法确定 ACK 对应原始发送还是重传 - 恰恰在频繁丢包时,RTT 对拥塞控制最重要,但却测不到 ### C. 重传歧义示意图 ```mermaid graph LR A[发送数据包] --> B{是否丢失?} B -->|未丢失| C[收到 ACK] B -->|丢失| D[重传数据包] C --> E[正常计算 RTT] D --> F{能否区分原始/重传?} F -->|能区分| G[可计算 RTT] F -->|不能区分| H[丢弃时间信息] ```   # 三、Linux 的解决方案 ## 1. 双 RTT 机制 Linux 内核实现了两类 RTT,从源码注释可见设计意图: ```c struct tcp_sacktag_state { ... /* Timestamps for earliest and latest never-retransmitted segment * that was SACKed. RTO needs the earliest RTT to stay conservative, * but congestion control should still get an accurate delay signal. */ u64 first_sackt; u64 last_sackt; } ``` ### A. srtt(Smoothed RTT) - 用途:计算 RTO(重传超时时间) - 特点:偏保守,避免激进重传 - 策略:使用最早的时间戳 ### B. ca_rtt(Congestion Control RTT) - 用途:拥塞控制算法 - 特点:侧重精度,度量网络时延 - 策略:使用最新的时间戳,最小化主机影响 ## 2. Timestamp 选项兜底 TCP Timestamp 选项可用于解决重传歧义: - 每个数据包携带发送时间戳(TSval) - ACK 携带回显时间戳(TSecr) - 即使发生重传,通过时间戳仍可计算 RTT ## 3. 多种花活儿方案 为在丢包重传场景下获取 RTT,Linux 实现了多种技巧: ### A. DSACK 方案 包含 old seg 来重传,用 DSACK 明确区分,计算 RTT ### B. Probe 方案 用新数据 probe,明确对该数据的 SACK,计算 RTT ### C. 不连续数据 Probe 用不连续数据 probe(新的或旧的),明确对该数据的 SACK/ACK,计算 RTT ### D. 简单示例 若将 una ~ una + MSS 作为一个数据包传输并被标记为丢失: - 重传 una ~ una + fk(0 < fk < MSS) - 记录当前时间戳 - 保留 una + fk ~ una + MSS 为空洞 - 等待接收端回复 ACK - 若 ACK 等于 una + fk 且等于重传数据包的 end_seq - 则 now_us - skb.ts_us 为精确的 RTT # 四、新协议的设计思路 ## 1. QUIC 的解决方案 ### A. 核心思想 QUIC 将包序号(packet id)和流序号(offset)区分开: - 包序号:用于传输控制 - 流序号:只用于数据语义,与重传关联 ### B. 机制优势 - 若丢包重传,包序号变化而流序号不变 - RTT 随时可以获得,不受重传歧义影响 - 避开了 TCP 的拧巴设计 ### C. QUIC RTT 测量流程 ```mermaid graph LR A[发送数据包] --> B{是否需要重传?} B -->|否| C[收到 ACK] B -->|是| D[使用新包序号重传] C --> E[直接计算 RTT] D --> F[流序号保持不变] F --> G[收到 ACK] G --> H[根据包序号识别<br/>仍可计算 RTT] ```   ## 2. 延迟协商机制 ### A. 设计建议 为新协议增加 1bit 或 nbit 的 type 字段,明确数据包的目的: - type = 1:RTT 测量位,receiver 收到后应立即应答 - type = 0:普通数据包,按配置策略自决(如 Delay 一段时间等待反向捎带) ### B. 设计理念 这并非对 TCP 的兼容,而是对 TCP 少有优良品质的继承。Delayed ACK 并非原罪,相反它很好,问题的根源在于 TCP 没有任何机制协商 Delayed ACK or not,加上即可。 ### C. 协议僵化问题 若修改现有 TCP,利用报头里的 unused bits: - 数据中心场景:可以使用,因为环境可控 - 广域网场景:由于协议僵化问题,这种把戏不适用 # 五、技术启示 ## 1. 统计先行 传输优化,统计先行,统计未动,数据先行。所有的一切都依赖采集数据的准确性: - 有了数据,统计就有了依托 - 支撑证据才更可信 - 基于准确性高的数据进行的统计分析反而不再需要准确性 ## 2. 协议设计原则 从 TCP RTT 测量的困境可得出以下设计原则: - 避免模糊不清的语义(如重传歧义) - 提供明确的协商机制(如 Delayed ACK 的开关) - 区分不同用途的标识符(如 QUIC 的包序号和流序号) - 保持时间信息的透明性 ## 3. 实现权衡 Linux 内核的实现展示了工程上的权衡: - 双 RTT 机制:平衡保守性和精确性 - Timestamp 选项:为关键场景兜底 - 多种花活儿:在协议限制下尽量优化 *** ## 参考资料 1. [关于 TCP 的 RTT 测量 - CSDN博客](https://blog.csdn.net/dog250/article/details/144212425) 最后修改:2026 年 01 月 18 日 © 允许规范转载 赞 如果觉得我的文章对你有用,请随意赞赏