TCP与UDP在加速中的取舍

list 文章目录

TCP和UDP的对比通常被简化为一句话:TCP可靠,UDP不可靠。

从协议角度看这句话没毛病,但它盖住了一个工程上的关键问题——”可靠”与”不可靠”并非谁优谁劣,而是两种设计取舍。TCP选可靠,代价是阻塞与延迟;UDP选不可靠,换来即时性和控制权。放到加速场景里,两条路通向完全不同的优化方向:TCP加速重在缓解可靠性的延迟代价,UDP加速则是在不可靠的底线上有选择地补偿丢包。

两边的加速手段不能混用:把TCP那套逻辑搬到UDP上会伤实时性,把UDP那套搬到TCP上又解决不了窗口增长受限的问题。

TCP的代价:顺序交付与队头阻塞

TCP向应用层提供一个保证:数据按发送顺序到达,不丢、不重、不乱。为了维持这个保证,TCP在协议栈内部做了几件事。

接收端维护一个重排序缓冲区。如果数据包到达的顺序与发送顺序不一致——这在IP网络中很常见,因为不同数据包可能走不同路径——接收端必须等待缺失的数据包到达后才能将后续数据交给应用层。这就是队头阻塞(Head-of-Line Blocking):一个丢失的数据包阻塞了后面所有已到达数据包的交付。

队头阻塞在长RTT链路上的影响尤其严重。假设RTT为200ms,一个数据包丢失,接收端需要至少200ms才能收到重传。在这200ms内,所有后续到达的数据包都被缓冲区扣留。应用层看到的不是“200ms后数据恢复”,而是“200ms的静默,然后所有数据一起涌出”。对于网页加载,这表现为某个资源卡住后突然完成。对于实时应用,这是不可接受的——200ms前的游戏状态已经没有意义。

TCP的第二个代价是拥塞控制的保守性。慢启动阶段窗口指数增长,看起来很快,但当窗口接近链路容量时,TCP会主动制造丢包来探测上限(在基于丢包的拥塞控制算法中),然后削减窗口重新增长。这个过程在短RTT链路上很快,在长RTT链路上意味着每次丢包后需要多个RTT才能恢复到满速。

加速器如何介入TCP

TCP加速器的核心逻辑是:把TCP的代价隔离在短RTT段内。

当加速器将一条端到端的TCP连接拆分为两段时,每一段的RTT都显著缩短。用户到加速节点的RTT可能只有直连的1/3到1/4。队头阻塞仍然会发生,但它的持续时间与RTT成正比——RTT越短,阻塞解除越快。拥塞控制的窗口恢复同样加速。一个在200ms RTT链路上需要2秒才能完成的窗口恢复过程,在50ms RTT链路上只需要0.5秒。

但拆分引入了一个新的问题:两段TCP连接各自独立,它们的拥塞控制互不知情。用户到加速节点这一段可能带宽充足,窗口快速增长。加速节点到目标这一段可能面临拥塞,窗口被压制。数据从用户端高速涌入加速节点,但在加速节点的发送缓冲区中堆积。如果缓冲区的数据量超过了加速节点到目标链路的带宽延迟积,额外的排队延迟就在加速节点内部产生了。

这意味着,加速器的有效运作要求节点的缓冲区管理策略与两段链路的带宽差异匹配。如果入口链路远快于出口链路(比如用户是千兆宽带,出口链路因为跨洋而只有几十Mbps的有效吞吐量),节点必须主动向用户端施加反压——通过调整TCP窗口或延迟ACK来降低入口段的发送速率。不做这个优化的加速器,会在节点内部制造自己的拥塞点。

UDP的设计:不保证,不阻塞

UDP向应用层提供的是一个完全不同的契约:数据报尽力交付,不保证到达,不保证顺序。如果数据报丢失,UDP不重传。如果数据报乱序,UDP不重排。应用层收到什么就是什么,UDP不替应用做任何决定。

这个“不替应用做决定”的设计,恰恰是很多实时应用选择UDP的原因。游戏、VoIP、实时视频——这些应用对时效性的要求高于对完整性的要求。一个语音数据包如果丢失,应用宁可听到短暂的静音,也不愿意等待200ms后重传的过时音频被插入到当前时刻。一个游戏的位置更新如果丢失,下一个更新(如果频率足够高)会在几毫秒内覆盖它。

UDP把可靠性决策权交给了应用层。应用可以自己实现选择性重传(只重传关键数据包),可以实现FEC(用冗余换丢包恢复),也可以什么都不做(容忍丢包)。这种灵活性是UDP在实时场景中的核心价值。

但UDP在公网上有一个结构性的劣势:网络中间设备对它的态度。

当拥塞发生时,路由器的队列管理策略通常对TCP更友好。一些主动队列管理算法(如WRED)会有选择地丢弃UDP数据包,因为它们不会像TCP那样响应拥塞信号并降低速率。一个不减速的UDP流在拥塞的链路上被视为“不合作的流量”,被优先标记或丢弃。这就是为什么在网络高峰时段,UDP流量的丢包率通常高于TCP流量——不是因为UDP本身更脆弱,而是因为网络设备的策略在设计上偏向于保护TCP的公平性。

加速器对UDP能做什么

加速器的路由选择

UDP加速不需要拆分连接,因为UDP没有连接。UDP加速不需要管理拥塞窗口,因为UDP没有窗口。那么加速器在做什么?

路径选择仍然是第一位的。如果加速器能将UDP数据包从一条丢包率2%的公共路径迁移到一条丢包率0.2%的私有骨干网路径,加速效果直接体现在应用层的丢包减少上。这部分逻辑和TCP加速相同,但对UDP的收益往往更明显——因为UDP对丢包没有自愈能力,丢一个就是一个。

在这条更优的路径之上,加速器可以在两端节点之间增加FEC。发送端在连续N个数据包后附加M个冗余包,使得接收端在N+M个包中任意丢失不超过M个时都能完整恢复。FEC不消除丢包——物理链路上的数据包仍然在丢失——但它让应用层感知到的丢包率降到零。

FEC的代价是带宽开销和编码延迟。发送端需要积累足够的数据包才能生成冗余包,这个积累时间对于高频小包的游戏流量(比如每秒60个更新包)影响较小(积累4个包只需要67ms),但对于低频流量可能需要等待更久。这个积累延迟与编码算法的计算延迟叠加,构成了加速器引入的额外处理延迟。

在工程实践中,经常观察到一个权衡:开启FEC后,游戏内显示的丢包率下降,但基础ping值增加了3-5ms。对大多数玩家来说,丢包率从1%降到0带来的体验改善远大于ping值增加5ms的代价。但对于对延迟极度敏感的场景(比如职业电竞),这个权衡需要被明确意识到。

误判:QUIC与”UDP加速”

QUIC是一个基于UDP的传输协议,它在应用层实现了类似TCP的可靠传输和拥塞控制。很多加速服务声称支持”UDP加速”,但实际测试会发现它们对QUIC的处理并不一致。

一些加速器将QUIC流量当作普通UDP处理——单纯转发,不做FEC,不做路径优化。这是合理的,因为QUIC已经内置了拥塞控制和丢包恢复,外部的FEC可能干扰QUIC自身的速率调节逻辑。但如果加速器对QUIC什么都不做,用户看到的效果就和直连没有区别(除了路径可能不同)。

另一些加速器会尝试对QUIC流量也做TCP式的分段中继——终结QUIC连接,在内部用自定义协议传输,到出口节点再重建QUIC连接。这种做法等于破坏了QUIC的端到端加密和认证模型,通常需要客户端安装根证书才能中间人解密。对用户来说,带来的安全风险可能超过加速收益。

一个更容易混淆的情况是,加速器宣称”UDP加速”,但实际只优化了特定端口的UDP流量(比如常见的游戏端口),而对QUIC使用的443端口的UDP流量走的是另一套逻辑(甚至可能被降级为直连)。用户在测速时看到UDP加速有效,但在使用QUIC的应用(比如基于HTTP/3的网站、某些视频流)时感受不到改善,原因就在这里——两种UDP流量经过了不同的处理管道。

bash
# 检查某个应用使用的UDP端口和协议
# QUIC通常使用UDP 443
ss -tunlp | grep 应用进程名

# 抓包观察UDP流量的行为模式
# QUIC的包通常较大,且有TLS握手的特征
tcpdump -i eth0 -n udp port 443 -c 50

协议的边界与选择

TCP和UDP在加速中的差异,归根结底来自它们对应用层的承诺不同。TCP承诺可靠、有序,因此加速器必须用分段的方式隔离这些承诺带来的延迟代价。UDP不承诺任何事,因此加速器的操作空间更大,但也更受限——它不能假设数据包的语义,不能重排,不能随意引入延迟,只能在路径优化和轻量冗余的范围内操作。

这个差异也决定了什么样的应用适合什么样的加速手段。网页和API调用依赖TCP,加速的重点是降低握手延迟和窗口增长时间。游戏和实时通信依赖UDP,加速的重点是降低丢包率和路径抖动。如果把针对TCP设计的加速方案套用到UDP流量上——比如强制分段中继——结果要么破坏了UDP的实时性,要么因为协议不匹配而导致连接失败。

反过来,如果试图用UDP的”尽力而为”逻辑去加速TCP——比如只做路径转发不做连接分段——那等于放弃了所有TCP层面的优化可能,退化为一个纯粹的VPN。

理解两个协议的底层设计选择,不是为了记住它们的特性列表,而是为了在看到一个加速方案时,能够判断它的核心逻辑是围绕哪个协议构建的,以及这个逻辑在多大程度上匹配你要加速的流量类型。两者不可互换,但大多数用户并不知道自己在用哪个协议。在判断之前先搞清楚自己流量的协议构成,比直接比较加速器的延迟数字更有价值。

作者

狗急加速器技术团队