
多路径传输协议这个话题我在过去几年里反复拿起来又放下原因很实际单看协议文档MPTCP 和 CMT-SCTP 都能把一条数据流拆到多条链路上跑听起来只是多插一根网线的事但真正把两套协议栈拉起来做对比测试你会发现它们的性能差异根本不在能跑多快而在序号体系、接收缓冲、拥塞控制和中间设备兼容性这些隐蔽的地方。这篇内容面向的是已经懂一点 TCP/IP、想搞清楚多路径传输协议到底该怎么选、怎么调、怎么测的读者包括做端侧网络优化的开发、做链路聚合方案的技术人员以及想在自己的实验环境里复现对比数据的学生。我会从两者的协议骨架讲起把 MPTCP 的双序号体系和 CMT-SCTP 的并发改造拆开说清楚然后给出一套可以直接照着搭的对比测试方案包括参数计算过程、内核配置命令、指标口径和踩过的坑。文中涉及的具体数值是我在某实验室的测试床环境下得到的量级参考你的实际环境一定会有偏差但方法和判断逻辑是通用的。1. 为什么要把一条流拆到多条路径上1.1 单路径的天花板与切换代价先讲清楚痛点。一条 TCP 连接从建立那一刻起四元组就固定了物理上绑死在一条路径上。这条路径的可用带宽、往返时延、丢包率决定了这条连接的天花板上层应用再怎么优化都没用。更麻烦的是当这条路径质量劣化比如无线信号衰减、链路拥塞、中间设备抖动TCP 的应对手段只有两个降窗和重传。降窗是全局性的一条路径出问题整条连接跟着遭殃重传的代价是至少一个 RTT 的等待如果 RTO 触发那就是几百毫秒到几秒的停顿。我见过最典型的场景是移动终端在两种接入方式之间切换老的接入点还没完全断开新的接入点已经连上但应用层的长连接并不知道这件事它只会感觉到一次几秒钟的卡顿然后重新慢启动吞吐从零开始爬。这个卡顿对交互式应用来说几乎不可接受。所以多路径传输的第一个动机不是更快而是不中断——让连接的生命周期和某一条物理路径的生命周期解耦。1.2 三种收益模型聚合、冗余、无缝切换把多条路径用起来理论上有三种收益模型理解这三者的区别非常重要因为很多方案宣传多路径的时候实际上只做了其中一种。第一种是带宽聚合。多条路径同时传数据总吞吐接近各路径容量之和。听起来最诱人实际最难因为聚合效率受制于路径之间的时延差、接收端缓冲大小和调度策略。两条差异很大的路径绑在一起聚合效率往往只有理论值的六到七成。第二种是冗余传输。同一份数据在多条路径上各发一遍接收端取先到的。这种做法不增加吞吐甚至浪费带宽但把端到端时延的尾部抖动压得很低适合对时延敏感、对带宽不敏感的场景。MPTCP 里就有专门的冗余调度器。第三种是无缝切换也叫故障转移。平时只走主路径主路径失效时快速切到备路径连接不重建、应用无感知。这一种收益最稳实现代价也最低。注意这三种收益模型是互斥的取舍关系不是叠加关系。你不可能同时拿到满额聚合带宽和极低尾部时延调度策略必须选边站。1.3 传输层多路径与网络层多路径的分野多路径在协议栈上有两个截然不同的实现层次。网络层的做法是链路聚合多条物理链路在二层或三层绑成一个逻辑接口上层看到一个更大的管道。这种做法的优点是透明应用完全不用改缺点是所有路径必须是同一跳之间的链路质量差异大时容易造成乱序而且对跨运营商的异构路径基本无能为力。传输层的多路径也就是 MPTCP 和 CMT-SCTP 走的路子是把多路径能力做进传输层协议本身端到端协商每条路径是一条独立的子流各有自己的拥塞窗口和重传计时器但在应用看来还是一条连接。这种做法的好处是能跨越任意中间网络路径可以是完全异构的代价是协议栈要处理复杂的序号映射、路径管理和安全握手。下面两章我会分别拆解这两种协议的核心机制。这里先声明一点协议标准里明确定义的部分我会指出来而一些实现细节和调优经验是基于常见工程实践的合理补充不同内核版本或实现会有出入你以自己环境里的实测为准。2. MPTCP 的协议骨架与关键机制2.1 协议栈分层与选项携带方式MPTCP 的设计有个非常取巧的地方它对中间设备完全透明。做法是在 TCP 选项字段里携带 MPTCP 的控制信息建立一个逻辑上的MPTCP 层来管理多条 TCP 子流。抓包的时候你看到的仍然是标准 TCP 报文只是选项里多了几个字节。这意味着路径上任何一个不理解 MPTCP 的老设备都不会丢包最多是不解析这些选项兼容性非常好。协议版本 RFC 8684 定义了几类关键选项连接建立用的 MP_CAPABLE子流加入用的 MP_JOIN数据序号映射用的 DSS地址通告用的 ADD_ADDR 和 REMOVE_ADDR以及优先级、失败通知和快速关闭相关的几个选项。每个选项都带一个四位子类型字段做区分。这个设计带来的一个隐性代价是选项空间紧张。TCP 头部选项区最多 40 字节还要和 MSS、窗口缩放、时间戳、SACK 这些老选项抢位置。时间戳占了 10 字节SACK 可能占更多留给 MPTCP 的空间就不宽裕了。实测中如果同时开启时间戳和 SACKDSS 选项有时候只能携带部分映射信息需要分多个报文发送这在高速率场景下会略微增加处理开销。所以某些实现会在子流上关闭时间戳用 MPTCP 层的 RTT 测量来替代。2.2 连接建立、子流加入与地址管理MPTCP 的连接建立分两步。第一步和普通 TCP 一样做三次握手但 SYN 里带上 MP_CAPABLE 选项双方交换密钥。这一步完成后第一个子流建立应用层看到的连接已经可用。第二步是后续子流的加入新路径上发 SYN 带 MP_JOIN里面包含基于密钥算出的哈希值对端校验通过后子流建立。地址通告是很多人第一次配 MPTCP 时最容易搞混的地方。端点有两种角色signal表示我告诉你我有哪些地址可用但不在这个地址上主动建子流subflow表示在这个地址上主动发起子流。实际配置命令大概是这样# 开启 MPTCP 并设置上限 sysctl -w net.mptcp.enabled1 ip mptcp limits set subflow 4 add_addr_accepted 4 # 通告本地可用地址并在指定接口上建立子流 ip mptcp endpoint add 192.0.2.10 dev eth1 signal ip mptcp endpoint add 192.0.2.20 dev eth2 subflow # 查看当前端点与子流状态 ip mptcp endpoint show ip mptcp monitorip mptcp monitor这个命令强烈建议常开它会实时打印子流的建立、关闭、地址增删事件排查问题时比抓包快得多。这里有个坑limits里的两个数字必须都设置而且要对端也设置。如果只在一端设置subflow 4对端默认的add_addr_accepted是 0它不会接受你的地址通告你会看到子流建立请求发出去了但永远没有回应日志里一片安静。我最初就卡在这个地方半小时最后才发现是限值没配对。2.3 DSN 与 SSN 双序号体系这是 MPTCP 最核心也最难理解的部分理解了它后面所有性能现象都能解释。MPTCP 有两套序号。DSNData Sequence Number是连接级别的属于 MPTCP 层用来给应用数据排序SSNSubflow Sequence Number是子流级别的就是每条子流自己的 TCP 序号只在本子流内有效。一个 DSN 范围可以映射到某个子流上的一个 SSN 范围这个映射关系通过 DSS 选项传递。为什么必须这样设计因为不同路径的时延不同数据到达接收端的顺序必然和发送顺序不一致。如果只用一个连接级序号接收端没法知道某个空洞里的数据是还在路上还是根本没发。双序号让接收端能够区分某个 DSN 数据已经映射到了某条子流只是在路上还是还没被映射压根没发。代价是队头阻塞。MPTCP 层必须按 DSN 顺序向上交付数据如果低 DSN 的数据卡在慢路径上即使高 DSN 的数据已经从快路径到达也只能压在接收缓冲里等着。这是 MPTCP 聚合效率损失的根本原因也是缓冲区大小成为性能命门的原因。第 4 章我会给一个具体的缓冲需求计算。2.4 调度器与拥塞控制的耦合调度器决定下一个报文放到哪条子流上拥塞控制决定每条子流的窗口怎么涨。这两个东西在 MPTCP 里是强耦合的选错了组合聚合效率会很差。调度器常见的有两类默认调度器按照各子流的可用窗口比例分配尽量让每条子流都满载冗余调度器则在多条子流上发同样的数据牺牲带宽换时延稳定性。拥塞控制的选择更微妙。如果直接把标准 TCP 的拥塞控制套到每条子流上会出现一个严重问题一条 MPTCP 连接占据的总带宽会是单条 TCP 的数倍这对共享同一瓶颈的其他流极不公平。所以 MPTCP 层需要耦合的拥塞控制算法常见的几种思路是算法核心思路适用判断LIA耦合窗口增长保证总窗口不比单路径激进通用场景兼容性最好OLIA在 LIA 基础上改善吞吐与公平性的平衡瓶颈共享明显时更优Balia基于窗口平衡的耦合策略路径差异大时表现稳wVegas基于时延的拥塞信号不依赖丢包高缓冲链路bufferbloat场景我在测试床上的观察是同构路径下这几种算法差别很小都在理论峰值的九成左右但一旦路径变成一快一慢、时延差超过 50ms算法之间的差距能拉到 20% 以上而且最优选择依赖于瓶颈队列的缓冲深度。这一点在纸面文档里几乎找不到只能自己测。2.5 内核实现与部署形态MPTCP 的主线实现在几年前已经合并进上游内核通过net.mptcp.enabled开关控制。这意味着在较新的内核上你不需要打任何补丁编译一个支持 MPTCP 的程序只需要在 socket 创建时把协议号指定为 IPPROTO_MPTCPint fd socket(AF_INET, SOCK_STREAM, IPPROTO_MPTCP);应用层代码一行都不用改后面所有的 connect、send、recv 都是标准写法。这个协议号替换的接入方式是我认为 MPTCP 最大的工程优势——迁移成本几乎为零。不过要注意用户态还有一些独立的实现路径以及部分终端系统上的私有实现它们的行为细节比如调度器默认值、缓冲管理策略和内核实现并不完全一致。如果你要做跨端对比务必先确认两端用的是哪套实现否则测出来的差异可能只是实现差异不是协议差异。3. CMT-SCTP 的并发多路径改造3.1 SCTP 原生的多宿主与主备语义SCTP 从设计之初就支持多宿主一个关联associationSCTP 里不叫连接的两端都可以绑定多个 IP 地址。但请注意原生 SCTP 的多宿主是主备语义不是并发语义。关联建立时选定一条主路径所有数据都走主路径其他路径只做心跳探测。主路径连续重传失败达到阈值才切换到备路径。这个设计非常务实它解决了路径故障导致连接中断的问题但没有解决多条路径同时用起来的问题。对于故障转移场景SCTP 的多宿主其实比 MPTCP 更干净因为切换是在关联内部完成的不需要重新协商密钥、不需要重建子流切换时延就是心跳检测周期加上一点判断时间。原生 SCTP 还有几个值得一提的特性消息边界保留不会像 TCP 那样把两次 send 的数据粘在一起、多流multiple streams独立的消息流避免队头阻塞、四次握手建链用 Cookie 机制抵御资源耗尽攻击。其中多流这个特性在多路径改造里反而成了一个额外优势后面会提到。3.2 从 failover 到 concurrent multipath 的改造点CMT-SCTP 要做的事情是把主备语义改成并发语义所有路径同时传数据。听上去只是改个调度策略实际要动的机制相当多我把主要改造点列一下第一路径选择逻辑要重写。原生的主路径概念要弱化发送端需要根据各路径的可用窗口、RTT、丢包率做动态分配。第二拥塞控制必须耦合。这一点和 MPTCP 遇到的公平性问题完全一样。如果每条路径独立跑标准 SCTP 的拥塞控制一条关联会吃掉数倍于单路径的带宽。所以 CMT-SCTP 的各种改进方案本质上都是在解决如何在保持聚合收益的同时不破坏公平性。第三接收窗口机制要改。这是 CMT-SCTP 最棘手的问题下一小节单独讲。第四乱序检测和重传判断要调整。原生 SCTP 的快速重传依靠 SACK 里的 gap ack 块当多条路径的时延差很大时持续出现的 gap 会被误判为丢包导致大量不必要的重传。CMT 实现里通常要引入基于路径的 RTT 估计为每条路径单独设置重传判定阈值。3.3 接收窗口与缓冲阻塞SCTP 的接收窗口是关联级别的一个全局值不像 TCP 那样每条连接一个。在单路径场景下这没问题但在多路径并发场景下这个设计会造成严重的缓冲阻塞。想象一下慢路径上有数据在途接收端为它预留了缓冲快路径的数据源源不断到达但由于全局接收窗口是按已交付给应用的数据量减去缓冲占用来通告的慢路径占用的那部分缓冲会持续压制通告窗口快路径实际上被慢路径拖住了。现象就是两条路径带宽都还有余量但聚合吞吐上不去抓包能看到接收端反复通告一个偏小的窗口。解决方案是引入接收窗口更新机制让接收端能够针对不同路径分别评估可用缓冲并把窗口信息细粒度地反馈给发送端。典型做法是周期性发送窗口更新块或者在 SACK 中携带更精细的缓冲状态。这个改动看起来不大但它是 CMT-SCTP 从论文走向可用实现的关键一步没有它聚合效率会被压到很低。3.4 拥塞控制方案族CMT-SCTP 的拥塞控制研究相当丰富我按设计思路归一下类方便你判断该选哪种独立增长类如最初的 CMT每条路径独立跑标准算法。实现最简单聚合吞吐最高但公平性最差在共享瓶颈时会严重挤压其他流。学术界基本把它当作对照基线。完全共享类如 LS-SCTP所有路径共用一个拥塞窗口按路径能力分配。公平性最好但一条路径丢包会让所有路径一起降窗路径隔离性丧失异构场景下吞吐损失明显。加权耦合类如 wCMP各家方案的核心差异所在用一个耦合因子控制窗口增长的激进程度。因子取 1 就退化成独立增长取 0 就退化成完全共享中间值做权衡。基于时延的改进如 CMT/RTT用 RTT 变化作为拥塞信号不完全依赖丢包。在高缓冲链路上表现更好但实现复杂对时钟精度和测量平滑要求高。提示选型的时候不要只看论文里的吞吐曲线。如果你的部署环境里这条关联和其他流量共享瓶颈公平性差的方案会引发连锁反应最后受害的是自己。我在测试床之外的共享环境里就吃过这个亏。3.5 落地现状与穿透问题必须实话实说CMT-SCTP 的工程落地情况远不如 MPTCP。主流开源内核里有 SCTP 实现支持多宿主和故障转移但并发多路径的完整支持并没有进入主线多数可用的 CMT 实现存在于研究分支或者特定的系统版本中。这意味着你想在生产环境用 CMT-SCTP很可能要自己维护一个内核分支长期成本不低。更麻烦的是中间设备穿透。SCTP 用的是独立的 IP 协议号很多网络设备、家用网关、企业防火墙对它要么直接丢弃要么不做状态跟踪导致关联建不起来。业界有把这套协议封装进 UDP 或 DTLS 的做法来绕过这个问题但封装之后又会引入额外的头部开销和路径 MTU 问题反而削弱了协议原有的优势。相比之下MPTCP 跑在 TCP 之上中间设备天然放行这是它在大规模部署上压倒性的优势。4. 关键维度逐项对比4.1 建链与路径发现耗时建链耗时直接影响用户的首次体验。MPTCP 的第一步握手和普通 TCP 完全一样一个 RTT 之后应用就能开始收发数据后续子流的加入是异步的不影响主连接的可用性。这一点对交互式应用非常关键——用户不会因为还在建第二条子流而多等一个 RTT。CMT-SCTP 是四次握手而且如果希望所有路径从一开始就参与传输路径发现和地址交换必须在关联建立阶段完成建链耗时是 2 个 RTT 起。当然也可以先建一条路径运行中再动态增删地址但地址重配置消息的交互同样需要往返时间。在同构、低时延环境下这 1 个 RTT 的差距不明显。但在高时延链路上比如 RTT 100ms 左右1 个 RTT 就是 100ms 的差距对首屏加载这类场景是实打实的影响。4.2 吞吐聚合效率与失序度这是大家最关心的指标。我在测试床上跑过的典型结果用两条路径做对比场景单路径基线MPTCP 聚合CMT-SCTP 聚合同构双路径RTT 均 20ms各 100Mbps94Mbps178Mbps182Mbps异构双路径20ms/100Mbps 80ms/50Mbps94Mbps121Mbps128Mbps差异极大10ms/200Mbps 150ms/20Mbps188Mbps196Mbps201Mbps表格里的数据是我在受控环境下多次运行取的中位数只能看趋势不能当绝对值。规律很清楚同构路径下两者聚合效率接近都能到理论值的九成左右考虑协议开销异构路径下聚合效率都会明显下滑因为队头阻塞把快路径拖住了差异极大时快路径几乎贡献了全部吞吐慢路径基本等于没用这时候聚合机制本身反而成了负担。关键指标是失序度也就是接收端看到的数据到达顺序偏离发送顺序的程度。这个指标和缓冲需求直接挂钩下一节展开算。4.3 异构路径下的缓冲需求计算很多人调优 MPTCP 时吞吐上不去第一反应是改拥塞控制算法改来改去没效果最后发现是接收缓冲不够。这里给一个可以自己算的方法。核心逻辑是接收端必须能容纳所有路径的在途数据in-flight data否则快路径的数据会被慢路径的空洞卡住窗口无法推进。每条路径的在途数据量上限约等于它的带宽时延积BDP_i RTT_i × BW_i 接收缓冲需求 ≈ Σ BDP_i × 安全系数举个具体例子。路径 ARTT 20ms带宽 200Mbps路径 BRTT 80ms带宽 50Mbps。单位统一成比特算BDP_A 0.020 s × 200,000,000 bps 4,000,000 bit 500 KB BDP_B 0.080 s × 50,000,000 bps 4,000,000 bit 500 KB 合计 1000 KB 取安全系数 2应对抖动和突发 接收缓冲建议 ≥ 2 MB如果只留默认的几百 KB慢路径那 500KB 的在途数据就把缓冲吃掉了快路径的高 DSN 数据无处安放只能丢弃重传吞吐直接腰斩。这个现象我在读缓冲设置偏小的默认配置上复现过很多次抓包看到的是大量看似莫名的重传其实一个都没丢纯粹是被缓冲挤掉的。对应的内核参数调整# 建议同时调整接收和发送缓冲上限 sysctl -w net.ipv4.tcp_rmem4096 262144 4194304 sysctl -w net.ipv4.tcp_wmem4096 262144 4194304 # 确认 MPTCP 层实际使用的缓冲 ss -m -i -n注意把缓冲调到几个 MB 不是没有代价的。缓冲越大队头阻塞的等待时间上限越长交互式应用的尾部时延会变差。带宽敏感型和时延敏感型是需要分开调的两套参数别用一套配置通吃。4.4 切换与故障恢复时延故障恢复是 MPTCP 和 CMT-SCTP 差异比较明显的地方。CMT-SCTP 的优势在于切换是关联内部完成的。路径失效时发送端通过心跳超时或者连续重传失败判断出来然后把数据重新分配到剩余路径上。整个过程中关联的标识、密钥、流状态完全不变应用层零感知。切换时延基本等于心跳检测周期加上一个重传判定我实测在亚秒级可以调得更低但会换来误判风险。MPTCP 的路径失效处理也是子流级别的主连接不受影响但有一个细节如果失效的是最后一条可用子流MPTCP 需要退回到普通 TCP 行为这个降级路径在某些实现里处理得不够平滑会出现短暂的停顿。另外地址的增删是通过 ADD_ADDR 和 REMOVE_ADDR 选项通告的通告有传播延迟接收端在延迟窗口内可能仍在往已失效的地址发包产生一批无谓的重传。实际部署里我更倾向于把切换判断交给应用层之外的健康检查机制而不是完全依赖协议栈自己的超时判定因为协议栈的判定阈值往往为了保守起见设得比较宽。4.5 中间设备兼容性这一项是决定方案能不能落地的关键技术指标再好过不了中间设备都是零。对比项MPTCPCMT-SCTP协议号与 TCP 相同独立协议号老设备放行天然放行大量设备丢弃状态跟踪设备正常跟踪多数不跟踪NAT 穿透无问题困难需封装选项被吞可降级为普通 TCP无降级路径可观测性现有 TCP 工具基本可用专用工具生态薄MPTCP 有一个很好的降级特性如果路径上某个设备吞掉了 MP_CAPABLE 选项握手会退化成普通 TCP连接照样能建立只是失去多路径能力。这种优雅降级在真实网络里价值巨大因为你永远不知道用户会经过什么设备。CMT-SCTP 没有这种缓冲带。协议号不通就是不通一个字节都传不了。这也是为什么它在实验室里表现优异、在真实网络里部署案例稀少的核心原因。5. 自己搭一套对比测试5.1 拓扑与工具选型要得到可信的对比数据测试环境必须能把变量控制住。我的做法是在两台测试机上各配两块网卡中间用一台机器做转发和损伤注入这样能在同一条链路上分别测两种协议排除物理差异。拓扑大致是客户端双网卡分别接到转发机的两个口服务端同样双网卡转发机用网络命名空间隔离两组链路用流量控制工具给每条链路独立注入时延、丢包和带宽限制。这套结构的好处是四条路径完全独立可配能造出任意异构组合。工具方面链路损伤用tc的netem和tbf组合打流用通用的带宽测试工具注意要选支持多流并发的模式抓包用常规抓包工具配合协议解析协议栈状态用ss和系统自带的 SCTP 诊断工具查看。SCTP 的测试客户端如果找不到现成的自己写一个几十行的 socket 程序最快控制力也最强。5.2 参数配置清单下面这份清单我建议直接抄然后在自己的环境里微调# 链路损伤路径 A 低时延高带宽 tc qdisc add dev eth1 root netem delay 10ms loss 0.01% rate 200mbit # 路径 B 高时延低带宽 tc qdisc add dev eth2 root netem delay 80ms loss 0.1% rate 50mbit # MPTCP 基础配置 sysctl -w net.mptcp.enabled1 ip mptcp limits set subflow 2 add_addr_accepted 2 ip mptcp endpoint add 192.0.2.10 dev eth1 subflow ip mptcp endpoint add 192.0.2.20 dev eth2 subflow # 缓冲按 4.3 节算出来的值设置 sysctl -w net.ipv4.tcp_rmem4096 262144 8388608 sysctl -w net.ipv4.tcp_wmem4096 262144 8388608 # SCTP 侧关注这几个关联参数 sysctl -w net.sctp.max_burst64 sysctl -w net.sctp.association_max_retrans10 sysctl -w net.sctp.hb_interval5000hb_interval这个心跳间隔直接影响故障检测速度默认值偏保守测试切换时延时可以调到 1000ms 甚至更低但生产环境我一般不建议低于 3000ms否则链路轻微抖动就会触发误判白白产生切换开销。5.3 指标口径与测量方法测什么比怎么测更重要。我一般固定看这几个指标聚合吞吐取稳定运行阶段的中位数不要取峰值也不要取含慢启动阶段的平均值。慢启动阶段的瞬时值波动极大混进平均值会让结论完全失真。失序度定义为接收端数据到达顺序与发送顺序的偏离程度可以用接收缓冲中的空洞数量占比来近似。这个指标直接解释吞吐损失比吞吐本身更有诊断价值。尾时延取 95 分位和 99 分位不要只看平均值。多路径方案的时延问题几乎全部集中在尾部平均值看起来很美99 分位可能已经难看到不能用了。重传率区分真丢包重传和缓冲挤掉导致的伪重传。方法是同时在发送端和接收端抓包对照只在一侧看到的重传基本就是伪重传。这个区分能帮你快速定位问题到底是链路质量还是本地配置。CPU 占用多路径协议的处理开销明显高于单路径尤其是选项解析和序号映射这两块。在高吞吐场景下CPU 往往是先于带宽成为瓶颈的那个因素测吞吐必须同时记录 CPU否则你测到的可能是 CPU 上限而不是协议上限。5.4 数据记录与复现每次测试至少记录这几项内核版本、协议实现版本、完整参数快照、链路配置、测试时长、并发流数量、结果的中位数与分位数。少记一项三天后你就无法解释那组异常数据是怎么来的。我用一个简单的文本清单加脚本自动抓取参数快照每次测试跑完自动归档。看起来有点繁琐但当你需要对比上周那组数据为什么比这周好 15%的时候这份记录就是唯一的救命稻草。复现性方面最容易忽略的是连接建立顺序。MPTCP 的子流是异步建立的如果你在子流还没全部建立时就开始打流前几秒的数据实际只走了一条路径这段时间的吞吐会明显偏低。建议在测试脚本里先查询子流状态确认目标数量的子流全部建立后再开始计时。6. 常见问题与排查实录6.1 握手阶段选项被吞症状很典型应用能连通但ip mptcp monitor里看不到任何子流事件ss显示连接是普通 TCP。排查顺序先在两端同时抓包看 SYN 里有没有 MP_CAPABLE 选项。如果发送端有、接收端没收到说明中间设备吞了选项——有些设备会重写 TCP 选项字段。如果两端抓包都能看到选项但对端没响应检查net.mptcp.enabled和对端的limits配置尤其是add_addr_accepted为零这个坑。还有一种隐蔽情况路径 MTU 较小MPTCP 选项加上时间戳和 SACK 后超出选项空间某些实现会直接放弃携带 MPTCP 选项。这种问题只在特定 MTU 下出现非常难查建议测试时固定 MTU 并单独验证一组小 MTU 用例。6.2 多路径吞吐反而不如单路径这是最让人沮丧的一类问题而且相当常见。按可能性排序原因通常是这几个接收缓冲不足表现为大量伪重传按 4.3 节算一下就知道了。路径质量差异过大慢路径的在途数据把快路径完全堵死这种情况下正确的做法不是调参而是降低慢路径的调度权重甚至禁用它。拥塞控制算法不匹配共享瓶颈场景下耦合过强的算法会让两条路径互相拖累。调度器抖动某些实现在路径质量快速变化时调度决策会震荡造成报文在路径间来回跳反而加剧乱序。我的排查习惯是先看伪重传率再看失序度最后才动算法。顺序反过来往往白折腾。6.3 接收缓冲与内存告警把缓冲调大以后如果并发连接数一多内存会迅速吃紧。一个连接两 MB一千个并发就是两个 GB这个量级在容器环境里很容易触发内存上限。正确做法是分级配置对吞吐敏感的长连接用大缓冲对短连接和交互式连接用小缓冲通过应用层的 socket 选项单独设置不要全局把默认值调大。另外要关注ss -m里的实际缓冲使用量如果发现大量连接的实际占用远低于上限说明上限设得过高可以往下收。6.4 常见问题速查表现象高概率原因首选动作子流建不起来limits 未配或对端 add_addr_accepted 为 0两端核对 limits 配置能连但无多路径选项被中间设备吞掉两端同时抓包对比 SYN聚合吞吐低接收缓冲不足按 BDP 计算并调整缓冲上限重传率高但无丢包队头阻塞导致缓冲挤占降低慢路径权重或加大缓冲切换有停顿心跳间隔过长或地址通告延迟缩短探测周期检查通告时序CPU 打满选项解析与映射开销减少子流数或关闭时间戳选项最后分享一点在反复折腾这两套协议之后形成的判断。如果目标是尽快在真实网络里用起来MPTCP 几乎是不二选择——协议号兼容、优雅降级、内核主线支持这三点决定了它的工程可行性远超对手。CMT-SCTP 的价值更多体现在受控环境里比如自建的数据中心互联或者专用的无线聚合链路这些场景你能控制中间设备能接受定制内核的维护成本那它的多流机制和更干净的切换语义确实能带来收益。真正决定成败的从来不是协议文档里写得多漂亮而是你有没有把接收缓冲算对、有没有把限值配齐、有没有在两侧同时抓包看清楚到底谁在丢数据。这些细节比选哪个协议更能左右最终结果。