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 测试用例设计
我建议至少覆盖以下场景:
- WiFi切4G:最常见场景,观察ICE重启和候选者更新
- 4G切WiFi:反向切换,注意候选者优先级变化
- 频繁切换:短时间内来回切换,看会不会崩溃
- 切换后立即断网:模拟切换失败的情况
- 切换时有大流量:比如正在传输高清视频
避坑指南:我曾经遇到过一个坑——在WiFi切4G后,ICE虽然重启了,但新的候选者里只有relay类型(因为4G网络下NAT穿透失败)。结果视频质量骤降,因为relay服务器带宽不够。后来我加了备用relay服务器才解决。
6.7 核心知识体系
下面这张图总结了网络切换调试的完整流程:
6.8 调试技巧总结
最后,分享几个我常用的调试技巧:
- 日志要详细:把ICE状态变化、候选者变化、RTT变化都打出来。我习惯用
console.table打印候选者列表,一目了然。 - 模拟弱网环境:用Clumsy或Network Link Conditioner模拟切换时的网络抖动,看看ICE重启能不能扛住。
- 自动化测试:写一个脚本,每隔30秒自动切换一次网络,跑一晚上。第二天看日志,有没有出现恢复失败的情况。
- 注意relay服务器:如果切换后只有relay候选者,要确保relay服务器带宽足够。我遇到过因为relay服务器过载导致恢复后视频卡顿的情况。
一句话总结:网络切换调试的核心是验证ICE重启的及时性和可靠性。不要只测一次,要反复测、在不同网络环境下测。你想想看,用户可不会只在完美网络下用你的应用。
公众号:蓝海资料掘金营,微信deep3321