网络切换与移动性: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)。
连接迁移的核心思想:不重新协商,直接换路径。
具体来说:
- 客户端检测到网络变化(比如 WiFi 断开)
- 立即使用新的网络接口发送 STUN 请求
- 对端收到来自新地址的 STUN 请求,更新绑定
- 媒体流无缝切换到新路径
这个过程不需要信令参与,完全在媒体层完成。
我的经验:连接迁移在 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变为disconnected或failed - RTC 统计信息:监控
googRtt、packetsLost等指标
// 使用 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 流程图:网络切换处理流程
总结一下
网络切换和移动性,是 WebRTC 在实际落地中必须面对的问题。核心思路就两个:
- 连接迁移:不重新协商,直接换路径,速度快但实现复杂
- ICE 重启:重新协商,通用性强但需要信令配合
我个人建议的做法是:连接迁移优先,ICE 重启兜底。同时,做好网络变化的检测和状态监控,给网络一点自我修复的时间。
嗯,今天就到这里。下次我们再聊更深入的话题——如何优化 ICE 候选者收集,让连接建立更快。
公众号:蓝海资料掘金营,微信deep3321