6、网络切换调试:模拟网络切换(WiFi 切 4G),观察 ICE 重启和连接恢复过程

网络切换,说白了就是你的手机从家里WiFi走到户外,自动切到4G/5G。这个场景在WebRTC里是个大坑。我刚开始做实时通信时,觉得这不过是换个IP地址而已,结果第一次测试就翻车了——视频卡死、重连失败、用户直接投诉。嗯,从那以后,我把网络切换列为了必测项。

6.1 网络切换到底发生了什么?

先搞清楚底层逻辑。WiFi和4G是两个完全不同的网络接口:

  • IP地址变了:WiFi下是192.168.x.x(内网),4G下是运营商分配的公网IP
  • 端口可能变了:NAT映射关系会重建
  • 候选者失效:之前协商好的ICE候选者(特别是host候选者)全部作废
  • 连接中断:正在传输的RTP/RTCP包会丢失

WebRTC的应对机制是ICE重启。说白了就是重新走一遍ICE流程:收集新候选者、连通性检查、选出一对可用的候选者对。

核心要点:ICE重启不是重新建立PeerConnection,而是复用已有的连接通道,只更新传输层地址。这样可以避免重新协商SDP,速度更快。

6.2 如何模拟网络切换?

我个人习惯用三种方式模拟,按难度从低到高:

方法一:物理切换(最真实)

拿两台手机,一台开热点,一台连热点。测试时直接关掉热点WiFi,让手机自动切到4G。这个方法最接近真实场景,但不好控制时机。

方法二:Chrome DevTools模拟(最方便)

打开Chrome的开发者工具,切到Network面板,有个Offline勾选框。但注意:这个只能模拟断网,不能模拟网络切换。我一般用它做快速验证。

方法三:系统级网络切换(我最推荐)

在Windows/Mac上,通过命令行禁用/启用网络适配器:

# Windows:禁用WiFi
netsh interface set interface "WiFi" admin=disable

# 等待3秒后启用
netsh interface set interface "WiFi" admin=enable

# Mac:断开WiFi(需要管理员权限)
sudo ifconfig en0 down
sudo ifconfig en0 up

配合脚本,可以精确控制切换时机。我在项目中就是这么做的,效果很好。

6.3 观察ICE重启的关键指标

网络切换后,你需要盯着这几个东西:

指标 正常表现 异常表现
iceConnectionState connected → disconnected → connected 一直卡在disconnected或failed
iceGatheringState complete → gathering → complete gathering超时或失败
候选者数量 新候选者出现(特别是srflx和relay) 候选者数量为0或只有host
RTT 切换后短暂升高,然后恢复 持续高延迟或超时
丢包率 切换瞬间丢包,之后恢复 持续丢包

我的经验:在Chrome的chrome://webrtc-internals页面里,可以实时看到这些指标的变化。我每次测试都会开着这个页面,截图保存,方便后续分析。

6.4 ICE重启的触发条件

WebRTC不会自动检测网络切换。它靠的是连接超时STUN心跳失败来触发ICE重启。具体来说:

  • 如果连续几个STUN请求没有回应,ICE会认为连接断了
  • 默认超时时间大约是5-10秒(取决于实现)
  • 超时后,ICE会重新收集候选者并开始连通性检查

为什么会这样?因为WebRTC不知道你换了网络接口,它只知道收不到数据了。所以它只能靠超时机制来触发恢复流程。

注意:默认的超时时间可能太长。如果你的应用对实时性要求高(比如在线游戏),建议手动缩短超时时间。可以通过设置iceConnectionState的监听器,在状态变为disconnected时主动触发ICE重启。

6.5 手动触发ICE重启

有时候自动恢复太慢,我们可以手动触发:

// 手动触发ICE重启
function restartIce(pc) {
  // 创建一个新的ICE重启offer
  pc.createOffer({ iceRestart: true })
    .then(offer => {
      return pc.setLocalDescription(offer);
    })
    .then(() => {
      // 发送新的offer给远端
      // 远端收到后调用setRemoteDescription
      // 然后创建answer
    })
    .catch(err => {
      console.error('ICE重启失败:', err);
    });
}

// 监听连接状态变化
pc.oniceconnectionstatechange = () => {
  console.log('ICE状态:', pc.iceConnectionState);
  if (pc.iceConnectionState === 'disconnected') {
    console.log('连接断开,尝试ICE重启');
    restartIce(pc);
  }
};

我曾经在一个项目中,用户反馈视频通话经常卡死。排查后发现是网络切换后ICE重启太慢。加上手动触发后,恢复时间从平均8秒降到了2秒以内。

6.6 测试用例设计

我建议至少覆盖以下场景:

  1. WiFi切4G:最常见场景,观察ICE重启和候选者更新
  2. 4G切WiFi:反向切换,注意候选者优先级变化
  3. 频繁切换:短时间内来回切换,看会不会崩溃
  4. 切换后立即断网:模拟切换失败的情况
  5. 切换时有大流量:比如正在传输高清视频

避坑指南:我曾经遇到过一个坑——在WiFi切4G后,ICE虽然重启了,但新的候选者里只有relay类型(因为4G网络下NAT穿透失败)。结果视频质量骤降,因为relay服务器带宽不够。后来我加了备用relay服务器才解决。

6.7 核心知识体系

下面这张图总结了网络切换调试的完整流程:

网络切换调试核心流程 网络切换 WiFi → 4G / 4G → WiFi 连接中断 IP/端口变化,候选者失效 ICE超时检测 STUN心跳失败 ICE重启 重新收集候选者 重新进行连通性检查 候选者更新 host → srflx → relay 新IP/端口加入 连接恢复 候选者对选定 媒体流恢复 监控指标 iceConnectionState | iceGatheringState | 候选者数量 | RTT | 丢包率 chrome://webrtc-internals | 手动触发restartIce()

6.8 调试技巧总结

最后,分享几个我常用的调试技巧:

  • 日志要详细:把ICE状态变化、候选者变化、RTT变化都打出来。我习惯用console.table打印候选者列表,一目了然。
  • 模拟弱网环境:用Clumsy或Network Link Conditioner模拟切换时的网络抖动,看看ICE重启能不能扛住。
  • 自动化测试:写一个脚本,每隔30秒自动切换一次网络,跑一晚上。第二天看日志,有没有出现恢复失败的情况。
  • 注意relay服务器:如果切换后只有relay候选者,要确保relay服务器带宽足够。我遇到过因为relay服务器过载导致恢复后视频卡顿的情况。

一句话总结:网络切换调试的核心是验证ICE重启的及时性和可靠性。不要只测一次,要反复测、在不同网络环境下测。你想想看,用户可不会只在完美网络下用你的应用。


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