可靠传输
- 可靠传输
-
- 确认应答机制
- 超时重传机制
-
- RTO的基本原理
- [RTO 的动态调整与指数退避](#RTO 的动态调整与指数退避)
- RTO带来的重复数据与去重机制
可靠传输
确认应答机制
为什么需要序号?
TCP 将应用层的数据流切分成多个段(Segment)。每个段携带的载荷(Payload)都是一段连续的字节。为了让接收方能够正确重组数据,TCP 为每个字节 都分配了一个唯一的序号(Sequence Number)。段头中的 32 位序号字段 填写的是该段载荷第一个字节的序号。
| 字段 | 大小 | 说明 |
|---|---|---|
| 序号 | 4 字节 | 本报文段数据的第一个字节的编号 |
| 确认号 | 4 字节 | 期望收到对方下一个报文段的第一个字节序号 |
例如,第一个段携带字节 1~1000,它的序号就是 1;第二个段携带字节 1001~2000,序号就是 1001。序号在整个连接生命周期中单调递增(超出 32 位范围后回绕)。
注意:TCP 报头本身不参与编号,序号和确认序号都是针对载荷数据的。
确认序号的含义
当接收方收到数据后,会发送一个确认应答段(ACK),其中的确认序号 填写的是收到的数据载荷的最后一个字节序号 + 1。
例如:主机 A 发送了序号为 1、长度为 1000 字节的数据(字节 1~1000)。主机 B 收到后,确认序号应为 1000 + 1 = 1001。
这个确认序号向发送方传达了双重含义:
- 所有序号小于 1001 的数据都已经确认收到了(即字节 1~1000 已安全抵达);
- 接下来请你从序号 1001 开始发送数据。
主机B(接收方) 主机A(发送方) 主机B(接收方) 主机A(发送方) 收到 1~1000 收到 ACK,知道 1~1000 已确认 下次从 1001 开始发送 数据段(序号=1,载荷 1~1000) 确认应答(确认序号=1001)
处理后发先至与数据排序
由于 IP 网络的路由具有不确定性,同一个 TCP 连接上的不同段可能 后发先至(先发出的段反而晚到)。如果 TCP 不处理,应用层可能读到乱序的数据。
TCP 的解决方案是在接收方内核中维护一个接收缓冲区:
- 所有通过网卡收到的 TCP 段,先存入接收缓冲区;
- 接收方根据序号对缓冲区中的段进行排序,乱序到达的段会被重新排列成正确的顺序;
- 应用程序调用
read()时,从缓冲区中按顺序读取数据。
因此,使用 TCP 的开发者完全不必担心数据顺序问题------TCP 已经默默处理了所有乱序。
接收方
网络
发送方
write
段2先到
段1后到
按序号排序
read
应用数据流
拆分为段
序号1,2,3...
接收缓冲区
有序队列
应用程序得到
正确顺序的数据
接收缓冲区:生产者消费者模型
接收缓冲区不仅用于排序,还起到了流量控制的作用。它可以看作一个典型的生产者-消费者模型:
- 生产者:网卡驱动从网络中读取 TCP 段,放入缓冲区;
- 消费者 :应用程序调用
read()从缓冲区取走数据。
如果应用程序读取速度慢于数据到达速度,缓冲区会逐渐填满,此时接收方会通过窗口大小字段告知发送方暂停或减慢发送,防止数据被丢弃。
网卡接收
read系统调用
处理完数据
网络到达的TCP段
内核接收缓冲区
应用程序
超时重传机制
RTO的基本原理
发送方每发送一个数据段,都会启动一个重传定时器 ,超时时间记为 RTO (Retransmission Timeout)。如果在 RTO 内没有收到对应的 ACK,发送方就认为该段已丢失,于是重新发送该数据段。
丢包可能发生在两种场景:
- 发送的数据段本身丢失(例如路由器丢弃);
- 接收方返回的 ACK 丢失(发送方同样无法收到确认,误以为数据丢失)。
无论哪种情况,超时重传都能促使发送方重发数据,保证最终抵达。
接收方 发送方 接收方 发送方 启动重传定时器 RTO 定时器超时,未收到 ACK 重启定时器(RTO 可能翻倍) loop [每次超时且未达上限] 收到确认,停止定时器,传输完成 发送数据段 (seq=1...1000) 重传数据段 (seq=1...1000) ACK (ack=1001)
RTO 的动态调整与指数退避
RTO 不是固定值,而是根据网络往返时间(RTT)动态计算出来的。简单说,TCP 会持续测量每个报文段的 RTT,使用平滑的 SRTT 和偏差 RTTVAR 计算:
text
RTO = SRTT + 4 × RTTVAR
初始 RTO 通常在 200ms~1s 之间。
超时重传发生时,TCP 会认为网络可能已经拥塞,继续用相同间隔重传只会加重拥塞。
于是采用指数退避 :每次超时后将 RTO 翻倍,直到收到 ACK 或放弃连接。
收到ACK
超时
否
是
发送数据段, 启动定时器
RTO = 初始值
等待ACK
完成
重传数据段
重传次数
已达上限?
RTO = RTO * 2
重启定时器
放弃传输, 断开连接
TCP 不会无限重传。通常有两个条件之一满足即放弃:
- 重传次数达到上限(Linux 中 tcp_retries2 默认 15);
- 累计超时等待时间超过绝对上限(通常几分钟)。
一旦放弃,TCP 会断开连接并通知应用程序(例如返回"连接超时"错误)。
RTO带来的重复数据与去重机制
当 ACK 丢失 时,发送方会误认为数据段丢失而重传,但数据实际上已被接收方正确收到,于是接收方会第二次收到完全相同的数据段。
接收方 发送方 接收方 发送方 正确收到,放入接收缓冲区 定时器超时,未收到 ACK 认为数据段丢失 发现重复! 检查缓冲区已有 1...1000 收到 ACK,停止重传 数据段 (seq=1...1000) ACK (ack=1001) ❌ 丢失 重传数据段 (seq=1...1000) 丢弃重复数据段 再次发送 ACK (ack=1001)
如果 TCP 不处理,应用程序会读到两次相同的数据。对于扣款、文件写入等操作,这会造成严重错误。因此 TCP 必须在接收方进行去重。
接收缓冲区如何实现去重?
接收方通过接收缓冲区 + 序号检查做到完美去重:
-
每个 TCP 段都有一个起始序号 和长度。
-
接收方内核维护一个已收到的连续字节流的缓冲区。
-
当收到新的 TCP 段时,检查其序号区间是否已经存在于缓冲区:
- 已存在 → 直接丢弃,不通知应用程序;
- 不存在 → 插入缓冲区合适位置,可能触发乱序重组。
已存在
不存在
收到 TCP 段
序号 = Seq, 长度 = Len
在接收缓冲区中
查找序号区间 Seq, Seq+Len-1
丢弃该段, 不通知应用
插入缓冲区适当位置
可能触发排序/合并
发送 ACK 确认
应用程序后续读取到
唯一且有序的数据
注意 :即使重复段被丢弃,接收方也必须再次发送 ACK,否则发送方可能继续无意义的重传。