13、ICE协议:ICE工作原理、Candidate类型(host、srflx、relay)、ICE连接状态机、ICE重启
ICE协议,说白了就是WebRTC里的“媒人”。它帮两个浏览器找到一条能通的路。你想想看,两个设备在不同的网络里,中间可能有防火墙、有NAT、有各种乱七八糟的网络设备。怎么让它们直接连上?这就是ICE要解决的问题。
我记得刚接触WebRTC那会儿,觉得ICE特别神秘。后来在项目里踩了不少坑,才慢慢摸清楚它的脾气。今天我就把ICE的核心内容给你讲透。
ICE到底在干什么?
ICE的全称是Interactive Connectivity Establishment,交互式连接建立。名字挺长,但核心逻辑很简单:收集所有可能的连接方式,然后一个个试,直到找到能用的那条路。
打个比方。你要从北京去上海,可以坐飞机、坐高铁、自己开车、甚至骑自行车。ICE就是帮你把所有方案列出来,然后按优先级一个个试。飞机最快,先试;不行就试高铁;再不行就试开车。总有一条能到。
在WebRTC里,ICE做三件事:
- 收集Candidate——找出所有可能的连接地址
- 排序和探测——按优先级尝试连接
- 选出一组最佳路径——确定最终的通信链路
核心要点:ICE不是一次性的。网络环境变了,ICE会重新跑一遍流程。这就是后面要讲的ICE重启。
Candidate类型:三种“候选人”
ICE收集到的每个连接地址,叫做一个Candidate。Candidate分三种类型,我一个个说。
Host Candidate(主机候选)
这是最直接的。就是设备本身的IP地址和端口。比如你电脑的192.168.1.100:5000。这种Candidate优先级最高,因为不需要经过任何中间设备,延迟最小。
但问题来了。如果两个设备在同一个局域网里,host candidate直接就能用。可一旦跨了网络,比如一个在公司内网,一个在家里,host candidate就废了——因为对方的局域网IP在你这边根本不可达。
我的经验:在调试阶段,我习惯先看host candidate能不能通。如果连host都不通,那大概率是网络配置有问题,而不是ICE的锅。
Srflx Candidate(Server Reflexive,服务器反射候选)
这个稍微绕一点。设备向一个STUN服务器发请求,STUN服务器看到请求的源地址(公网IP和端口),然后把这个地址返回给设备。这个地址就是srflx candidate。
说白了,就是“你的公网地址是什么”。有了这个地址,外网的设备就能直接连到你。
但这里有个坑。很多企业网络有对称NAT(Symmetric NAT),STUN服务器看到的地址和实际对端看到的地址可能不一样。这种情况下,srflx candidate就不好使了。
注意:srflx candidate依赖STUN服务器。如果STUN服务器挂了或者网络不通,这个类型的candidate就收集不到。我在项目里遇到过STUN服务器被防火墙拦截的情况,排查了好久才发现。
Relay Candidate(中继候选)
这是最后的保底方案。如果host和srflx都试过了,还是连不上,那就走TURN服务器中转。
Relay candidate的地址是TURN服务器分配的一个公网地址。所有数据都经过TURN服务器转发。延迟肯定高,带宽也受限,但至少能通。
三种candidate的优先级是这样的:
| 类型 | 优先级 | 延迟 | 依赖 |
|---|---|---|---|
| Host | 最高 | 最低 | 无 |
| Srflx | 中等 | 中等 | STUN服务器 |
| Relay | 最低 | 最高 | TURN服务器 |
一句话总结:能直连就直连,直连不了就反射,反射还不行就中继。ICE的哲学就是“尽最大努力找到最优路径”。
ICE连接状态机
ICE不是一锤子买卖。它有一个完整的状态机,从开始到结束,中间可能经历多个状态。我画了一张图,你看一眼就明白了。
状态机里几个关键点:
- New:刚创建,还没开始收集candidate
- Checking:正在收集和探测candidate。这个阶段最耗时
- Connected:至少找到一对可用的candidate,但可能还有更好的没试完
- Completed:所有candidate都检查完了,选出了最优路径
- Failed:所有candidate都试过了,没一个能通
- Disconnected:之前连上了,但后来断了,而且一段时间内没恢复
我建议:在实际开发中,监听ICE连接状态的变化非常有用。比如状态变成Failed,你可以主动触发ICE重启。状态变成Disconnected,你可以给用户一个提示,而不是直接断掉连接。
ICE重启:什么时候用?怎么用?
ICE重启,说白了就是让ICE重新跑一遍完整的流程。重新收集candidate,重新探测,重新选路。
什么时候需要重启?我遇到过几种情况:
- 网络切换:比如用户从WiFi切到了4G,IP地址变了,原来的连接肯定断了
- 连接质量严重下降:延迟突然飙升到几秒,丢包率超过50%,这时候重启可能找到更好的路径
- ICE状态长时间卡在Disconnected:等了十几秒还没恢复,不如主动重启
代码里怎么触发ICE重启?很简单:
// 触发ICE重启
peerConnection.restartIce();
// 或者通过createOffer时设置ICE restart标志
const offer = await peerConnection.createOffer({ iceRestart: true });
await peerConnection.setLocalDescription(offer);
我曾经踩过的坑:ICE重启不是无成本的。重启期间,音视频会短暂中断。所以不要频繁触发重启。我见过有人每5秒重启一次,结果通话全程都在卡顿。正确的做法是:先尝试恢复,恢复不了再重启。
ICE的完整工作流程
把上面所有知识点串起来,ICE的完整流程是这样的:
- 应用层创建RTCPeerConnection,ICE agent随之启动
- ICE agent收集所有candidate:host、srflx(通过STUN)、relay(通过TURN)
- candidate通过信令通道发送给对端(SDP交换)
- 双方各自对收到的candidate进行配对,形成candidate pair
- 按优先级对candidate pair进行连通性检查(STUN binding请求)
- 找到第一对可用的pair后,状态变为Connected
- 继续检查剩余的pair,确保没有更好的路径
- 所有pair检查完毕,状态变为Completed
- 后续如果网络变化,触发ICE重启,回到步骤2
嗯,到这里ICE的核心内容就讲完了。ICE是WebRTC里最复杂的部分之一,但也是最重要的。理解了ICE,你就理解了WebRTC连接的本质。
一句话记住ICE:收集所有可能的路,按优先级试,选最好的走。路断了就重新找。
公众号:蓝海资料掘金营,微信deep3321