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 提供了 iceConnectionState 和 iceGatheringState 两个属性来监控状态。
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 连接的核心逻辑。我习惯把它画在纸上,每次排查问题时就对照着看。
9.5 避坑指南:我踩过的三个坑
做 WebRTC 这么多年,ICE 相关的坑我踩了不少。挑三个最典型的分享给你:
- 不要频繁触发 ICE 重启:我曾经在
disconnected状态下一秒触发一次重启,结果信令服务器被打爆了。后来加了防抖,5 秒内只触发一次。 - 注意 STUN/TURN 服务器的配置:有些开发者只配了 STUN,没配 TURN。结果在严格网络环境下,ICE 一直失败。我建议至少配一个 TURN 服务器作为保底。
- ICE 重启后要更新 SDP:重启后生成的 SDP 和之前的不一样。如果你直接复用旧的 SDP,远端会拒绝连接。我吃过这个亏,排查了半天才发现是 SDP 没更新。
总结一下:ICE 连接是 WebRTC 的基石。理解候选类型、监控状态变化、掌握重启机制,你就能应对大部分网络问题。记住,没有万能的方案,但 ICE 给了我们最大的容错空间。