Files
16gagent/game-accelerator-tech-plan/04-流量转发系统设计.md
T

45 KiB
Raw Blame History

第四部分:流量转发系统设计

4.1 MVP阶段 — UDP Relay

4.1.1 协议设计

┌──────────────────────────────────────────────────────────────────┐
│                    UDP Relay 协议报文结构                          │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  报文头 (16字节):                                                │
│  ┌────────┬────────┬────────┬────────┬────────┬────────┐        │
│  │ Magic  │ Version│  Type  │ Flags  │ SeqNum │ Length │        │
│  │ 2B     │ 1B     │ 1B     │ 1B     │ 4B     │ 4B     │        │
│  │ 0xACE0 │ 0x01   │        │        │        │        │        │
│  └────────┴────────┴────────┴────────┴────────┴────────┘        │
│                                                                  │
│  字段说明:                                                      │
│  ┌──────────┬──────────┬────────────────────────────────────┐   │
│  │ 字段     │ 大小     │ 说明                                │   │
│  ├──────────┼──────────┼────────────────────────────────────┤   │
│  │ Magic    │ 2 bytes  │ 魔数 0xACE0,用于识别协议           │   │
│  │ Version  │ 1 byte   │ 协议版本号(当前0x01)              │   │
│  │ Type     │ 1 byte   │ 报文类型                            │   │
│  │          │          │ 0x01=DATA, 0x02=ACK, 0x03=HEARTBEAT │   │
│  │          │          │ 0x04=CONTROL, 0x05=FEC              │   │
│  │ Flags    │ 1 byte   │ 标志位                              │   │
│  │          │          │ bit0: 加密                          │   │
│  │          │          │ bit1: 压缩                          │   │
│  │          │          │ bit2: FEC                           │   │
│  │          │          │ bit3: 重传                          │   │
│  │          │          │ bit4-7: 保留                        │   │
│  │ SeqNum   │ 4 bytes  │ 序列号(用于乱序检测和重传)        │   │
│  │ Length   │ 4 bytes  │ 载荷长度                            │   │
│  └──────────┴──────────┴────────────────────────────────────┘   │
│                                                                  │
│  扩展头(可选,当Flags中有加密/压缩时):                         │
│  ┌────────┬────────┬────────┬────────┬────────┐                │
│  │ ExtLen │ ExtType│ IV     │ Padding│ HMAC   │                │
│  │ 1B     │ 1B     │ 12B    │ 可变   │ 16B    │                │
│  └────────┴────────┴────────┴────────┴────────┘                │
│                                                                  │
│  完整报文:                                                      │
│  ┌────────────┬────────────┬────────────┬────────────┐         │
│  │ 报文头     │ 扩展头     │ 加密载荷   │ HMAC校验   │         │
│  │ 16B        │ 可变       │ 可变       │ 16B        │         │
│  └────────────┴────────────┴────────────┴────────────┘         │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

4.1.2 数据流转时序图

┌──────────────────────────────────────────────────────────────────┐
│                    UDP Relay 数据流时序图                          │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  玩家客户端          接入节点          中转节点          游戏服务器│
│      │                  │                │                  │   │
│      │  1.游戏UDP包      │                │                  │   │
│      │─────────────────→│                │                  │   │
│      │                  │                │                  │   │
│      │                  │ 2.封装为       │                  │   │
│      │                  │   Relay包      │                  │   │
│      │                  │ (加序列号)     │                  │   │
│      │                  │                │                  │   │
│      │                  │ 3.加密+转发    │                  │   │
│      │                  │───────────────→│                  │   │
│      │                  │                │                  │   │
│      │                  │                │ 4.解密+解封装    │   │
│      │                  │                │                  │   │
│      │                  │                │ 5.转发原始包     │   │
│      │                  │                │─────────────────→│   │
│      │                  │                │                  │   │
│      │                  │                │ 6.游戏响应       │   │
│      │                  │                │←─────────────────│   │
│      │                  │                │                  │   │
│      │                  │ 7.封装+加密    │                  │   │
│      │                  │←───────────────│                  │   │
│      │                  │                │                  │   │
│      │ 8.解密+解封装    │                │                  │   │
│      │←─────────────────│                │                  │   │
│      │                  │                │                  │   │
│      │ 9.返回游戏       │                │                  │   │
│      │                  │                │                  │   │
│                                                                  │
│  总延迟增加:2-10ms(取决于节点间距离)                           │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

4.1.3 伪代码实现

// UDP Relay 核心转发逻辑
package relay

import (
    "crypto/aes"
    "crypto/cipher"
    "encoding/binary"
    "net"
    "sync"
    "time"
)

// 协议常量
const (
    Magic       = 0xACE0
    Version     = 0x01
    TypeDATA    = 0x01
    TypeACK     = 0x02
    TypeHEART   = 0x03
    TypeCONTROL = 0x04
    TypeFEC     = 0x05
    HeaderSize  = 16
)

// RelayHeader 报文头
type RelayHeader struct {
    Magic    uint16
    Version  uint8
    Type     uint8
    Flags    uint8
    SeqNum   uint32
    Length   uint32
}

// Session 会话状态
type Session struct {
    SessionID    uint64
    ClientAddr   *net.UDPAddr
    ServerAddr   *net.UDPAddr
    SendSeq      uint32
    RecvSeq      uint32
    AESKey       []byte
    LastActive   time.Time
    RTT          time.Duration
    LossRate     float64
    mu           sync.Mutex
}

// RelayServer 转发服务器
type RelayServer struct {
    listenAddr *net.UDPAddr
    conn       *net.UDPConn
    sessions   sync.Map // map[uint64]*Session
    aesGCM     cipher.AEAD
}

// Start 启动转发服务
func (s *RelayServer) Start() error {
    conn, err := net.ListenUDP("udp4", s.listenAddr)
    if err != nil {
        return err
    }
    s.conn = conn
    
    buf := make([]byte, 65535)
    for {
        n, clientAddr, err := conn.ReadFromUDP(buf)
        if err != nil {
            continue
        }
        go s.handlePacket(buf[:n], clientAddr)
    }
}

// handlePacket 处理收到的包
func (s *RelayServer) handlePacket(data []byte, clientAddr *net.UDPAddr) {
    // 解析报文头
    if len(data) < HeaderSize {
        return
    }
    
    header := parseHeader(data[:HeaderSize])
    if header.Magic != Magic {
        return
    }
    
    switch header.Type {
    case TypeDATA:
        s.handleData(header, data[HeaderSize:], clientAddr)
    case TypeHEART:
        s.handleHeartbeat(header, clientAddr)
    case TypeACK:
        s.handleACK(header, clientAddr)
    }
}

// handleData 处理数据包
func (s *RelayServer) handleData(header RelayHeader, payload []byte, clientAddr *net.UDPAddr) {
    // 获取或创建会话
    session := s.getOrCreateSession(clientAddr)
    
    // 解密载荷
    plaintext, err := s.decrypt(payload, session.AESKey)
    if err != nil {
        return
    }
    
    // 更新序列号
    session.mu.Lock()
    session.RecvSeq = header.SeqNum
    session.LastActive = time.Now()
    session.mu.Unlock()
    
    // 转发到游戏服务器
    _, err = s.conn.WriteToUDP(plaintext, session.ServerAddr)
    if err != nil {
        // 记录错误,触发重连
        s.handleForwardError(session, err)
    }
    
    // 发送ACK(如果需要可靠传输)
    if header.Flags&0x01 != 0 {
        s.sendACK(session, header.SeqNum)
    }
}

// handleHeartbeat 处理心跳
func (s *RelayServer) handleHeartbeat(header RelayHeader, clientAddr *net.UDPAddr) {
    session := s.getSession(clientAddr)
    if session == nil {
        return
    }
    
    session.mu.Lock()
    session.LastActive = time.Now()
    // 计算RTT(心跳包中有时间戳)
    session.mu.Unlock()
    
    // 回复心跳
    s.sendHeartbeatReply(session)
}

// ForwardLoop 转发循环(从游戏服务器到客户端)
func (s *RelayServer) ForwardLoop(session *Session) {
    buf := make([]byte, 65535)
    for {
        n, err := session.ServerConn.Read(buf)
        if err != nil {
            break
        }
        
        // 加密
        ciphertext := s.encrypt(buf[:n], session.AESKey)
        
        // 封装
        header := RelayHeader{
            Magic:   Magic,
            Version: Version,
            Type:    TypeDATA,
            SeqNum:  session.SendSeq,
            Length:  uint32(len(ciphertext)),
        }
        
        session.mu.Lock()
        session.SendSeq++
        session.mu.Unlock()
        
        // 发送
        packet := append(header.Bytes(), ciphertext...)
        s.conn.WriteToUDP(packet, session.ClientAddr)
    }
}

// encrypt AES-256-GCM加密
func (s *RelayServer) encrypt(plaintext []byte, key []byte) []byte {
    block, _ := aes.NewCipher(key)
    gcm, _ := cipher.NewGCM(block)
    nonce := make([]byte, gcm.NonceSize())
    // 生成随机nonce
    return gcm.Seal(nil, nonce, plaintext, nil)
}

// CheckSessionTimeout 会话超时检查
func (s *RelayServer) CheckSessionTimeout() {
    ticker := time.NewTicker(10 * time.Second)
    for range ticker.C {
        s.sessions.Range(func(key, value interface{}) bool {
            session := value.(*Session)
            if time.Since(session.LastActive) > 60*time.Second {
                // 会话超时,清理
                s.sessions.Delete(key)
                session.Close()
            }
            return true
        })
    }
}

4.2 商业版阶段 — QUIC Tunnel

4.2.1 为什么选择QUIC

┌──────────────────────────────────────────────────────────────────┐
│                    QUIC vs UDP Relay vs TCP                       │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  特性          │ UDP Relay │ QUIC       │ TCP                    │
│  ──────────────┼───────────┼────────────┼────────────            │
│  连接建立延迟   │ 0-RTT     │ 0-RTT      │ 3-RTT (TLS)           │
│  多路复用       │ ✗         │ ✓          │ ✗ (HOL阻塞)           │
│  可靠传输       │ 可选      │ 可选       │ 必须                   │
│  流量控制       │ 手动      │ 内置       │ 内置                   │
│  拥塞控制       │ 手动      │ BBR/Cubic  │ Cubic                  │
│  连接迁移       │ 手动      │ 内置       │ ✗                      │
│  加密           │ 手动      │ 内置(TLS1.3)│ 需要TLS               │
│  前向纠错       │ 手动      │ 可扩展     │ ✗                      │
│  多路径         │ 手动      │ 扩展中     │ MPTCP                  │
│  ──────────────┼───────────┼────────────┼────────────            │
│  适合场景       │ 简单转发  │ 游戏加速   │ 文件传输              │
│                                                                  │
│  结论:QUIC是最优选择                                             │
│  - 0-RTT连接建立(快速切换节点)                                  │
│  - 多路复用无HOL阻塞(多游戏流量并行)                            │
│  - 内置加密(安全性)                                             │
│  - 连接迁移(IP变化不断线)                                      │
│  - 可扩展(加FEC、多路径)                                        │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

4.2.2 QUIC Tunnel协议设计

┌──────────────────────────────────────────────────────────────────┐
│                    QUIC Tunnel 架构                               │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                    应用层                                │    │
│  │  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐      │    │
│  │  │ Stream 0│ │ Stream 1│ │ Stream 2│ │ Stream N│      │    │
│  │  │ 游戏数据│ │ 控制流  │ │ 心跳流  │ │ 其他    │      │    │
│  │  └─────────┘ └─────────┘ └─────────┘ └─────────┘      │    │
│  └─────────────────────────────────────────────────────────┘    │
│                         ↓                                        │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                    QUIC层                                │    │
│  │  ┌──────────────────────────────────────────────────┐   │    │
│  │  │  QUIC Frames:                                    │   │    │
│  │  │  - STREAM (数据帧)                               │   │    │
│  │  │  - ACK (确认帧)                                  │   │    │
│  │  │  - CRYPTO (加密握手)                             │   │    │
│  │  │  - PADDING (填充)                                │   │    │
│  │  │  - CONNECTION_CLOSE (关闭)                       │   │    │
│  │  │  - FEC (前向纠错,自定义扩展)                    │   │    │
│  │  └──────────────────────────────────────────────────┘   │    │
│  └─────────────────────────────────────────────────────────┘    │
│                         ↓                                        │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                    TLS 1.3层                             │    │
│  │  ┌──────────────────────────────────────────────────┐   │    │
│  │  │  - 握手加密                                      │   │    │
│  │  │  - 会话密钥轮换                                  │   │    │
│  │  │  - 证书验证                                      │   │    │
│  │  └──────────────────────────────────────────────────┘   │    │
│  └─────────────────────────────────────────────────────────┘    │
│                         ↓                                        │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                    UDP层                                 │    │
│  │  ┌──────────────────────────────────────────────────┐   │    │
│  │  │  - UDP Socket                                    │   │    │
│  │  │  - BBR拥塞控制                                   │   │    │
│  │  │  - 多路径支持                                    │   │    │
│  │  └──────────────────────────────────────────────────┘   │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

4.2.3 Multipath多路径传输

┌──────────────────────────────────────────────────────────────────┐
│                    Multipath 多路径设计                           │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ┌────────┐                      ┌────────┐                     │
│  │        │──── Path 1 (联通) ───→        │                     │
│  │ 客户端 │──── Path 2 (电信) ───→ 接入节点│──→ 游戏服务器       │
│  │        │──── Path 3 (移动) ───→        │                     │
│  └────────┘                      └────────┘                     │
│                                                                  │
│  路径管理:                                                      │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │  PathTable:                                              │   │
│  │  ┌─────────┬────────┬────────┬────────┬────────┐        │   │
│  │  │ PathID  │ RTT    │ Loss   │ Bw     │ Weight │        │   │
│  │  ├─────────┼────────┼────────┼────────┼────────┤        │   │
│  │  │ path-1  │ 30ms   │ 0.1%   │ 50Mbps │ 0.5    │        │   │
│  │  │ path-2  │ 45ms   │ 0.5%   │ 30Mbps │ 0.3    │        │   │
│  │  │ path-3  │ 60ms   │ 1.0%   │ 20Mbps │ 0.2    │        │   │
│  │  └─────────┴────────┴────────┴────────┴────────┘        │   │
│  └──────────────────────────────────────────────────────────┘   │
│                                                                  │
│  数据分配策略:                                                  │
│  1. 关键数据(游戏动作)→ 走最优路径(path-1)                   │
│  2. 状态数据(位置同步)→ 分散到多路径                           │
│  3. 冗余数据(FEC)→ 走次优路径                                  │
│  4. 实时监控路径质量,动态调整权重                                │
│                                                                  │
│  路径切换:                                                      │
│  - 主路径故障 → 无缝切换到备用路径                               │
│  - 切换延迟 < 100ms                                              │
│  - 连接保持(Session ID不变)                                    │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

4.2.4 FEC前向纠错

┌──────────────────────────────────────────────────────────────────┐
│                    FEC前向纠错设计                                │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  原理:发送N个数据包 + K个冗余包,丢任意K个包都能恢复            │
│                                                                  │
│  编码方式:Reed-Solomon码                                        │
│                                                                  │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │  示例:RS(10,4) — 10个数据包 + 4个冗余包                  │   │
│  │                                                          │   │
│  │  发送端:                                                │   │
│  │  D1 D2 D3 D4 D5 D6 D7 D8 D9 D10  ← 数据包              │   │
│  │  P1 P2 P3 P4                      ← 冗余包(校验包)      │   │
│  │  ─────────────────────────────────                       │   │
│  │  共14个包,允许丢4个包                                    │   │
│  │                                                          │   │
│  │  接收端(假设D2,D5,D8,P2丢失):                         │   │
│  │  D1 ✗ D3 D4 ✗ D6 D7 ✗ D9 D10                           │   │
│  │  ✗ P3 P4                                                 │   │
│  │  ─────────────────────────────────                       │   │
│  │  通过RS解码恢复D2,D5,D8,P2                              │   │
│  └──────────────────────────────────────────────────────────┘   │
│                                                                  │
│  动态FEC参数调整:                                               │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │  丢包率     │ FEC比例    │ 说明                          │   │
│  ├─────────────┼────────────┼──────────────────────────────│   │
│  │ < 1%        │ N=10, K=1  │ 低冗余,节省带宽              │   │
│  │ 1% - 5%    │ N=10, K=2  │ 中等冗余                      │   │
│  │ 5% - 10%   │ N=10, K=3  │ 高冗余                        │   │
│  │ > 10%       │ N=10, K=4  │ 最大冗余,保护关键数据        │   │
│  │ > 20%       │ 切换路径   │ 丢包太严重,切换链路          │   │
│  └─────────────┴────────────┴──────────────────────────────┘   │
│                                                                  │
│  实现代码(Go):                                                │
│                                                                  │
│  type FECEncoder struct {                                        │
│      dataShards   int    // 数据分片数                          │
│      parityShards int    // 冗余分片数                          │
│      enc          reedsolomon.Encoder                           │
│  }                                                               │
│                                                                  │
│  func (f *FECEncoder) Encode(data []byte) [][]byte {            │
│      // 将数据分割为dataShards个分片                             │
│      shards := f.splitData(data, f.dataShards)                  │
│      // 生成parityShards个冗余分片                               │
│      err := f.enc.Encode(shards)                                │
│      // 返回所有分片(数据+冗余)                                │
│      return shards                                               │
│  }                                                               │
│                                                                  │
│  func (f *FECEncoder) Decode(shards [][]byte) ([]byte, error) { │
│      // 通过RS解码恢复丢失的分片                                 │
│      err := f.enc.Reconstruct(shards)                           │
│      // 合并分片为原始数据                                       │
│      return f.mergeShards(shards[:f.dataShards]), err           │
│  }                                                               │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

4.2.5 动态拥塞控制

┌──────────────────────────────────────────────────────────────────┐
│                    动态拥塞控制设计                                │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  目标:在不丢包的前提下,最大化利用带宽                           │
│                                                                  │
│  算法选择:BBR (Bottleneck Bandwidth and Round-trip)             │
│                                                                  │
│  BBR工作原理:                                                   │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │                                                          │   │
│  │  1. 测量瓶颈带宽 (BtlBw)                                 │   │
│  │     - 跟踪最大交付速率                                   │   │
│  │     - 滑动窗口取最大值                                   │   │
│  │                                                          │   │
│  │  2. 测量最小RTT (RTprop)                                 │   │
│  │     - 跟踪最小往返时间                                   │   │
│  │     - 10秒窗口取最小值                                   │   │
│  │                                                          │   │
│  │  3. 计算发送速率                                         │   │
│  │     cwnd = BtlBw × RTprop × gain                        │   │
│  │     pacing_rate = BtlBw × gain                          │   │
│  │                                                          │   │
│  │  4. 四个阶段循环:                                       │   │
│  │     Startup → Drain → ProbeBW → ProbeRTT                │   │
│  │                                                          │   │
│  └──────────────────────────────────────────────────────────┘   │
│                                                                  │
│  游戏场景优化:                                                  │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │  1. 低延迟优先                                           │   │
│  │     - 限制最大cwnd,避免缓冲区膨胀                       │   │
│  │     - pacing_rate不超过实际带宽的80%                      │   │
│  │                                                          │   │
│  │  2. 快速恢复                                             │   │
│  │     - 丢包时快速降低发送速率                             │   │
│  │     - 恢复时快速提升(比标准BBR更快)                     │   │
│  │                                                          │   │
│  │  3. 多流公平                                             │   │
│  │     - 多个游戏流共享带宽时公平分配                        │   │
│  │     - 优先级流获得更多带宽                               │   │
│  └──────────────────────────────────────────────────────────┘   │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

4.2.6 UDP重传优化

┌──────────────────────────────────────────────────────────────────┐
│                    UDP重传优化设计                                │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  问题:标准UDP不重传,丢包就丢了                                  │
│  方案:选择性重传 + 优先级重传                                    │
│                                                                  │
│  数据分类:                                                      │
│  ┌──────────┬────────────────┬────────────┬──────────────┐      │
│  │ 优先级   │ 数据类型       │ 重传策略   │ 超时时间     │      │
│  ├──────────┼────────────────┼────────────┼──────────────┤      │
│  │ P0       │ 玩家操作指令   │ 必须重传   │ 50ms         │      │
│  │ P1       │ 游戏状态同步   │ 选择重传   │ 100ms        │      │
│  │ P2       │ 位置更新       │ 不重传     │ -            │      │
│  │ P3       │ 视觉效果数据   │ 不重传     │ -            │      │
│  └──────────┴────────────────┴────────────┴──────────────┘      │
│                                                                  │
│  重传流程:                                                      │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │  发送端:                                                 │   │
│  │  1. 发送数据包,记录发送时间                             │   │
│  │  2. 等待ACK                                              │   │
│  │  3. 超时未收到ACK → 检查优先级                          │   │
│  │  4. P0/P1 → 立即重传                                    │   │
│  │  5. P2/P3 → 放弃,等新数据                              │   │
│  │                                                          │   │
│  │  接收端:                                                 │   │
│  │  1. 收到数据包 → 发送ACK                                │   │
│  │  2. 检测到序列号跳跃 → 发送NACK(选择性否定确认)        │   │
│  │  3. 乱序到达 → 缓存并重排序(10ms窗口)                 │   │
│  └──────────────────────────────────────────────────────────┘   │
│                                                                  │
│  伪代码:                                                        │
│                                                                  │
│  type RetransmitQueue struct {                                   │
│      pending map[uint32]*Packet                                  │
│      mu      sync.Mutex                                          │
│  }                                                               │
│                                                                  │
│  func (q *RetransmitQueue) Add(pkt *Packet) {                   │
│      q.mu.Lock()                                                 │
│      defer q.mu.Unlock()                                        │
│      pkt.SendTime = time.Now()                                  │
│      q.pending[pkt.SeqNum] = pkt                                │
│  }                                                               │
│                                                                  │
│  func (q *RetransmitQueue) CheckTimeout() []*Packet {           │
│      q.mu.Lock()                                                 │
│      defer q.mu.Unlock()                                        │
│      var retransmit []*Packet                                   │
│      now := time.Now()                                          │
│      for seq, pkt := range q.pending {                          │
│          if now.Sub(pkt.SendTime) > pkt.Timeout {               │
│              if pkt.Priority <= P1 {                            │
│                  retransmit = append(retransmit, pkt)           │
│                  pkt.SendTime = now                             │
│                  pkt.RetransmitCount++                          │
│              } else {                                           │
│                  delete(q.pending, seq)                         │
│              }                                                  │
│          }                                                      │
│      }                                                          │
│      return retransmit                                          │
│  }                                                               │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

4.2.7 智能链路切换

┌──────────────────────────────────────────────────────────────────┐
│                    智能链路切换设计                                │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  切换触发条件:                                                  │
│  1. 当前链路延迟 > 最优链路延迟 × 1.5                            │
│  2. 当前链路丢包率 > 5%                                          │
│  3. 连续3次探测质量下降                                          │
│  4. 当前链路断开                                                 │
│                                                                  │
│  切换流程:                                                      │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │                                                          │   │
│  │  ┌──────────┐    ┌──────────┐    ┌──────────┐          │   │
│  │  │ 探测新   │───→│ 建立新   │───→│ 迁移会话 │          │   │
│  │  │ 链路质量 │    │ 连接     │    │ 状态     │          │   │
│  │  └──────────┘    └──────────┘    └──────────┘          │   │
│  │       │                                    │            │   │
│  │       │              ┌─────────────────────┘            │   │
│  │       │              ↓                                  │   │
│  │       │         ┌──────────┐    ┌──────────┐          │   │
│  │       │         │ 切换流量 │───→│ 关闭旧   │          │   │
│  │       │         │ 到新链路 │    │ 连接     │          │   │
│  │       │         └──────────┘    └──────────┘          │   │
│  │       │                                                │   │
│  │       └─── 整个过程 < 200ms,用户无感知                 │   │
│  │                                                          │   │
│  └──────────────────────────────────────────────────────────┘   │
│                                                                  │
│  会话保持:                                                      │
│  - 使用SessionID标识会话                                         │
│  - 切换时携带SessionID到新链路                                   │
│  - 新链路通过SessionID恢复会话状态                               │
│  - 序列号连续(不因切换而重置)                                  │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘