8、ICE与NAT穿透:NAT类型、ICE候选者收集、连接性检查、ICE重启
说实话,WebRTC 里最让我头疼的就是 NAT 穿透。你想想看,两个浏览器要直接建立连接,中间却隔着各种路由器、防火墙,这活儿真不好干。我早期做项目时,经常遇到用户反馈「视频连不上」,排查到最后,十有八九都是 NAT 穿透的问题。
ICE(Interactive Connectivity Establishment)就是专门解决这个问题的。它像个聪明的中间人,帮两端找到一条能通的路。今天咱们就把这块掰开揉碎了讲清楚。
8.1 NAT 类型:你的路由器到底有多「严」?
先说说 NAT 是什么。简单讲,你家路由器把内网 IP 映射成公网 IP,这样才能上网。但不同的路由器,映射规则不一样。我习惯把 NAT 分成四种类型,越往后越难穿透。
| NAT 类型 | 特点 | 穿透难度 |
|---|---|---|
| 完全锥形 NAT | 只要映射过一次,外网任何主机都能通过这个映射访问内网 | 容易 |
| 受限锥形 NAT | 只有内网主机曾经发过包的目标 IP,才能从外网打进来 | 中等 |
| 端口受限锥形 NAT | 比上面更严,IP 和端口都得匹配 | 较难 |
| 对称 NAT | 每次发往不同目标,都分配不同的公网端口 | 极难 |
我在项目中遇到过最头疼的就是对称 NAT。有一次做视频会议,两边都是对称 NAT,ICE 折腾了半天还是走 TURN 中继了。说白了,对称 NAT 下 P2P 基本没戏,必须靠 TURN 服务器中转。
核心要点:判断 NAT 类型是 ICE 穿透的第一步。STUN 协议就是干这个的——它让客户端问服务器「我看到的公网地址是什么」,然后对比本地地址,就能推断出 NAT 类型。
8.2 ICE 候选者收集:找遍所有可能的「路」
ICE 的第一步,就是收集候选者(Candidate)。候选者就是可能的通信地址。我一般把它们分成三类:
- 主机候选者(Host Candidate):本机的 IP 地址,比如 192.168.1.100。这个最直接,但只在局域网有用。
- 反射候选者(Server Reflexive Candidate):通过 STUN 服务器获取的公网地址。比如你路由器的公网 IP + 映射端口。
- 中继候选者(Relay Candidate):通过 TURN 服务器分配的地址。所有数据都走服务器中转,成本高但最可靠。
收集过程其实挺有意思的。浏览器会同时向 STUN 和 TURN 服务器发请求,拿到结果后,把所有候选者打包成 SDP 发给对方。嗯,这里要注意:候选者是有优先级的。ICE 协议里有一套计算公式,一般主机候选者优先级最高,反射次之,中继最低。为什么?因为能直连就别绕路嘛。
我的经验:调试时可以用 chrome://webrtc-internals 查看候选者收集情况。如果发现只有 host 候选者,说明 STUN/TURN 配置可能有问题。我曾经排查过一个线上问题,就是 TURN 服务器地址写错了,导致所有用户都走不了中继。
8.3 连接性检查:一条一条路试过去
候选者收集完了,接下来就是连接性检查。说白了,就是两端互相发 STUN 探测包,看看哪条路能通。
ICE 会按照优先级排序,从最高优先级的候选者对开始试。比如先试 host-host,不行再试 host-reflexive,再不行就 reflex-reflex,最后才试 relay-relay。
检查过程是这样的:
- 发起方发送 STUN Binding Request,带上自己的候选者信息
- 接收方收到后,回复 STUN Binding Response
- 如果收到回复,说明这条路径通了,选它作为最终路径
- 如果超时没回复,换下一对候选者继续试
你想想看,如果两边各有 3 个候选者,那最多要试 9 对组合。不过实际中不会全试,因为 ICE 有「剪枝」机制——一旦找到一条可用的,就停止检查了。
注意:连接性检查是有超时时间的。默认情况下,每个检查对等待 500ms 没回应就算失败。如果网络状况差,这个时间可能不够。我建议在弱网环境下适当调大超时时间,比如设成 1.5 秒。
8.4 ICE 重启:连接断了怎么办?
ICE 连接建立后,不是一劳永逸的。网络环境会变——比如用户从 WiFi 切到 4G,或者路由器重启了。这时候就需要 ICE 重启。
ICE 重启的触发条件主要有:
- 网络接口变化(比如切换网络)
- ICE 连接超时(长时间收不到对方消息)
- 应用层主动要求(比如用户点了「重新连接」)
重启过程其实和初始连接差不多:重新收集候选者,重新做连接性检查。但有个关键区别——ICE 重启会生成新的 ufrag 和 pwd(用户名和密码),这样旧连接的数据包就不会干扰新连接。
我记得有一次做直播项目,用户反馈看一会儿就卡住不动了。排查发现是 ICE 连接断了,但没有触发重启。后来加了 ICE 连接状态监测,一旦发现状态变成 FAILED,立即触发重启,问题就解决了。
避坑指南:我曾经在 ICE 重启时忘记更新 SDP,导致对端还在用旧的 ufrag 验证包,结果所有包都被丢弃了。记住:ICE 重启一定要重新协商 SDP,把新的 ufrag 和 pwd 告诉对方。
8.5 知识体系总览
下面这张图把 ICE 与 NAT 穿透的核心流程串起来了。我画图时习惯把关键步骤和决策点都标清楚,方便你对照理解。
这张图把整个流程串起来了。从 NAT 类型检测开始,到候选者收集、排序配对、连接性检查,最后是 ICE 重启。你跟着这个流程走,基本能搞定大部分穿透问题。
说实话,ICE 这块内容挺多的,但核心就这几个步骤。我建议你在实际项目中多抓包看看,用 Wireshark 过滤 STUN 协议,观察候选者交换和连接检查的过程,比看十遍文档都管用。
小技巧:调试 ICE 时,可以开启 WebRTC 的日志:chrome://webrtc-internals。里面能看到候选者列表、连接检查状态、ICE 状态变化。我排查问题时第一件事就是打开这个页面。