网络切换与移动性:WiFi 与 4G/5G 切换、ICE 重启、连接迁移策略

大家好,我是你们的 WebRTC 讲师。今天我们来聊一个非常实际的问题——网络切换。

你想想看,用户拿着手机从办公室走到电梯,WiFi 信号慢慢变弱,4G 信号逐渐满格。这时候视频通话会怎样?直接断掉?还是卡几秒后恢复?

嗯,这就是我们今天要解决的问题。说白了,就是让用户在移动过程中,通话体验尽量不受影响。

网络切换的本质问题

网络切换,本质上就是传输路径变了。原来走 WiFi,现在走 4G。原来 IP 地址是 192.168.1.x,现在变成了 10.x.x.x。原来用的网卡是 wlan0,现在变成了 rmnet0。

对于 WebRTC 来说,这意味着什么?

  • 原来的 UDP 连接,源 IP 变了
  • 对端收到的包,源地址对不上了
  • STUN 绑定失效,NAT 映射关系变了
  • 如果不处理,媒体流直接断掉

我在项目中遇到过最典型的情况:用户正在开视频会议,从会议室走到走廊,WiFi 切换到 4G,画面直接卡死 5 秒,然后恢复。这 5 秒的空白,对用户体验来说是致命的。

ICE 重启:最直接的解决方案

ICE 重启,说白了就是让双方重新协商一遍连接。但注意,不是重新建立整个 PeerConnection,而是只重新跑 ICE 流程。

具体怎么做?

// 触发 ICE 重启
const pc = new RTCPeerConnection(config);

// 方式一:设置 iceRestart 为 true
pc.createOffer({ iceRestart: true })
  .then(offer => pc.setLocalDescription(offer))
  .then(() => {
    // 发送 offer 给对端
    signaling.send(JSON.stringify(pc.localDescription));
  });

// 方式二:检测到网络变化时手动触发
window.addEventListener('online', () => {
  if (pc.iceConnectionState === 'disconnected') {
    pc.createOffer({ iceRestart: true })
      .then(offer => pc.setLocalDescription(offer));
  }
});

这里有个关键点:iceRestart: true 这个参数。它告诉 ICE Agent,不要复用之前的候选者,重新收集、重新排序、重新连通性检查。

核心原理:ICE 重启会生成新的 ufrag 和 pwd,旧的 STUN 绑定全部失效,双方重新建立连接。

连接迁移策略:更优雅的做法

ICE 重启虽然有效,但有个问题——它需要信令交互。如果信令通道也断了呢?

所以,更高级的做法是连接迁移(Connection Migration)。

连接迁移的核心思想:不重新协商,直接换路径

具体来说:

  1. 客户端检测到网络变化(比如 WiFi 断开)
  2. 立即使用新的网络接口发送 STUN 请求
  3. 对端收到来自新地址的 STUN 请求,更新绑定
  4. 媒体流无缝切换到新路径

这个过程不需要信令参与,完全在媒体层完成。

我的经验:连接迁移在 WebRTC 标准中是有定义的,但浏览器实现参差不齐。Chrome 支持得比较好,Safari 偶尔会有问题。我建议在应用层做一层兜底——先尝试连接迁移,如果 2 秒内没恢复,再触发 ICE 重启。

WiFi 与 4G/5G 切换的典型场景

场景 切换类型 推荐策略 预期恢复时间
WiFi 信号弱到 4G 主动切换 连接迁移 < 500ms
WiFi 突然断开 被动切换 ICE 重启 1-3s
4G 到 5G 同网切换 连接迁移 < 200ms
飞行模式再恢复 完全断开 ICE 重启 + 重协商 3-5s

你看,不同的场景,恢复时间差别很大。最理想的情况是连接迁移,200-500ms 就能恢复,用户几乎无感知。最差的情况是飞行模式,需要完全重建连接。

如何检测网络变化

检测网络变化是第一步。常用的方法有:

  • navigator.onLine:浏览器提供的 API,但不够精细
  • Network Information API:可以监听 type 变化(wifi/cellular)
  • ICE 连接状态iceConnectionState 变为 disconnectedfailed
  • RTC 统计信息:监控 googRttpacketsLost 等指标
// 使用 Network Information API
if ('connection' in navigator) {
  const connection = navigator.connection;
  
  connection.addEventListener('change', () => {
    console.log('网络类型变化:', connection.type);
    console.log('有效带宽:', connection.downlink);
    
    // 检测到网络变化,触发连接迁移
    if (pc.iceConnectionState === 'connected') {
      // 尝试连接迁移
      triggerConnectionMigration(pc);
    }
  });
}

// 监控 ICE 连接状态
pc.oniceconnectionstatechange = () => {
  console.log('ICE 状态:', pc.iceConnectionState);
  
  if (pc.iceConnectionState === 'disconnected') {
    // 等待 2 秒,看是否能自动恢复
    setTimeout(() => {
      if (pc.iceConnectionState === 'disconnected') {
        // 触发 ICE 重启
        pc.createOffer({ iceRestart: true });
      }
    }, 2000);
  }
};

注意:不要一检测到网络变化就立刻触发 ICE 重启。有时候网络只是短暂抖动,几秒后会自动恢复。我建议加一个 1-2 秒的延迟,给网络一点自我修复的时间。

我曾经踩过的坑

嗯,这里分享一个我真实的踩坑经历。

有一次,我们做一款移动端视频会议 App。测试发现,用户在 WiFi 和 4G 之间来回切换时,通话会频繁中断。我们当时用的是 ICE 重启方案,每次切换都要重新协商,耗时 3-5 秒。

后来排查发现,问题出在信令通道上。WiFi 切换时,WebSocket 连接也断了,ICE 重启的 offer 发不出去。等 WebSocket 重连成功,已经过去好几秒了。

解决方案是什么?

我们改成了连接迁移优先的策略。客户端检测到网络变化后,直接用新网络发 STUN 请求。如果 2 秒内没收到响应,再走 ICE 重启。同时,信令通道也做了优化——使用多个备选服务器,切换时自动重连。

改完之后,切换恢复时间从 3-5 秒降到了 500ms 以内。用户反馈明显好了很多。

SVG 流程图:网络切换处理流程

网络切换处理流程 检测到网络变化 ICE 状态 是否 connected? 尝试连接迁移 触发 ICE 重启 迁移 成功? 媒体流恢复 触发 ICE 重启 通话恢复 成功路径 失败路径

总结一下

网络切换和移动性,是 WebRTC 在实际落地中必须面对的问题。核心思路就两个:

  • 连接迁移:不重新协商,直接换路径,速度快但实现复杂
  • ICE 重启:重新协商,通用性强但需要信令配合

我个人建议的做法是:连接迁移优先,ICE 重启兜底。同时,做好网络变化的检测和状态监控,给网络一点自我修复的时间。

嗯,今天就到这里。下次我们再聊更深入的话题——如何优化 ICE 候选者收集,让连接建立更快。


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