13、ICE 重启与连接恢复:ICE restart 场景、触发方式、数据通道的恢复行为

做 WebRTC 开发,最怕什么?

我最怕的就是用户正视频聊着,突然画面卡住,然后连接断了。用户那边骂娘,我们这边查日志查到天亮。其实很多时候,问题出在 ICE 连接上——网络切换了、NAT 映射过期了、或者干脆就是运营商搞鬼。

这时候,ICE restart 就是我们的救命稻草。

什么是 ICE restart?

说白了,ICE restart 就是让双方重新进行一次完整的 ICE 连接建立过程。它不像普通的 ICE 连通性检查那样只是发几个 STUN 包探探路,而是彻底推倒重来——新的 ufrag、新的 pwd、新的候选者收集。

我习惯把它理解成「网络层的断线重连」。应用层的 WebSocket 断了可以重连,传输层的 TCP 断了可以重连,那 ICE 连接断了,自然也要有重连机制。ICE restart 就是这个机制。

核心要点:ICE restart 会生成全新的 ICE 会话,旧的候选者全部作废,双方重新开始候选者收集和连通性检查。

什么场景下需要 ICE restart?

我在项目中遇到过好几种情况,总结下来主要有这么几类:

  • 网络切换:用户从 Wi-Fi 切到 4G/5G,或者从家里 Wi-Fi 切到公司 Wi-Fi。IP 地址变了,原来的候选者自然就失效了。
  • NAT/防火墙映射超时:有些运营商的 NAT 映射只有几分钟有效期。映射一过期,外面的包就进不来了。我记得有一次线上反馈说视频通话 5 分钟准时断,查了半天就是 NAT 映射超时的问题。
  • ICE 连接彻底断开:比如用户把手机放口袋里,信号被遮挡了十几秒,ICE 连接状态变成了 FAILED
  • 主动触发:有些场景下,应用层检测到网络质量急剧下降,主动发起 ICE restart 来尝试切换到更好的网络路径。
场景 触发时机 典型表现
网络切换 IP 地址变化 候选者全部失效
NAT 映射超时 长时间无媒体流 STUN 请求无响应
ICE 连接失败 ICE 状态变为 FAILED 连通性检查全部超时
应用层主动触发 检测到网络质量差 RTT 飙升、丢包率升高

ICE restart 的触发方式

触发 ICE restart 其实就两种方式,一种是在 JS 层调用 API,另一种是在信令层协商。

1. 通过 JS API 触发

在浏览器端,我们可以直接操作 RTCPeerConnection 对象。代码很简单:

// 创建一个新的 offer,触发 ICE restart
const offer = await pc.createOffer({ iceRestart: true });
await pc.setLocalDescription(offer);
// 通过信令通道发送给远端
sendToRemote(offer);

注意那个 { iceRestart: true } 参数。不加这个,createOffer 只是生成一个普通的 offer。加了之后,生成的 SDP 里会包含全新的 ufrag 和 pwd。

小技巧:我个人习惯在调用 createOffer 之前先检查一下 pc.iceConnectionState。如果已经是 FAILED 了,直接触发 restart。如果还是 CONNECTED,那就要慎重——别没事找事。

2. 通过信令层协商

有时候远端可能先检测到连接异常,这时候就需要远端主动发起 restart。流程是这样的:

  1. 远端检测到 ICE 连接失败
  2. 远端生成新的 offer(带 iceRestart: true
  3. 通过信令通道发送给本地
  4. 本地收到后,调用 setRemoteDescription,然后生成 answer
  5. 双方重新开始 ICE 流程

嗯,这里要注意:信令通道本身不能依赖 ICE 连接。如果 ICE 都断了,信令通道还走 ICE 数据通道,那就死循环了。所以实际项目中,信令通道一般走 WebSocket 或者 HTTP,跟 ICE 是独立的。

数据通道在 ICE restart 中的行为

这个问题我当初踩过坑。数据通道(DataChannel)是基于 ICE 连接的,ICE 一重启,数据通道会怎么样?

答案是:数据通道会断开,然后自动重建

具体来说:

  • ICE restart 开始时,所有现有的数据通道都会进入 CLOSED 状态
  • ICE 连接重新建立成功后,数据通道会自动重新打开
  • 数据通道的 onopenonclose 事件会再次触发

但是——这里有个大坑——数据通道里的数据会丢失。ICE restart 不会帮你缓存未发送的消息。如果你在 restart 前发送了一条消息,但还没发出去,这条消息就没了。

避坑指南:我曾经在一个实时协作编辑的项目里,用户正在打字,网络切换触发了 ICE restart,结果刚打的那段话丢了。后来我加了一层应用层的消息确认机制——每条消息发送后等待 ACK,超时未确认就重发。这样即使 ICE restart 了,消息也不会丢。

ICE restart 的完整流程

为了让你更直观地理解整个过程,我画了一张流程图:

ICE Restart 完整流程 ICE 连接正常 检测到连接异常 触发 ICE Restart 生成新 Offer (iceRestart) 信令通道交换 SDP 重新收集候选者 ICE 连接恢复 数据通道: OPEN 数据通道: CLOSING 数据通道: CLOSED 数据通道: 自动重建 数据通道: OPEN

实际项目中的最佳实践

说了这么多理论,来点实际的。我在做音视频 SDK 的时候,总结了几条经验:

  1. 不要频繁触发 ICE restart。每次 restart 都会导致短暂的连接中断,频繁触发反而影响体验。我一般设置一个冷却时间,比如 10 秒内最多触发一次。
  2. 结合连接状态监控。不要等到 ICE 状态变成 FAILED 才触发 restart。可以监控 RTT 和丢包率,提前预判。比如连续 3 秒 RTT 超过 500ms,就主动发起 restart。
  3. 应用层做好消息重传。前面说了,数据通道在 restart 期间会丢消息。我建议在应用层实现一个简单的消息队列,每条消息都带序列号,接收方收到后回复 ACK。超时未确认就重传。
  4. 信令通道要独立。千万别把信令通道放在数据通道上。ICE restart 时数据通道会断,信令也断了,那就彻底没法恢复了。

个人经验:我习惯在 RTCPeerConnectiononiceconnectionstatechange 回调里做状态机管理。状态变成 DISCONNECTED 时启动一个定时器,5 秒后如果还没恢复就触发 restart。状态变成 FAILED 时立即触发。这样既不会太激进,也不会反应太慢。

总结

ICE restart 是 WebRTC 连接恢复的核心机制。它不是什么黑魔法,就是一套标准化的重连流程。你想想看,网络环境这么复杂,用户可能在高铁上、电梯里、地下室,连接断掉是常态。关键是怎么优雅地恢复。

数据通道在 restart 期间会断开然后自动重建,但消息会丢失。所以应用层一定要做好消息确认和重传。这个坑我替你们踩过了,别再踩一次。

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