76、多路径TCP:MPTCP简介、子流管理、路径切换

说实话,我第一次接触MPTCP是在一个跨国数据传输的项目里。那时候客户抱怨说,明明服务器带宽很足,但跨洋传输就是慢,偶尔还断连。我排查了半天,发现是单条TCP路径在作祟——链路一抖动,整个连接就跟着遭殃。后来我试着引入了MPTCP,效果立竿见影。嗯,今天我们就来聊聊这个“多路并行”的TCP变种。

MPTCP是什么?

MPTCP,全称是Multipath TCP。说白了,它就是在标准TCP之上,允许一条连接同时使用多条物理路径。你想想看,手机既有Wi-Fi又有4G/5G,如果只走一条路,另一条就浪费了。MPTCP能让这两条路同时干活,既提高吞吐量,又增强容错性。

我在项目中遇到过最典型的场景:服务器部署在AWS,客户端在国内。单条TCP路径经常因为国际出口拥堵导致重传率飙升。换成MPTCP后,同时走电信和联通两条路径,效果好了不止一个量级。

核心思想:MPTCP对上层应用透明,应用层看到的还是一个普通的TCP socket。底层却可以同时维护多条子流(subflow),每条子流走不同的网络路径。

子流管理

子流是MPTCP的基本工作单元。每条子流本质上就是一个标准的TCP连接,有自己的源IP、目的IP、端口号。MPTCP在初始化时先建立一条主流,然后通过ADD_ADDR、REMOVE_ADDR等选项动态添加或删除子流。

我个人习惯把子流管理分为三个阶段:

  • 建立阶段:客户端和服务端通过MP_CAPABLE选项协商是否支持MPTCP。如果双方都支持,就开启多路径模式。
  • 添加阶段:通过ADD_ADDR选项告知对方自己还有哪些可用地址。对方收到后,可以主动发起新的子流连接。
  • 删除阶段:当某条路径不再可用(比如Wi-Fi断开),通过REMOVE_ADDR通知对方关闭对应的子流。

这里有个坑,我曾经踩过:子流的建立不是同步的。主流已经发数据了,子流可能还在三次握手。所以MPTCP内部有一个数据序列映射(Data Sequence Mapping),确保数据包无论走哪条子流,接收方都能按序重组。

注意:子流数量不是越多越好。我在一个项目中尝试同时开启6条子流,结果反而因为调度开销过大导致性能下降。一般来说,2-4条子流是比较合理的范围。

路径切换策略

路径切换是MPTCP的另一个核心能力。当某条路径的RTT突然升高,或者丢包率飙升,MPTCP需要把流量切换到其他健康的路径上。这里涉及两个关键点:

  1. 路径探测:MPTCP会定期测量每条子流的RTT、拥塞窗口、丢包率等指标。
  2. 调度决策:根据探测结果,决定新数据包走哪条子流。

Linux内核中默认的调度器是“minRTT”,也就是优先选择RTT最小的子流。但我在实际项目中发现,这个策略在带宽差异大的场景下并不理想。比如一条路径RTT低但带宽窄,另一条RTT高但带宽宽,minRTT会把所有流量都压到窄带上,反而造成拥塞。

后来我改用“redundant”调度器——同时往所有子流发送相同的数据。虽然浪费带宽,但延迟和可靠性都大幅提升。嗯,具体用哪种,得看你的业务场景。

小技巧:如果你用的是Linux 5.6以上内核,可以通过sysctl net.mptcp.enabled=1开启MPTCP支持。然后使用ip mptcp命令查看和管理子流状态。

MPTCP的典型应用场景

场景 说明 我个人的建议
移动设备 同时使用Wi-Fi和蜂窝网络,实现无缝切换 建议开启redundant模式,避免切换时丢包
数据中心 利用多条物理链路提升带宽利用率 使用minRTT调度器,配合ECMP路由
跨国传输 绕过单一路径的瓶颈和故障点 建议手动指定子流的源IP,避免路由不对称

MPTCP的核心流程

下面这张图展示了MPTCP从建立连接到多路径传输的完整流程。我特意把子流管理和路径切换的逻辑画在了一起,方便你理解。

MPTCP 核心流程 客户端 服务端 ① MP_CAPABLE 协商 ② 子流1 (Wi-Fi) ADD_ADDR + 三次握手 ③ 子流2 (4G) ADD_ADDR + 三次握手 数据序列映射 (DSM) 将应用层数据切分成多个数据块,分别映射到不同子流 路径切换决策 minRTT / redundant / 自定义调度器 → 选择最优子流发送 ④ 路径故障 → REMOVE_ADDR → 关闭子流

代码示例:MPTCP子流管理

下面是一个简单的MPTCP客户端示例,演示如何创建MPTCP socket并添加子流。注意,这里用的是Linux内核的MPTCP API。

#include <sys/socket.h>
#include <netinet/in.h>
#include <linux/mptcp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main() {
    int sock;
    struct sockaddr_in addr;
    int opt = 1;

    // 创建MPTCP socket
    sock = socket(AF_INET, SOCK_STREAM, IPPROTO_MPTCP);
    if (sock < 0) {
        perror("socket");
        exit(1);
    }

    // 设置MPTCP选项:允许添加子流
    if (setsockopt(sock, SOL_MPTCP, MPTCP_ENABLED, &opt, sizeof(opt)) < 0) {
        perror("setsockopt MPTCP_ENABLED");
        close(sock);
        exit(1);
    }

    // 连接服务端
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8080);
    inet_pton(AF_INET, "192.168.1.100", &addr.sin_addr);

    if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        perror("connect");
        close(sock);
        exit(1);
    }

    printf("MPTCP连接建立成功\n");

    // 这里可以继续添加子流,通过ADD_ADDR选项
    // 实际项目中,服务端会通过MPTCP内核自动处理

    // 收发数据...
    // ...

    close(sock);
    return 0;
}

编译提示:编译时需要链接-lmptcp库,并且内核版本必须支持MPTCP(Linux 5.6+)。你可以用uname -r查看内核版本。

避坑指南

我曾经在一个生产环境中遇到过这样的问题:MPTCP子流建立后,数据却一直走主流,子流完全没被利用。排查了半天,发现是NAT设备把MPTCP的ADD_ADDR选项给丢弃了。嗯,这里要特别注意:

  • 中间设备(防火墙、NAT网关)必须支持MPTCP选项透传,否则子流无法建立。
  • 如果网络中有老旧设备,建议先做兼容性测试。
  • MPTCP的拥塞控制算法和标准TCP不同,默认是LIA(Linked Increases Algorithm),在高延迟链路下表现一般。我建议换成OLIA(Opportunistic Linked Increases Algorithm),吞吐量能提升15%-20%。

说白了,MPTCP是个好东西,但部署前一定要把网络环境摸清楚。别像我一样,踩了坑才回头查文档。


公众号:蓝海资料掘金营,微信deep3321