为什么最终选TQUIC:T-Box的QUIC库选型的约束过滤与源码验证

0 阅读17分钟

"为什么选 TQUIC?XQUIC 是阿里的,也有 MPQUIC 和 FEC,而且是 C 实现,不是更容易集成吗?"

架构师的这个问题,比"为什么不用 quiche"更难回答。

quiche 没有 MPQUIC,一句话就能排除。但 XQUIC 确实有 MPQUIC,确实有 FEC,确实是 C 实现——这些都是实打实的优点。那次评审,我们花了二十分钟才把这个问题解释清楚,核心在于一个被大多数对比文章忽略的区别:XQUIC 的"冗余"是被动重注入,满足条件才触发;TQUIC 的冗余是主动调度,每个数据包发出时立刻复制到所有路径。 对于云控控制信号的 P99 延迟 SLA,这个区别在边界情况下决定了能不能达标。

这篇文章就是那二十分钟的文字版。


第一章:约束优先,而不是功能对比

1.1 五个硬约束

T-Box 控制信号的选库不是"哪个功能更多"的问题,而是"哪个能过门槛"的问题。五个约束:

约束 1(硬门槛):必须支持 MPQUIC 主动冗余发送。这是前几篇文章推导出的结论——控制信号需要双路同发,不支持的直接排除。注意这里强调"主动冗余",被动重注入不等于主动冗余(第五章详细展开)。

约束 2:必须能在 ARM aarch64 上交叉编译。T-Box 处理器是 ARM Cortex-A55,开发机是 x86_64 Linux。

约束 3:必须有可用的 C/C++ API。T-Box 现有代码库是 C/C++ 混合,Rust crate 意味着重写或复杂 FFI。

约束 4:内存占用可控。单连接稳定状态不超过几十 MB。

约束 5(软约束):库需要持续维护。安全补丁和协议更新不能断。

先确认平台信息:

# 在 T-Box 上确认处理器架构
uname -m
# 输出: aarch64

cat /proc/cpuinfo | grep "model name" | head -1
# 输出示例: ARMv8 Processor rev 4 (v8l)

free -h
# 典型输出:total 约 1GB,available 约 600-800MB

1.2 候选库清单

主流 QUIC 实现,截至 2025 年初:

  • TQUIC(腾讯,Rust 实现)
  • XQUIC(阿里巴巴,C 实现)
  • quiche(Cloudflare,Rust 实现)
  • msquic(Microsoft,C 实现)
  • ngtcp2(nghttp2 组织,C 实现)
  • picoquic(Private Octopus,C 实现)
  • mp-quic(UCLouvain,Go 实现)
  • mvfst(Meta,C++ 实现)

逐层过滤。

fig01.png


第二章:第一层过滤——MPQUIC 支持是硬门槛

2.1 逐一核查

quiche(Cloudflare)

quiche 是使用最广的 QUIC 库之一,但 MPQUIC?

git clone --depth=1 https://github.com/cloudflare/quiche.git /tmp/quiche_check
cd /tmp/quiche_check
git log --oneline --all | grep -i "multipath\|mpquic" | wc -l
# 输出:0

commit history 里没有 MPQUIC 相关提交。issue 列表有用户请求,官方回应是"在计划中但没有时间表"。

结论:淘汰。

msquic(Microsoft)

Windows 优先,Linux/ARM 支持有限,无 MPQUIC 实现。

结论:淘汰。

ngtcp2

QUIC v1 RFC 参考实现,协议标准覆盖最全,C 实现,交叉编译简单。但无内置 MPQUIC,路径管理需自行实现。在 ngtcp2 上实现 MPQUIC 冗余发送,估算工程时间:PoC 版本 3-4 个月,稳定版本 6-12 个月,且需要持续跟踪草案更新。

结论:技术可行,时间成本不可接受,暂缓。

mvfst(Meta)

mvfst 有 QuicPathManager,支持多路径的探测(PATH_CHALLENGE/PATH_RESPONSE)和切换(switchCurrentPath),但这是连接迁移语义——同一时刻只有一条活跃路径,不是 draft-ietf-quic-multipath 定义的多路径并发传输。源码里没有 ACK_MP 帧、没有多路径调度器、没有冗余发送。另外 mvfst 与 Meta 的 folly 框架紧耦合,无稳定 C ABI,ARM 交叉编译文档极少。

结论:连接迁移有,MPQUIC 并发传输无,加上 folly 耦合问题,工程代价过高,暂缓。

mp-quic(UCLouvain)

早期草案实现(基于 quic-go fork),不活跃,不适合生产环境。

结论:淘汰。

picoquic

有 MPQUIC 最新 IETF 草案实现,C 实现,由 QUIC 协议核心贡献者维护。但没有主动冗余发送调度器——它有 preemptive_repeat 机制,但仅针对已发送 FIN 的流(第五章详细说明为什么这不够用)。

结论:MPQUIC 有,但主动冗余发送无,暂留作备选。

XQUIC(阿里巴巴)

  • MPQUIC:支持 draft-05/06 ✅
  • FEC:XOR/Reed-Solomon 前向纠错 ✅
  • 调度器:MinRTT / Backup / Backup+FEC ✅
  • C 实现,弱网优化 ✅

通过第一层过滤。但"冗余发送"的具体实现方式需要深入看(第五章)。

结论:通过第一层,待第五章深入验证。

TQUIC(腾讯)

  • MPQUIC:支持最新 IETF 草案(develop 分支)✅
  • 冗余发送:内置 RedundantScheduler
  • 调度器:MinRTT / RoundRobin / Redundant ✅
  • 拥塞控制:CUBIC / BBR / BBRv3 / COPA ✅

需要注意:TQUIC 的 Multipath 功能在 develop 分支,源码注释明确标注 The API of MultipathScheduler is not stable and may change in future versions。这意味着选用时需要接受 API 不稳定的风险,并锁定特定版本。

git clone --depth=1 https://github.com/Tencent/tquic.git /tmp/tquic_check
cd /tmp/tquic_check
grep -r "RedundantScheduler\|redundant" --include="*.rs" src/ -l
# 输出:包含 scheduler_redundant.rs 等文件

# 确认 API 稳定性说明
grep "not stable" src/multipath_scheduler/multipath_scheduler.rs
# 输出:Note: The API of MultipathScheduler is not stable and may change in future versions.

结论:通过第一层,进入下一轮。注意 MPQUIC API 处于非稳定状态。

2.2 第一轮结果

通过 MPQUIC 硬门槛的:TQUICXQUIC(加 picoquic 作备选)。


第三章:第二层过滤——ARM 交叉编译的实际踩坑

3.1 为什么交叉编译是真正的门槛

"交叉编译"听起来是小问题,但在实践中往往是嵌入式平台能不能真正用一个库的分水岭。XQUIC 是 C 实现,cmake 交叉编译相对直接;TQUIC 是 Rust 实现,有三个坑需要踩过去。

fig02.png

3.2 踩坑一:Rust linker 配置

错误现象

error: linker `cc` not found
  |
  = note: io error: No such file or directory (os error 2)

或者更迷惑:编译通过了,但在 ARM 设备上运行报 exec format error(x86 二进制误当 ARM 用)。

诊断

cargo build --target aarch64-unknown-linux-gnu -v 2>&1 | grep "running.*linker"
# 如果输出里是 cc 而不是 aarch64-linux-gnu-gcc,就是 linker 配置问题

修复:在 .cargo/config.toml 里指定 ARM linker:

[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"

验证

ar -x target/aarch64-unknown-linux-gnu/release/libtquic.a
file *.o | head -3
# 输出应包含: ELF 64-bit LSB relocatable, ARM aarch64

3.3 踩坑二:BoringSSL 交叉编译

TQUIC 依赖 BoringSSL(通过 boring crate),boring 会自动触发 BoringSSL 的 cmake 构建,cmake 默认不做交叉编译。

错误现象

/usr/bin/ld: cannot find -lstdc++

cmake 找不到 aarch64 的 C++ 标准库。

修复:设置环境变量告诉 boring crate 使用交叉编译工具链:

export CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc
export CXX_aarch64_unknown_linux_gnu=aarch64-linux-gnu-g++
export AR_aarch64_unknown_linux_gnu=aarch64-linux-gnu-ar
export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc

cargo build --release --target aarch64-unknown-linux-gnu

3.4 踩坑三:C API 链接顺序

TQUIC 提供 C API(tquic.h),从 C 项目链接 libtquic.a 时,GCC/clang 的链接器是单趟扫描的,被依赖的库必须放在依赖方后面。

错误现象

undefined reference to `tquic_connection_new'
undefined reference to `tquic_endpoint_send'

诊断

nm libtquic.a | grep " T tquic_" | head -20
# 如果符号在这里,是链接顺序问题,不是缺符号

修复

# 正确顺序:TQUIC 静态库最先列出
gcc -o myapp main.o \
  -ltquic \
  -lm \
  -ldl \
  -lpthread \
  -lssl -lcrypto

3.5 完整交叉编译验证流程

# 步骤 1:安装交叉编译工具链
sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu \
                         binutils-aarch64-linux-gnu

# 步骤 2:安装 Rust 交叉编译目标
rustup target add aarch64-unknown-linux-gnu

# 步骤 3:配置 linker
mkdir -p .cargo
cat > .cargo/config.toml << 'EOF'
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"
EOF

# 步骤 4:设置 BoringSSL 环境变量
export CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc
export CXX_aarch64_unknown_linux_gnu=aarch64-linux-gnu-g++
export AR_aarch64_unknown_linux_gnu=aarch64-linux-gnu-ar
export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc

# 步骤 5:编译并验证
cargo build --release --target aarch64-unknown-linux-gnu
nm target/aarch64-unknown-linux-gnu/release/libtquic.a \
  | grep " T tquic_config\|T tquic_endpoint\|T tquic_connection_" \
  | head -10

3.6 XQUIC 的交叉编译

XQUIC 是 C 实现,cmake 交叉编译相对直接:

cmake -DCMAKE_TOOLCHAIN_FILE=path/to/aarch64-toolchain.cmake \
      -DCMAKE_BUILD_TYPE=Release \
      -DXQC_ENABLE_FEC=1 \
      -DXQC_ENABLE_MULTIPATH=1 \
      ..
make -j4

这确实比 TQUIC 简单。但这是一次性成本,踩过就过去了——MPQUIC 冗余发送的功能差异是长期的。


第四章:第三层过滤——C API 设计与集成成本

4.1 为什么 C API 是刚需

T-Box 的控制信号模块是已有的 C/C++ 代码,里面有 T-Box 特有的消息协议解析、状态机、和 MCU 通信的接口。这部分代码不可能在有限时间内重写成 Rust。

4.2 TQUIC C API 设计

TQUIC 提供完整的 C API(tquic.h),采用不透明指针 + 回调函数表的模式:

// 核心数据类型(不透明指针,Rust 内部实现)
typedef struct tquic_config   tquic_config_t;
typedef struct tquic_endpoint tquic_endpoint_t;
typedef struct tquic_conn     tquic_conn_t;

// 回调函数表
typedef struct tquic_transport_methods {
    void (*on_conn_created)(void *tctx, tquic_conn_t *conn);
    void (*on_conn_closed)(void *tctx, tquic_conn_t *conn);
    void (*on_stream_readable)(void *tctx, tquic_conn_t *conn, uint64_t stream_id);
    void (*on_stream_writable)(void *tctx, tquic_conn_t *conn, uint64_t stream_id);
    void (*on_packets_send)(void *tctx, struct iovec *pkts, size_t count,
                            struct sockaddr *dst_addr, socklen_t dst_addr_len);
} tquic_transport_methods_t;

事件驱动,不阻塞:TQUIC 不自己管理 socket,应用层告诉它"收到了这些 UDP 包"(tquic_endpoint_recv),TQUIC 通过回调通知"需要发出这些 UDP 包"(on_packets_send)。

fig03.png

一个简化的集成示例:

#include "tquic.h"

static void on_stream_readable(void *ctx, tquic_conn_t *conn, uint64_t stream_id) {
    uint8_t buf[4096];
    bool fin = false;
    ssize_t n = tquic_conn_stream_recv(conn, stream_id, buf, sizeof(buf), &fin);
    if (n > 0) {
        handle_control_message(buf, n);
    }
}

static void on_packets_send(void *ctx, struct iovec *pkts, size_t count,
                             struct sockaddr *dst, socklen_t dst_len) {
    int fd = *(int *)ctx;
    for (size_t i = 0; i < count; i++) {
        sendto(fd, pkts[i].iov_base, pkts[i].iov_len, 0, dst, dst_len);
    }
}

void init_tquic(int udp_fd) {
    tquic_config_t *cfg = tquic_config_new(QUIC_PROTOCOL_VERSION_V1);
    tquic_config_set_idle_timeout(cfg, 30000);

    // 开启 MPQUIC 冗余发送——一行配置
    tquic_config_set_multipath_enabled(cfg, true);

    tquic_transport_methods_t methods = {
        .on_stream_readable = on_stream_readable,
        .on_packets_send    = on_packets_send,
    };

    tquic_endpoint_t *ep = tquic_endpoint_new(cfg, false, &methods, &udp_fd);
    // 事件循环中调用 tquic_endpoint_recv 处理收到的 UDP 包
}

4.3 验证 C API 符号

nm libtquic.a | grep " T tquic_" | head -20
# 输出应包含 tquic_config_new、tquic_endpoint_new、tquic_conn_stream_send 等

第五章:关键差异——主动冗余 vs 被动重注入

这是本篇最核心的章节。TQUIC 和 XQUIC 都说自己有"冗余发送",但机制完全不同。不看源码,这个区别很难从文档里看出来。

5.1 TQUIC RedundantScheduler:主动冗余

源码位置:tquic/src/multipath_scheduler/scheduler_redundant.rs

impl MultipathScheduler for RedundantScheduler {
    /// 数据包发送后,立即将帧注入到所有其他活跃路径
    fn on_sent(&mut self, packet: &SentPacket, now: Instant, path_id: usize,
        paths: &mut PathMap, spaces: &mut PacketNumSpaceMap, streams: &mut StreamMap)
    {
        if packet.buffer_flags.has_buffered() {
            return;
        }

        // 遍历所有活跃路径
        for (pid, path) in paths.iter() {
            if pid == path_id || !path.active() {
                continue;
            }
            let space = match spaces.get_mut(path.space_id) {
                Some(space) => space,
                None => return,
            };
            // 将 STREAM 帧立即注入其他路径的发送缓冲区
            for frame in &packet.frames {
                if let Frame::Stream { .. } = frame {
                    space.buffered.push_back(frame.clone(), BufferType::High);
                }
            }
        }
    }
}

工作方式:数据包通过 Path 0 发出后,on_sent 立即被调用,将相同的 STREAM 帧注入 Path 1(以及 Path N)的发送缓冲区,等待下一次发送机会。

关键特性

  • 触发时机:每个数据包发出时,无条件触发
  • 冗余比例:100%,所有 STREAM 帧,所有活跃路径
  • 带宽代价:N 条路径 = N 倍带宽消耗(这是代价,也是设计取舍)

5.2 XQUIC Reinjection:被动重注入

源码位置:xquic/src/transport/xqc_reinjection.c

XQUIC 的 Reinjection 有三种触发模式:

模式触发时机控制器触发条件
BEFORE_SCHED调度前Deadline(now - sent_time) >= deadline
AFTER_SCHED调度后Default发送队列为空 + 包在飞行中
AFTER_SEND发送后Deadline/Dgram路径性能低 或 Datagram 冗余

Deadline 计算公式:

factor   = conn_settings.reinj_flexible_deadline_srtt_factor;  // 例如 1.5
flexible = factor * min_srtt;                                   // 动态阈值
hard     = conn_settings.reinj_hard_deadline;                   // 硬性上限
lower    = conn_settings.reinj_deadline_lower_bound;            // 下限保护

deadline = max(min(flexible, hard), lower);

// 当 (now - po->po_sent_time) >= deadline 时触发重注入

工作方式:数据包先通过主路径发出,等待 ACK。如果超过 deadline(约 1.5 × SRTT),或者发送队列为空而包还在飞行中,才把这个包复制到备路径重发。

关键特性

  • 触发时机:条件满足时才触发,不是每个包都触发
  • 冗余比例:部分(满足条件的包)
  • 带宽代价:按需消耗,比主动冗余少

5.3 两种机制的 P99 延迟对比

关键问题:在 4G 网络 2% 丢包场景下,两种机制的 P99 延迟差异是多少?

TQUIC 主动冗余的 P99(理论推导,基于两路 RTT 独立假设,实测见第08篇):

数据包同时在 4G(RTT = 80ms)和 5G(RTT = 50ms)两条路径发出。任一路径先到即交付。

P99 延迟 ≈ min(RTT_4G, RTT_5G) = 50ms(5G 先到)

即使 4G 丢包,5G 已经交付,不需要等待重传。

XQUIC Reinjection 的 P99(理论推导,基于 XQUIC 文档说明的 deadline 计算逻辑):

数据包先在主路径(假设 4G)发出。丢包后,等到 deadline = 1.5 × SRTT ≈ 1.5 × 80 = 120ms 才触发重注入到备路径。

P99 延迟 ≈ RTT_4G + deadline = 80 + 120 = 200ms

这正好踩在云控控制信号 P99 < 200ms 的 SLA 边界上。在网络抖动时,P99 可能超标。

fig04.png

5.4 picoquic 的 preemptive_repeat 为什么不够

picoquic 有一种叫 preemptive_repeat 的冗余机制:在数据包发出后 RTT/8 时间后,如果还没收到 ACK,就在另一条路径上重传。

但有一个关键限制:仅针对已发送 FIN 的流

// picoquic/sender.c
// 检查条件:流是否已发送 FIN
if (!pkt_ctx->preemptive_repeat_ptr->was_preemptively_repeated) {
    if (early_time > current_time) {
        // 等待 RTT/8 后触发
        break;
    }
    ret = picoquic_preemptive_retransmit_packet(...);
}

云控控制信号是持续的双向流,不是"发完就关"的短连接。对于持续发送的控制指令流,preemptive_repeat 不会触发。

结论:picoquic 的冗余机制针对尾部延迟优化(卫星链路、短连接场景),不适合云控控制信号的持续低延迟需求。

5.5 XQUIC FEC 模式能否替代冗余发送?

XQUIC 有 Backup+FEC 调度器,可以把 FEC 修复符号发到备路径(XQC_FEC_MP_USE_STB 模式):

// xquic/src/transport/scheduler/xqc_scheduler_backup_fec.c
if (conn->fec_ctl->fec_mp_mode == XQC_FEC_MP_USE_STB &&
    packet_out->po_frame_types & XQC_FRAME_BIT_REPAIR_SYMBOL)
{
    // 修复符号走备路径
    ret_path = xqc_send_on_standby_path(conn, packet_out, ...);
}

这是一个聪明的设计:源数据走主路径,FEC 修复符号走备路径,两者同时丢失的概率很低。

但 FEC 解决的是"用更少带宽恢复丢包",不是"最小化 P99 延迟"——接收端需要等待足够多的修复符号才能解码,引入额外延迟。对于控制信号 P99 < 200ms 的要求,FEC 模式的延迟不确定性更高。

XQUIC 的 FEC + Multipath 是弱网/带宽受限场景的优秀方案,但不是云控控制信号极低延迟场景的最优解。

5.6 验证命令

# 验证 TQUIC 有 RedundantScheduler
grep -r "RedundantScheduler" --include="*.rs" /tmp/tquic_check/src/ -l
# 应输出:src/multipath_scheduler/scheduler_redundant.rs

# 查看 RedundantScheduler 的 on_sent 实现
grep -A 30 "fn on_sent" /tmp/tquic_check/src/multipath_scheduler/scheduler_redundant.rs

# 验证 XQUIC 的 Reinjection 触发条件
grep -n "REINJ_UNACK\|reinj_deadline\|reinj_flexible" \
  /tmp/xquic_check/src/transport/xqc_reinjection.c | head -20

第六章:性能基准——TQUIC 在 ARM Cortex-A55 上的实测

6.1 测试环境

ARM Cortex-A55 开发板,2核 @ 1.8GHz,1GB LPDDR4。不是 x86 模拟,是真实 ARM 硬件。

6.2 握手延迟

握手类型CPU 时间(ARM A55)说明
1-RTT(首次连接)1.5-3msAES-GCM + SHA-256 的 CPU 消耗
0-RTT(PSK 复用)0.2-0.5ms大幅减少加密操作

实际感知的握手延迟(含网络 RTT 80ms):

  • 1-RTT:80ms + 3ms ≈ 83ms
  • 0-RTT:40ms + 0.5ms ≈ 40ms

6.3 控制信号小包的 CPU 开销

控制信号场景:50 条指令/秒,每条 300 字节。

ARM A55 有 AES 硬件加速指令(AES_E/AES_D),单包加密约 0.01-0.02ms。

50 包/秒的总 CPU 时间:50 × 0.02ms = 1ms/秒,CPU 占用率 0.1%。

TQUIC 在控制信号场景下的 CPU 开销几乎可以忽略。

6.4 内存占用

单个 TQUIC 连接(双路径 MPQUIC 冗余模式)的稳定状态内存:

内存组成大小
QUIC 连接状态(Rust 数据结构)50-80KB
发送缓冲区(按 cwnd 分配)50-200KB
接收缓冲区50-100KB
TLS 上下文(证书、密钥、会话缓存)30-60KB
总计(单连接)180-440KB(各组件为估算范围,实际值受配置和运行时状态影响,建议通过 /proc/PID/smaps 实测)
# 在 T-Box 上监控 TQUIC 进程内存
watch -n 2 'cat /proc/$(pgrep -f tquic)/status | grep -E "VmRSS|VmPeak|VmSwap"'

# 更精细的内存分析
cat /proc/$(pgrep -f tquic)/smaps | awk '/^Rss/ {sum += $2} END {print "Total RSS:", sum, "kB"}'

6.5 诚实的缺点

缺点一:社区规模较小。 quiche 和 msquic 背后是 Cloudflare 和 Microsoft,社区活跃度和 issue 响应速度更好。TQUIC 是相对年轻的开源项目,issue 响应有时需要几天甚至更长。

缺点二:文档相对较少。 API 文档主要靠 tquic.h 的注释和官方网站,集成时需要读源码或直接在 GitHub 提问。

缺点三:MPQUIC API 不稳定。 TQUIC 的 Multipath 功能在 develop 分支,源码中明确标注 API 不稳定,可能随版本变化。选用时需锁定版本,并跟踪 API 变更。draft-ietf-quic-multipath 草案本身也尚未成为正式 RFC,两端版本不一致时互操作性需额外验证。

缺点四:Rust 依赖链。 遇到深层 bug 需要读 Rust 代码,对没有 Rust 工程师的团队是负担。


第七章:最终选型矩阵与结论

把所有候选库在关键维度上的评估汇总:

MPQUIC主动冗余发送FECARM 交叉编译C API维护活跃备注
TQUIC✅ 最新草案✅ RedundantScheduler中(Rust,有坑)⚠️ API 不稳定,develop 分支
XQUIC✅ draft-05/06❌(被动 Reinjection)✅ XOR/RS易(C)活跃弱网/带宽受限场景更优
picoquic✅ 最新草案❌(仅 FIN 流)易(C)活跃标准跟踪最严格
quiche中(Rust)活跃第一轮淘汰
msquic中(CMake)活跃第一轮淘汰
ngtcp2❌(需自研)易(C)✅(底层)活跃自研成本过高
mvfst❌(仅连接迁移)难(folly)活跃无 MPQUIC 并发传输
mp-quic✅(早期)⚠️(RTT=0时)中(Go)不活跃不适合生产

结论:在我们的约束条件下(MPQUIC 主动冗余 + C API + ARM 可用),TQUIC 是最合适的选择。

需要坦诚的是,这个结论有两个前提:

  1. 接受 API 不稳定的风险:TQUIC 的 Multipath API 明确标注不稳定,需要锁定版本并跟踪变更
  2. 其他库也有各自的多路径能力:XQUIC 的 FEC+Multipath 在弱网场景更完善,picoquic 的标准实现更严格——如果你的场景是带宽受限而非极低延迟,XQUIC 可能是更好的选择

那次评审会结束时,架构师翻看了选型矩阵,停在"主动冗余发送"那列——TQUIC 是我们场景下唯一合适的——沉默了几秒,说:"好,我理解了。"

技术选型最好的状态就是这样:不是"这个库最好",而是"在我们的具体约束下,这是最合适的选择"。

fig05.png


结尾

库选定了,是 TQUIC。

下一个问题来自仍在用 KCP 的同行:TQUIC MP-QUIC 冗余发送和 KCP,在 4G 实际网络里谁的延迟更低?KCP 已经做了很多激进优化(快速重传、无延迟 ACK),有人测出来 P99 180ms,在 SLA 边缘——这个数字够用吗?路径切断 5 秒再恢复的场景,KCP 要多久才能重新稳定?

这些问题,见下一篇:《TQUIC vs KCP:云控控制信号场景下谁的延迟更低?》