前两年在线上教网络基础课,我最喜欢在开篇问学员一个问题:如果一份数据跨过几百公里送到对端,中途出了差错,你是打算让发送方重传一次,还是在数据里预先塞进足够的“备份信息”,让接收方自己把错误修回来?这其实就是在问,ARQ 和 FEC 你选哪个。搞懂这个问题,才算真正理解“可靠传输”这四个字。
这篇文章我不会只给你堆一堆协议名词,而是把这两项技术的底层逻辑、量化计算方法、常见的工程误解,以及我在实际项目里踩过的坑,一次性讲清楚。文章既适合刚接触网络协议栈的初学者,也适合做无线、音视频、底层链路开发的工程师参考。
1. 同一场“丢包事故”,两种不同的修复哲学
先说一个我常用来给新人打比方的场景:你从 A 城往 B 城寄一箱零件,运输途中颠簸了几公里,开箱一看,有 5 个零件碎了。这时候你有两种处理思路。
第一种是打电话告诉收货方:“把那 5 个碎掉的零件型号报给我,我马上补寄一份过去。”这就是 ARQ(Automatic Repeat reQuest,自动重传请求)。它依赖一条“反馈通道”,让接收方告诉发送方哪些数据丢了、错了,然后发送方针对性地重传。TCP 里的超时重传、快速重传,数据链路层的帧重传机制,本质都是这个思路。
第二种是出发之前,你就往箱子里多塞了一套备用的零件。收货方打开箱子,发现几个零件碎了,直接拿备用件替换,连电话都不用打。这就是 FEC(Forward Error Correction,前向纠错)。它的特点是“闭环不需要回来”,发送方通过冗余数据和纠错算法,让接收方在无反馈的前提下自己修复错误。DVD 光盘上的划痕能正常读出来,二维码撕了一角还能扫出来,靠的就是 FEC 在背后干活。
两种哲学的分界线很明显:
| 维度 | ARQ | FEC |
|---|---|---|
| 反馈通道 | 必须要有 | 完全不需要 |
| 冗余开销 | 出错时才重传,平时几乎零额外开销 | 不管有没有错,都要带冗余,固定开销 |
| 实时性 | 依赖 RTT,一传一收,延迟高 | 无等待期,适合实时流 |
| 纠错能力 | 只要反馈通道在,理论上可无限次重传 | 纠错能力固定,超出冗余范围就无能为力 |
| 复杂度 | 收发双方实现重传状态机 | 编解码算法复杂度高,硬件/CPU 开销大 |
正因为两者各有死穴,所以真实工程里很少只用一家。TCP 不会把 FEC 作为标准能力,无线基站则把两者焊死在一起用。理解各自边界之前,得先搞清楚它们对抗的“敌人”到底长什么样。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 先认清信道脾气:随机比特错、突发错与丢包不是一回事
很多人一上来就谈 ARQ 和 FEC,结果连“差错”本身的分类都没摸清。我见过不少现场排查的人,看到 ping 丢包就喊链路质量差,结果一查,根本不是“包丢了”,而是帧里的比特翻了个位,被校验层当成垃圾丢掉了。两者在应用层看都是“丢”,但在协议栈上完全是两码事。
2.1 随机比特错:噪声导致的“单点翻车”
随机比特错,指的是数据流里孤立的某一位从 0 变成 1。理想情况下,信道里的热噪声、白噪声会让信号幅度发生微小波动,当噪声幅度恰好跨过判决门限,接收端就把一个符号判错了。比如光模块的 OSNR 劣化、无线信道深衰落边缘附近,最容易出现这种错误。
随机错的特点是分散、孤立的,前后比特大概率是好的。对付它,单比特纠错码就够用,比如汉明码。这也是为什么很多系统的 FEC 设计首选汉明距离合适的码字。
2.2 突发错:多个比特连着坏
相比之下,突发错更棘手。脉冲干扰、电源浪涌、无线信道快衰落、光盘上的物理划痕、Wi-Fi 在某段时间里被微波炉干扰,这些场景都会导致连续多个比特一起出错。一张光盘有一道刮痕,可能连续几百个字节都读不出来;一条网线在强电磁环境下,数据帧尾部可能连着 CRC 校验全算不过。
突发错对纠错码是个大考验。如果 FEC 的码字长度就那么几十比特,一道突发错误进来,整个码字可能直接被击穿。工程上通行的手段是“交织”,把连续比特分散到不同的码字里,让一批相邻错误变成每个码字里的一两个孤立错误,再交给纠错码去收拾。
2.3 被上层“包装”出来的丢包
还有一种情况,物理层其实没丢比特,但数据帧到了链路层或网络层,CRC 校验、IP 头校验和不对,直接整包丢弃。从 TCP 的视角看,这跟“包在路上消失了”效果一样,都是序列号凭空缺了一块。
这里得分清楚两个概念:比特错是物理层的事,丢包往往是协议层主动选择的结果。ARQ 站在协议层,看到的是“包没了”;FEC 站在物理层或编码层,看到的是“比特坏了”。所以你知道真相了吗?一个包被丢弃之前,往往已经有 FEC 先尝试过修复;只有 FEC 修不动,错误帧才会被上层校验剔掉,最后轮到 ARQ 重传。这就是为什么现代系统越来越流行把两者串成一条链来用。
3. ARQ 的三种经典重传模式,以及它们在 TCP 里的真身
ARQ 不是单一技术,它有三个经典形态,从简到繁,可以用来解释 TCP 和各种私有链路协议里大量诡异行为。
3.1 停止等待 ARQ:最朴素,也最容易被 RTT 拖死
停止等待(Stop-and-Wait)策略很简单:发一帧,等确认,收到 ACK 再发下一帧;如果超时没等到,就重发当前这一帧。它实现简单,但效率极其依赖往返时间。
链路利用率可以用下面这个公式估算:
U = T_f / (T_f + 2 * T_p + T_ack)
其中 T_f 是发送一帧的时间,T_p 是单向传播时延,T_ack 是发送确认帧的时间。假设一条 1 Mbps 链路,MTU 1500 字节,也就是一帧要发 12 ms,往返时延 RTT 是 200 ms,不算 ACK 发送时间,利用率:
U = 12 / (12 + 200) ≈ 5.7%
也就是说九成多的时间都花在等 ACK 上了。卫星链路的 RTT 动辄 500 ms 以上,用停止等待就是灾难。所以实际协议很少用裸的停止等待,就算用也会加“连续发送”的窗口来对冲。
3.2 回退 N 步:出错之后的“连坐”机制
回退 N 步(Go-Back-N)允许发送方在未收到 ACK 时连续发送多个帧,同时用一个窗口限制在途帧数。接收方只按顺序接收,一旦发现某帧出错,就丢弃该帧和后续所有已到达的帧,并向发送方回复否定确认或者干脆不确认。
发送方收到 NAK 或超时后,会退回到出错帧,把窗口里所有帧重新发一遍。问题在于,出错帧后面的帧明明已经安全到达了,也要跟着陪跑重传。信道越好,这种浪费越明显;信道一出问题,吞吐量直接被打回解放前。早期的 TCP 行为其实很接近这个模型:接收端只回累积 ACK,发送端一旦超时重传,后续数据全部按未确认处理。
3.3 选择性重传:只补缺口,不连坐
选择性重传(Selective Repeat)是终极形态。接收方把乱序到达的帧先缓存起来,只通知发送方“哪个序列号缺了”,发送方只重传缺的那一段,不碰其他帧。这需要接收方维护一个大缓存,发送方维护复杂的定时器和位图,复杂度上了一个台阶,但信道利用率最漂亮。
TCP 的演进就是活生生的例子。最初的 TCP 用的累积 ACK 并不区分到底是哪个段丢了,拥塞窗口内的所有在途包都有嫌疑,轻则重传一段,重则触发超时后窗口减半,效率很差。后来业界补上了快速重传(收到 3 个重复 ACK 就立即重传)和 SACK 选项(RFC 2018),接收方把接收位图反馈给发送方,TCP 才真正具备了选择性重传的能力。直到今天,你抓 Windows、Linux 上的 TCP 包,握手阶段几乎都会看到双方声明 SACK 支持。
3.4 协议栈视角:TCP 究竟在用哪种 ARQ?
很多资料喜欢把 TCP 归类为“ARQ 的一种实现”,严格说 TCP 是 GBN 思想加 SR 扩展的混合体。正常拥塞窗口内,它连续发送;出现丢包,快速重传只回补缺口;但当拥塞导致超时,它又会保守地认为后续所有包都未确认,并大幅缩小窗口。理解这一点,对分析线上 TCP 性能非常重要——你看到的重传,不一定全是链路错包引起,也可能是拥塞窗口主动收缩后的集体重发。
4. FEC 的核心:不加一条反馈报文,怎么在接收端把数据修回去
FEC 说穿了就是“多带几个备用零件”。但怎么带、带几个、修到什么程度,背后是一整套编码理论。我不打算把矩阵运算糊你一脸,但从最简单的汉明码入手,你会明白 FEC 的整个思路。
4.1 汉明码:一个人手就能算的 FEC 示例
汉明码的经典版本是 (7,4) 汉明码:每 4 个数据比特,附加 3 个校验比特,总共发送 7 个比特。它能纠正 1 个比特错误,同时能发现 2 个比特错误。
发送端把 4 个数据位记为 d1、d2、d3、d4,构造 3 个校验位:
- p1 = d1 ⊕ d2 ⊕ d4
- p2 = d1 ⊕ d3 ⊕ d4
- p4 = d2 ⊕ d3 ⊕ d4
接收端拿到 7 个比特后,重新计算这三个校验式,和收到的校验位对比,得到一个 3 位二进制数(称为“伴随式” syndrome)。如果伴随式的计算结果正好指向某个比特位,那一位就是错的,直接翻转即可。
比如数据是 d=1011,p1=0、p2=1、p4=0,发送序列为 0110011。假设接收端收到的序列在第 6 位发生了翻转,变成 0110001,重新计算伴随式,结果会精确命中 6 这个位置。整个过程不需要发送端再发任何东西,错误当场就修好了。
这就是 FEC 的核心能力:自愈。代价是原本 4 比特数据变成了 7 比特,冗余率 75% 的对照组都达不到,实际情况当然没这么夸张。
4.2 从单比特纠正到突发错误:RS 码与交织
汉明码只适用于孤立比特错误,工程上远远不够。于是有了里德-所罗门码(Reed-Solomon,RS 码),它把数据按“符号”分组,例如每 8 比特一组,一次可以纠一组甚至多组损坏的符号。经典参数 RS(255,223) 表示每 255 个符号里,有 223 个数据符号、32 个校验符号,能够纠正最多 16 个符号错误,或者 32 个符号擦除(知道具体位置但内容丢失的情况)。
对突发错误,RS 码很有用,但码字长度有限,一道 100 比特的突发错误完全可能把一个小码字打穿。所以实际产品会把“交织”做在前面:把几十个码字的符号交错排列后再送上信道。这样一来,物理上的连续错误,经过交织、解交织后,被打散到不同的码字里,每个码字只需要承受一两个孤立错误,纠错能力就统统派上用场。
说个大家都有体感的例子:二维码。QR 码的不同纠错等级(L/M/Q/H)就对应不同数量的 RS 校验符号。H 级最多能容忍约 30% 的符号损坏,代价模块密度高、可辨识距离变小。票务、支付场景里大量二维码爱用 H 级,就是为了抗污损、抗遮挡。
4.3 现代链路里的 FEC 大军:以太网、光模块和 5G
现在的 25GE、50GE、100GE 以太网已经不是“可选项开 FEC”,而是高速链路几乎必开 RS-FEC。典型码字如 RS(544,514),符号大小 10 比特,冗余开销在 5% 左右。交换机端口协商时,两端会发现彼此的 FEC 能力,常见选项包括 RS-FEC、Fire Code(老式 25G 方案)以及关闭 FEC。你手动关闭 FEC,很多 25G 链路在长距离或劣质线缆下会直接误码率爆炸,上层 TCP 重传率起飞。
无线方向更夸张。5G NR 的物理层同时用到了 LDPC 码(用于数据信道)和 Polar 码(用于控制信道),并且配合 HARQ,形成了一套渐进冗余的可靠传输体系。这里的 FEC 不是死板的固定冗余,而是根据信道条件动态调整编码速率,这部分我放下一章细讲。
5. 现实中真正落地的是混合方案:HARQ 怎么把两者拧在一起
你可能已经意识到了,纯 ARQ 怕高延迟,纯 FEC 怕开销不稳。真正的工业级方案里,两者经常是拧在一起的。这就是混合自动重传请求(Hybrid ARQ,HARQ)。
5.1 Type-I HARQ:先纠错,纠不了再重传
第一代 HARQ 很简单:把 FEC 和 ARQ 串起来。接收端先做 FEC 解码,错误能修就修,修不了就请求重传出错的数据块。这比纯 ARQ 多用一轮“纠错机会”,比纯 FEC 多了“兜底保障”。硬件实现不复杂,很多早期无线数据系统就这么干。
5.2 Type-II HARQ:增量冗余才是王道
第二代 HARQ 更聪明,它把重传内容和首次传输内容区分开。
首次发送时,用较高的编码速率(比如只带少量冗余)发射数据。接收端发现解码失败,不会立刻要求把整个包再来一遍,而是发送一个 NAK 让发送方补发“额外的校验比特”。接收端把首次传输的软信息和新到的冗余合并在一起联合解码,相当于把 FEC 的冗余量逐步加大,直到能解出来为止。
这就是所谓“增量冗余(Incremental Redundancy)”。4G/5G 的 HARQ 正是这么干的,它和自适应调制编码(AMC)配合,调制阶数、码率随信道质量浮动,有效吞吐量比固定 FEC 高出一截。
5.3 分层视角:每一层的可靠传输技术都不完全相同
真实网络里,可靠传输不是某单层技术包办,而是分层配合的结果:
| 协议层次 | 典型技术 | 目的 |
|---|---|---|
| 应用层/传输层 | 应用层 FEC(如 RTP FEC)、TCP 重传 | 对抗端到端丢包 |
| 传输层 | TCP/QUIC 的 ACK、重传、SACK | 保证字节流完整有序 |
| 链路层 | Wi-Fi 的块 ACK、重传;以太网的 FCS | 保证单跳帧不丢 |
| 物理层/编码层 | RS-FEC、LDPC、交织 | 修正信道比特错误 |
看到没有,一头一尾各干各的:FEC 在最底层把物理错误先修掉一大半,ARQ 在最上层兜底,把 FEC 修不掉的数据再要回来。这其实是套组合拳,不是二选一。
6. 吞吐量、时延、开销的取舍:我从实际项目里总结的选型经验
很多做传输方案的朋友面对“到底上不上 FEC、重传要不要那么激进”这种问题时,容易犯迷糊。我的经验是先别谈技术流派,把以下四个指标量化一遍,答案基本就出来了。
6.1 先算重传的“回血能力”
假设你在做一条基于 UDP 私有协议的文件传输通道,往返时延 RTT = 100 ms,单包大小 1200 字节,丢包率 p = 1%,你在应用层做选择性重传。理想情况下,每传 100 个包就要重传 1 个,但重传同样可能丢,所以实际需要传输的包数约为 100 / (1 - p) ≈ 101 个。如果 RTT 是 100 ms,首轮 100 个包发完后再等 100 ms 补发 1 个包,一个完整文件下来,有效吞吐会受到明显压缩。尤其是在大文件传输场景,每个丢失的包都会引入一个额外的 RTT 等待,RTT 越大,吞吐越低。
如果这条链路的 RTT 是 500 ms,你会特别难受。单纯靠 ARQ 修,整个传输链路就像“慢动作回放”。这时候 FEC 的优势就出来了:抽 3%~5% 的带宽来承载冗余,大部分丢包直接在接收端原地修复,不需要等 RTT。
6.2 FEC 也不是越多越好
FEC 冗余比例越高,抗丢包能力越强,但代价是即使信道全部正常,你也要多付带宽。一个 10 Mbps 的视频流,如果 FEC 冗余 20%,实际占用 12 Mbps,而丢包率其实只有 0.1%,那这 2 Mbps 就纯属浪费。
我见过不止一个项目,为了追求 PSNR 指标,把 RS 冗余开到 25% 以上,结果同一条物理链路里其他业务流量被挤爆,整体体验反而更差。FEC 参数应该动态调整,和实时丢包率、带宽预算联动。这部分如果做得好,就是一套“自适应 FEC 控制器”。
6.3 一张决策表,覆盖大多数场景
下面这张表是我在架构评审时最常用的判断框架:
| 场景特征 | 倾向方案 | 理由 |
|---|---|---|
| 低延迟语音/视频直播 | FEC 为主 | 重传等不起,甚至等不到 |
| 大文件/批量数据同步 | ARQ 为主 | 实时性要求低,重传成本可控 |
| 高 RTT 链路(卫星、跨国) | FEC + HARQ | 每次重传代价太高,需要“一次到位” |
| 局域网内大量数据搬运 | ARQ 就够 | RTT 极低,重传惩罚小,FEC 浪费带宽 |
| 物理信道极差(无线移动) | 物理层 FEC + 链路层 ARQ | 两层互补,动态编码速率 |
| 高吞吐数据中心网络 | 链路层 RS-FEC | 强制开启,不上 FEC 误码率根本压不下去 |
还有一个容易忽略的维度:复杂度与功耗。RS 码和 LDPC 的编解码不是抠几个寄存器就能跑的,高端网卡选择把 RS-FEC 下沉到 PHY 芯片里,由硬件完成;如果你在 CPU 上软实现 LDPC,几十 Gbps 的吞吐会让你直接怀疑人生。选型时务必确认是“硬件卸载”还是“软件库实现”,两者不是一个量级。
7. 排障现场:怎么用工具把“重传”和“纠错”的变化抓出来
最后回到实战。前面讲了那么多理论,真正到了现场,最怕的就是“理论全懂,工具不会用”。我给你列几个我最常使的排查手段。
7.1 看链路层:网卡 FEC 计数器和 FCS 错包
很多支持 RS-FEC 的网卡会把纠错统计暴露给操作系统。在 Linux 下先看当前 FEC 状态:
bash复制ethtool --show-fec eno1
输出会显示 Auto/RS/Baser/Off 等状态。如果两边协商不一致,会出现间歇性错包,链路还起得来。接着看错包计数:
bash复制ethtool -S eno1 | grep -iE "fec|crc|fcs|rx_errors"
重点看两个数字:rx_fec_corrected(被纠错的帧数量)和 rx_fec_uncorrectable(无法纠正、被丢弃的帧数量)。如果 uncorrectable 持续增长,说明物理信号质量已经很差了,别指望上层重传补救,该查光模块衰减、线缆损耗、连接器脏污就去查。
7.2 抓包看 TCP 重传和 SACK
应用层反应迟钝的时候,我习惯直接上 Wireshark,给 TCP 加上几个显示过滤:
text复制tcp.analysis.retransmission
tcp.analysis.fast_retransmission
tcp.analysis.duplicate_ack
这三个过滤器能快速把重传、快速重传、重复 ACK 高亮出来。需要注意的是,Wireshark 显示的重传时间和真实重传时间可能因为抓包点位置不同而有偏移,别直接把时间差当成 RTT。要量化 RTT,更靠谱的是看 TCP 的 Timestamps 选项,或者直接看系统里 ss -ti 输出的 rtt 字段。
7.3 丢包率和重传率的关联判断
还有一种常见场景:ping 只丢 0.1% 的包,应用却频繁卡顿。这时别急着下结论,用 netstat -s 看 TCP 层重传段统计:
bash复制netstat -s | grep -iE "retrans|out-of-order|sack"
如果重传率远高于 ICMP 丢包率,通常怀疑两点:一是设备上有队列丢包(交换机/网卡缓冲溢出),二是中间设备有基于深度包的过滤/整形行为。这种情况和物理信道比特错关系不大,别盲目去调 FEC。
7.4 一个踩坑提醒:FEC 和交换机不匹配
最后提醒一个非常隐蔽的坑。25G/100G 链路两端如果一端开启 RS-FEC、另一端没开,或者一个用 RS-FEC、一个用 Fire Code,协商阶段可能显示“链路 up”,但物理层误码率极高。表面现象是 ARP 通、ping 能通,大流量一跑就断断续续,TCP 重传满天飞。查这类问题最快的方式,就是对比两端 ethtool --show-fec 的协商结果,务必确认两端的 FEC 模式完全一致。
我在多个现场踩完这些坑后最大的体会是:ARQ 和 FEC 从来不是判断题,而是多选题。它们一个负责“事后补救”,一个负责“未雨绸缪”。越是追求极致的可靠性,就越要懂得让它们在正确的层次上各司其职。希望这篇文章不只是让你记住了两个缩写,而是下次面对一条烂链路时,你能拿起工具,先看清错误长什么样,再决定让哪个技术上场。
