1. 超时与重传概述:为什么需要超时与重传?

网络不可靠的本质

说实话,我刚入行那会儿,总觉得网络应该挺靠谱的。数据发出去,对方就能收到——这不是天经地义的事吗?直到我第一次在生产环境排查一个“数据丢了”的Bug,才真正被现实上了一课。

网络为什么不可靠?我总结了几点:

  • 物理层不稳定:网线松动、电磁干扰、信号衰减,这些都会导致比特翻转或丢包。我在机房见过一根被老鼠咬了一半的网线,偶尔通偶尔断,排查了整整两天。
  • 缓冲区溢出:路由器、交换机的队列是有限的。流量一拥而上,队列满了怎么办?直接丢包。说白了,网络设备不会等你,满了就扔。
  • 路由变化:数据包走的路径不是固定的。今天走A路径,明天可能走B路径。路径变了,延迟和丢包率也跟着变。
  • 拥塞控制:TCP自己会主动丢包。你没看错,当网络拥塞时,TCP会故意丢一些包来告诉发送端“慢点发”。

核心观点:网络不可靠不是“偶尔发生”,而是“必然发生”。你设计的系统,必须假设任何一次传输都可能失败。

可靠传输的基石:确认与重传

那怎么办?总不能因为网络不可靠就不传数据了吧。其实思路很简单——发出去的东西,得确认对方收到了。没收到就再发一次。

这就是可靠传输的两个基本动作:

  1. 确认(ACK):接收方收到数据后,告诉发送方“我收到了”。
  2. 重传(Retransmission):发送方在一定时间内没收到确认,就重新发送数据。

你想想看,这就像你给人发。如果对方没回,你会等一会儿再问一次“收到了吗?”——超时重传就是这个道理。

超时与重传的关系

超时和重传是绑在一起的。没有超时,重传就不知道什么时候该触发。没有重传,超时就是个没用的定时器。

我习惯用一个简单的状态机来理解:

发送数据 → 启动定时器 → 等待ACK
    ↓                    ↓
 收到ACK              定时器超时
    ↓                    ↓
 取消定时器           重传数据
    ↓                    ↓
  完成                重新启动定时器

嗯,这里要注意:超时时间设多长是个大学问。设短了,容易误判丢包,造成大量不必要的重传。设长了,真丢包时恢复太慢,用户体验极差。

我的经验:超时时间不能是固定的。网络状况随时在变,RTT(往返时间)也在变。我建议用动态算法来计算超时,比如TCP用的RTO(Retransmission TimeOut)估算。后面章节我会详细讲。

一个简单的例子

假设你在写一个文件传输工具。客户端向服务端发送一个1KB的数据块:

客户端 → 服务端: [数据块 #1]
客户端: 启动定时器,等待5秒

场景A(正常):
服务端 → 客户端: ACK #1
客户端: 取消定时器,发送数据块 #2

场景B(丢包):
(5秒后,定时器超时)
客户端: 重传数据块 #1
客户端: 重新启动定时器,等待5秒

你看,就这么简单。但实际生产环境远比这个复杂——ACK本身也可能丢,数据可能延迟到达,重传的数据和原始数据可能同时到达……这些坑,我后面都会一一讲到。

知识体系总览

为了让你对整个章节有个全局认识,我画了一张图:

超时与重传知识体系 可靠传输 网络不可靠 确认机制 重传机制 物理层干扰 缓冲区溢出 路由变化 拥塞丢包 ACK / NACK 累积确认 / 选择确认 超时重传 快速重传 核心思想 网络不可靠是常态 → 确认 + 重传 = 可靠

避坑指南

我曾经踩过的坑:有一次我写一个UDP之上的可靠传输协议,把超时时间设成了固定值1秒。结果在跨洋链路上,RTT经常超过2秒。客户端疯狂重传,服务端收到大量重复数据,最终导致内存溢出。后来我改成动态RTO估算,问题才解决。

教训:永远不要用固定超时。网络是动态的,你的超时策略也必须是动态的。

小结

超时与重传,说白了就是给不可靠的网络加了一层“保险”。确认机制告诉你“到了没”,超时机制告诉你“啥时候再发”,重传机制负责“再发一次”。这三者缺一不可。

我个人习惯在设计任何网络协议时,先把这三个问题想清楚:

  • 超时时间怎么算?
  • 重传策略是什么?
  • 重复数据怎么处理?

想清楚了,再动手写代码。后面几章,我会逐一拆解这些细节。