9、ICE连接:ICE候选类型、ICE连接状态监控、ICE重启机制

ICE,全称 Interactive Connectivity Establishment(交互式连接建立)。

说白了,它就是 WebRTC 的「探路者」。两个浏览器要建立 P2P 连接,中间隔着 NAT、防火墙、各种复杂的网络环境。ICE 的作用就是帮它们找到一条能通的路。

我个人习惯把 ICE 比作「快递员」。它要找到你的地址,如果门牌号不对(NAT 映射失败),它还得换个方式再试。今天我们就来聊聊这个快递员是怎么工作的。

9.1 ICE 候选类型:三种「地址」

ICE 在收集候选地址时,会找到三种类型的候选者。我刚开始接触时总觉得记不住,后来用了一个比喻就清楚了:

  • Host 候选(主机候选):你本机的 IP 地址。比如你电脑的局域网 IP 192.168.1.100。这是最直接的地址,但只能在同一个局域网内使用。
  • Srflx 候选(服务器反射候选):通过 STUN 服务器获取的公网 IP 和端口。比如你的路由器映射出来的公网地址 8.8.8.8:3478。这是最常见的 P2P 连接方式。
  • Relay 候选(中继候选):通过 TURN 服务器转发的地址。当 P2P 直连失败时,数据会经过 TURN 服务器中转。这是最后的保底方案。

优先级规则:Host > Srflx > Relay。ICE 会优先尝试延迟最低的候选者。

我在项目中遇到过一个问题:某个用户的网络环境非常严格,Host 和 Srflx 都失败了,最后只能走 Relay。但 Relay 的带宽有限,导致视频卡顿。嗯,这里要注意,如果你的应用对实时性要求高,一定要提前评估 TURN 服务器的带宽成本。

9.2 ICE 连接状态监控:别等出问题了再查

ICE 连接不是一成不变的。网络波动、NAT 映射超时、用户切换网络,都可能导致连接状态变化。所以,监控 ICE 状态是每个 WebRTC 应用的必修课。

WebRTC 提供了 iceConnectionStateiceGatheringState 两个属性来监控状态。

9.2.1 iceConnectionState 的几种状态

状态 说明 我的经验
new 初始状态,还没开始连接 刚创建 PeerConnection 时就是这个状态
checking 正在检查候选者是否可用 这个阶段通常很快,但如果网络差可能会卡住
connected 至少找到一条可用连接 恭喜,P2P 通了!
completed 所有候选者都检查完毕 连接稳定,可以放心了
disconnected 连接暂时断开 可能是网络抖动,ICE 会尝试恢复
failed 连接彻底失败 需要触发 ICE 重启或提示用户
closed 连接已关闭 PeerConnection 被销毁了

我的建议:不要只监听 failed 状态。很多时候 disconnected 持续几秒后就会变成 failed。我习惯在 disconnected 状态持续 5 秒后主动触发 ICE 重启,而不是干等。

9.2.2 代码示例:监听 ICE 状态变化

const pc = new RTCPeerConnection(config);

pc.oniceconnectionstatechange = () => {
  console.log('ICE 状态:', pc.iceConnectionState);
  
  switch(pc.iceConnectionState) {
    case 'disconnected':
      // 启动一个 5 秒的定时器
      this.disconnectTimer = setTimeout(() => {
        console.warn('连接断开超过 5 秒,尝试 ICE 重启');
        this.restartIce();
      }, 5000);
      break;
    case 'connected':
    case 'completed':
      // 连接恢复,清除定时器
      if (this.disconnectTimer) {
        clearTimeout(this.disconnectTimer);
        this.disconnectTimer = null;
      }
      break;
    case 'failed':
      // 彻底失败,提示用户
      this.showError('网络连接失败,请检查网络');
      break;
  }
};

9.3 ICE 重启机制:断网后的「救命稻草」

为什么会需要 ICE 重启?

你想想看,用户可能从 WiFi 切换到 4G,或者 NAT 映射超时了。这时候原来的候选地址就失效了。ICE 重启就是重新收集候选者,重新建立连接。

9.3.1 什么时候触发 ICE 重启?

  • 网络切换:检测到 IP 地址变化时
  • 连接断开超时disconnected 状态持续超过阈值
  • 用户主动操作:比如点击「重新连接」按钮
  • 业务层检测到丢包严重:即使 ICE 状态还是 connected,但实际传输质量很差

注意:ICE 重启不是万能的。如果用户的网络环境本身就不支持 P2P(比如严格的对称 NAT),重启多少次都没用。这时候只能走 TURN 中继。

9.3.2 如何实现 ICE 重启?

ICE 重启的核心是调用 createOffer 时传入 { iceRestart: true } 参数。这会告诉浏览器:重新收集候选者,不要用旧的。

async function restartIce(pc) {
  try {
    // 创建带 iceRestart 标志的 offer
    const offer = await pc.createOffer({ iceRestart: true });
    await pc.setLocalDescription(offer);
    
    // 通过信令服务器发送给远端
    signalingServer.send({
      type: 'ice-restart',
      sdp: offer.sdp
    });
    
    console.log('ICE 重启已触发');
  } catch (err) {
    console.error('ICE 重启失败:', err);
  }
}

我曾经在一个项目中遇到过一个问题:ICE 重启后,远端没有正确处理新的 offer,导致连接一直卡在 checking 状态。后来发现是信令服务器没有把 iceRestart 标志透传给远端。嗯,这里要注意,ICE 重启需要两端配合,远端收到新的 offer 后要重新设置 setRemoteDescription

9.4 知识体系总览

下面这张图总结了 ICE 连接的核心逻辑。我习惯把它画在纸上,每次排查问题时就对照着看。

ICE 连接核心流程 候选类型收集 Host(本机IP) Srflx(STUN反射) Relay(TURN中继) 连接建立 优先级排序 连通性检查 选择最佳路径 状态监控 connected disconnected failed 失败? ICE 重启 重新收集候选者 重新收集

9.5 避坑指南:我踩过的三个坑

做 WebRTC 这么多年,ICE 相关的坑我踩了不少。挑三个最典型的分享给你:

  1. 不要频繁触发 ICE 重启:我曾经在 disconnected 状态下一秒触发一次重启,结果信令服务器被打爆了。后来加了防抖,5 秒内只触发一次。
  2. 注意 STUN/TURN 服务器的配置:有些开发者只配了 STUN,没配 TURN。结果在严格网络环境下,ICE 一直失败。我建议至少配一个 TURN 服务器作为保底。
  3. ICE 重启后要更新 SDP:重启后生成的 SDP 和之前的不一样。如果你直接复用旧的 SDP,远端会拒绝连接。我吃过这个亏,排查了半天才发现是 SDP 没更新。

总结一下:ICE 连接是 WebRTC 的基石。理解候选类型、监控状态变化、掌握重启机制,你就能应对大部分网络问题。记住,没有万能的方案,但 ICE 给了我们最大的容错空间。

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