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。

检查过程是这样的:

  1. 发起方发送 STUN Binding Request,带上自己的候选者信息
  2. 接收方收到后,回复 STUN Binding Response
  3. 如果收到回复,说明这条路径通了,选它作为最终路径
  4. 如果超时没回复,换下一对候选者继续试

你想想看,如果两边各有 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 穿透的核心流程串起来了。我画图时习惯把关键步骤和决策点都标清楚,方便你对照理解。

ICE 与 NAT 穿透核心流程 NAT 类型检测 STUN 协议获取公网地址 候选者收集 Host / Reflexive / Relay 候选者排序与配对 按优先级排列 连接性检查 STUN Binding Request/Response 通? ✅ 建立 P2P 连接 选最高优先级可用路径 换下一对候选者 或降级到 TURN 中继 ICE 重启 网络变化 / 连接超时 NAT检测 候选者收集 排序配对 连接检查 ICE重启

这张图把整个流程串起来了。从 NAT 类型检测开始,到候选者收集、排序配对、连接性检查,最后是 ICE 重启。你跟着这个流程走,基本能搞定大部分穿透问题。

说实话,ICE 这块内容挺多的,但核心就这几个步骤。我建议你在实际项目中多抓包看看,用 Wireshark 过滤 STUN 协议,观察候选者交换和连接检查的过程,比看十遍文档都管用。

小技巧:调试 ICE 时,可以开启 WebRTC 的日志:chrome://webrtc-internals。里面能看到候选者列表、连接检查状态、ICE 状态变化。我排查问题时第一件事就是打开这个页面。