第一百一十八章:重传
问道期涉及内核源码:
一
林小源站在一条繁忙的航道上,看着无数光点在空中飞行。有些光点顺利到达对岸,有些却在半途中消失了——被风吹散,被浪花吞没。
"丢包了。"他低声说。
"当然会丢。"一个沙哑的声音从旁边传来。
林小源转头,看到一个满脸疲惫的中年人坐在一块礁石上。他的眼睛布满血丝,手中握着一个沙漏,沙漏里的沙子不断流逝。他的袍子上绣着密密麻麻的序列号,有些序列号被划掉了,旁边标注着"重传"。
"我是 tcp_retransmit_skb。"中年人说,"重传的执行者。你看到的那些消失的光点——如果发送方没有在规定时间内收到 ACK,就会让我重新发送。"
"规定时间?"
"RTO——Retransmission Timeout。"中年人翻转沙漏,"初始值 1 秒。每次超时,翻倍——2 秒、4 秒、8 秒……最大 120 秒。"
林小源看着沙漏里的沙子:"等这么久,效率不是很低?"
"所以有快重传。"中年人指向远处。
林小源看到一个年轻的哨兵站在高台上,手中举着三面红旗。每当他收到三个重复的 ACK,就立刻挥动旗帜——不等超时,立刻重传。
"三个重复 ACK 说明后面的数据包到了,中间那个丢了。"哨兵喊道,"等超时太慢,不如提前重传!"
二
"但重传有个问题。"中年人站起身,走到一块石板前,上面刻着复杂的时间线。
"什么问题?"
"你不知道哪些数据包对方收到了。"中年人指着时间线,"假设你发了 1、2、3、4 四个包,2 丢了。对方收到 1 后返回 ACK=2,收到 3 后还是返回 ACK=2(因为它期望的是 2),收到 4 后还是返回 ACK=2。你只知道 2 丢了,但不知道 3 和 4 到了没有。"
"那重传的时候……"
"没有 SACK 的话,你只能重传 2。如果 3 和 4 也丢了呢?你不知道,得等对方的 ACK 来判断。"中年人叹了口气,"这就是传统重传的低效之处。"
"SACK 呢?"
中年人的眼睛亮了起来:"SACK——选择性确认。接收方在 ACK 中告诉发送方:'我已经收到了 3 和 4,只缺 2。'这样发送方只重传 2,不用猜。"
他从怀中掏出一块光幕,上面显示着 SACK 选项的格式——一个个区间,精确地标注着已收到的数据范围。
"SACK 是信息的力量。"中年人说,"你知道得越多,做得越好。"
三
林小源注意到中年人的沙漏在不断变化——有时候沙子流得快,有时候流得慢。
"你的 RTO 在变?"
"当然。"中年人说,"RTO 不是固定的——它根据实际的往返时间动态调整。公式是 RTO = SRTT + 4 * RTTVAR。SRTT 是平滑的往返时间,RTTVAR 是往返时间的方差。"
"方差?"
"如果网络延迟稳定——每次 RTT 都差不多,方差小,RTO 就小。"中年人解释道,"如果网络抖动大——有时候 10ms,有时候 500ms,方差大,RTO 就大。RTO 必须比最大的 RTT 还大,否则会误判。"
林小源看着沙漏里忽快忽慢的沙子,忽然明白了——RTO 不是随便定的,它是网络状态的反映。一个稳定的网络,RTO 可以很小;一个抖动的网络,RTO 必须很大。
"那初始 RTO 为什么是 1 秒?"
"因为我们不知道网络是什么样的。"中年人说,"1 秒是保守估计——太小会误超时,太大会延迟重传。等有了实际的 RTT 样本,再动态调整。"
/*
* TCP 重传机制:
*
* 1. 超时重传
* 发送数据后启动定时器
* 超时未收到 ACK 则重传
* RTO (Retransmission Timeout) 动态调整
*
* 2. 快重传
* 收到 3 个重复 ACK
* 不等超时,立即重传
*
* 3. SACK (选择性确认)
* 接收方告知收到了哪些数据
* 发送方只重传丢失的数据
*
* RTO 计算:
* SRTT — 平滑的往返时间
* RTTVAR — 往返时间方差
* RTO = SRTT + 4 * RTTVAR
*/
printf("=== TCP 重传 — 丢失恢复 ===\n\n");
printf("重传机制:\n\n");
printf("1. 超时重传:\n");
printf(" 发送方:\n");
printf(" [seq=1] → [seq=2] → [seq=3]\n");
printf(" ↓ 丢失\n");
printf(" 超时后重传 [seq=2]\n\n");
printf("2. 快重传:\n");
printf(" 发送方:\n");
printf(" [seq=1] → [seq=2] → [seq=3] → [seq=4]\n");
printf(" ↓ 丢失\n");
printf(" 接收方: ACK=2, ACK=2, ACK=2 (重复)\n");
printf(" 发送方: 立即重传 [seq=2]\n\n");
printf("3. SACK:\n");
printf(" 接收方告知收到的数据范围:\n");
printf(" SACK: 3-4 (收到 3 和 4)\n");
printf(" 发送方只重传 2\n\n");
printf("--- RTO 计算 ---\n");
printf("SRTT: 平滑的往返时间\n");
printf("RTTVAR: 往返时间方差\n");
printf("RTO = SRTT + 4 * RTTVAR\n\n");
printf("--- 超时时间 ---\n");
printf("初始 RTO: 1 秒\n");
printf("最大 RTO: 120 秒\n");
printf("每次超时: RTO 翻倍\n\n");
printf("--- 重传的问题 ---\n");
printf("1. 延迟: 超时时间长\n");
printf("2. 重复: 可能重传已收到的数据\n");
printf("3. 效率: 只能重传一个包\n\n");
printf("--- 查看重传统计 ---\n");
printf("netstat -s | grep retrans\n");
printf(" segments retransmitted: 123\n");#include <stdio.h>
/*
* TCP 重传机制:
*
* 1. 超时重传
* 发送数据后启动定时器
* 超时未收到 ACK 则重传
* RTO (Retransmission Timeout) 动态调整
*
* 2. 快重传
* 收到 3 个重复 ACK
* 不等超时,立即重传
*
* 3. SACK (选择性确认)
* 接收方告知收到了哪些数据
* 发送方只重传丢失的数据
*
* RTO 计算:
* SRTT — 平滑的往返时间
* RTTVAR — 往返时间方差
* RTO = SRTT + 4 * RTTVAR
*/
int main() {
printf("=== TCP 重传 — 丢失恢复 ===\n\n");
printf("重传机制:\n\n");
printf("1. 超时重传:\n");
printf(" 发送方:\n");
printf(" [seq=1] → [seq=2] → [seq=3]\n");
printf(" ↓ 丢失\n");
printf(" 超时后重传 [seq=2]\n\n");
printf("2. 快重传:\n");
printf(" 发送方:\n");
printf(" [seq=1] → [seq=2] → [seq=3] → [seq=4]\n");
printf(" ↓ 丢失\n");
printf(" 接收方: ACK=2, ACK=2, ACK=2 (重复)\n");
printf(" 发送方: 立即重传 [seq=2]\n\n");
printf("3. SACK:\n");
printf(" 接收方告知收到的数据范围:\n");
printf(" SACK: 3-4 (收到 3 和 4)\n");
printf(" 发送方只重传 2\n\n");
printf("--- RTO 计算 ---\n");
printf("SRTT: 平滑的往返时间\n");
printf("RTTVAR: 往返时间方差\n");
printf("RTO = SRTT + 4 * RTTVAR\n\n");
printf("--- 超时时间 ---\n");
printf("初始 RTO: 1 秒\n");
printf("最大 RTO: 120 秒\n");
printf("每次超时: RTO 翻倍\n\n");
printf("--- 重传的问题 ---\n");
printf("1. 延迟: 超时时间长\n");
printf("2. 重复: 可能重传已收到的数据\n");
printf("3. 效率: 只能重传一个包\n\n");
printf("--- 查看重传统计 ---\n");
printf("netstat -s | grep retrans\n");
printf(" segments retransmitted: 123\n");
return 0;
}道藏笔记
内核启示
TCP 重传保证数据可靠到达。
重传机制:
- 超时重传 — RTO 超时后重传
- 快重传 — 收到 3 个重复 ACK 立即重传
- SACK — 选择性确认,只重传丢失的数据
RTO 计算:
RTO = SRTT + 4 * RTTVAR- 根据实际往返时间动态调整
重传是"可靠性"的保障——让丢失的数据重新到达。
重传之试
重传超时时间不是固定值,本章给出的 RTO 估算公式是什么?