4. ICE 连接失败排查:Candidate 收集不全、STUN/TURN 服务器不可达、NAT 类型不匹配

ICE 连接失败,是 WebRTC 开发里最让人头疼的问题之一。我刚开始做嵌入式 WebRTC 的时候,有一半的调试时间都花在跟 ICE 较劲上。说白了,ICE 就是帮两个设备在复杂的网络环境里找到一条能通的路。路没找到,或者找到的路是断的,通话自然就建立不起来。

这一章,我们聚焦三个最常见的 ICE 失败原因:Candidate 收集不全、STUN/TURN 服务器不可达、NAT 类型不匹配。我会结合项目里的实际案例,带你一步步定位问题。

4.1 Candidate 收集不全

ICE 的第一步,是收集本端所有的网络候选地址(Candidate)。这些 Candidate 包括:本机 IP(host)、STUN 反射地址(srflx)、TURN 中继地址(relay)。如果收集不全,后续的连通性检查就无从谈起。

4.1.1 典型表现

在日志里,你会看到类似这样的输出:

ICE: gathering candidates
ICE: found host candidate: 192.168.1.100:49152
ICE: found host candidate: 10.0.0.2:49153
ICE: gathering done

注意,这里只有 host 类型的 Candidate,没有 srflx 和 relay。这意味着 STUN/TURN 服务器要么没配置,要么配置了但没生效。

⚠️ 常见误区: 很多开发者以为只要配置了 ICE 服务器地址,系统就会自动收集 srflx 和 relay。实际上,如果网络不通或配置格式错误,ICE 层会静默跳过,只返回 host 类型。

4.1.2 排查步骤

  1. 检查 ICE 服务器配置:确认 iceServers 字段是否正确填写。我见过有人把 STUN 和 TURN 的 URL 写反了,结果折腾了半天。
  2. 验证 DNS 解析:在嵌入式设备上,DNS 解析可能受限。手动用 nslookup 或 ping 测试 STUN/TURN 域名是否能解析。
  3. 检查防火墙/代理:有些嵌入式网络环境会拦截 UDP 或特定端口的流量。STUN 默认用 3478 端口,TURN 可能用 3478 或 5349。
  4. 查看 ICE 日志级别:把日志级别调到 RTC_LOG_LEVEL_VERBOSE,你会看到更详细的 Candidate 收集过程。
💡 我的习惯: 在调试阶段,我会在代码里加一个回调,把每次收集到的 Candidate 类型和数量打印出来。这样一眼就能看出少了什么。

4.2 STUN/TURN 服务器不可达

STUN 和 TURN 服务器是 ICE 流程里的关键角色。STUN 帮你拿到公网地址,TURN 则作为最后的 relay 保底。如果它们不可达,ICE 就只能靠 host 和直连,成功率会大幅下降。

4.2.1 如何判断服务器不可达

在 WebRTC 日志里,搜索 STUN 或 TURN 关键字。如果看到类似下面的信息,说明服务器有问题:

STUN: server 192.168.1.1:3478 unreachable
TURN: allocation request failed, error code: 401

第一行表示网络层不可达,第二行表示服务器返回了认证错误(401)。

4.2.2 常见原因

原因 日志特征 解决方案
网络不通 unreachable 或 timeout 检查路由、防火墙、端口是否开放
认证失败 401 或 403 检查用户名/密码是否正确
服务器过载 503 或 allocation denied 更换服务器或增加带宽
协议不匹配 unsupported protocol 确认 STUN/TURN 版本兼容

我记得有一次,客户反馈设备无法建立通话。我远程上去一看,日志里全是 STUN timeout。后来发现,他们的网络策略把 UDP 3478 端口给封了。改成 TCP 3478 后,问题就解决了。

🔑 关键点: 嵌入式设备经常部署在受限网络里。建议同时配置 UDP 和 TCP 两种传输方式,增加容错性。

4.3 NAT 类型不匹配

NAT 类型决定了两个设备能否直接建立 P2P 连接。如果双方的 NAT 类型不兼容,ICE 的连通性检查就会失败,最终只能依赖 TURN 中继。

4.3.1 NAT 类型分类

常见的 NAT 类型有四种:

  • Full Cone:最开放,任何外部主机都能通过映射地址访问内网主机。
  • Restricted Cone:只有内网主机曾经发过包的外部主机才能回包。
  • Port Restricted Cone:比 Restricted 更严格,还限制了端口。
  • Symmetric:最严格,每个外部目标都映射到不同的端口。

4.3.2 不匹配的场景

当一方是 Symmetric NAT,另一方是 Restricted Cone 时,ICE 的连通性检查大概率会失败。因为 Symmetric NAT 每次映射的端口都不同,对方无法预测。

在日志里,你会看到类似这样的模式:

ICE: check candidate pair: 192.168.1.100:49152 <-> 203.0.113.5:3478
ICE: check state: FAILED
ICE: check candidate pair: 192.168.1.100:49153 <-> 203.0.113.5:3478
ICE: check state: FAILED

注意,每次检查的本地端口都在变,这就是 Symmetric NAT 的典型特征。

4.3.3 如何应对

说实话,NAT 类型不匹配没有完美的解决方案。我的做法是:

  1. 优先使用 TURN:如果检测到 Symmetric NAT,直接走 TURN 中继,避免浪费时间做 P2P 尝试。
  2. 调整 ICE 策略:把 iceTransportPolicy 设为 relay,强制使用 TURN。
  3. 增加 Candidate 优先级:给 relay 类型的 Candidate 设置更高的优先级,让 ICE 优先选择它。
⚠️ 注意: 强制使用 TURN 会增加服务器带宽成本。如果设备数量多,建议先做 NAT 类型检测,再动态决定策略。

4.4 知识结构图:ICE 连接失败排查流程

下面这张图总结了 ICE 连接失败的排查路径,你可以把它贴在工位上,遇到问题照着查。

ICE 连接失败排查流程图 ICE 连接失败排查流程 ICE 连接失败 检查 Candidate 是否完整 有 srflx/relay? 检查 STUN/TURN 可达性 检查 ICE 配置 检查 NAT 类型 修复配置后重试 问题定位完成 提示:如果 Candidate 完整但连接仍失败,重点检查 NAT 类型和防火墙策略

4.5 实战案例:一个典型的 ICE 失败排查

去年我帮一个客户调试嵌入式设备,症状是:设备能注册到服务器,但就是打不通电话。日志里 Candidate 只有 host 类型,没有 srflx 和 relay。

我按上面的流程走了一遍:

  1. 检查配置:iceServers 里填了 STUN 地址,但格式写错了——用了 stun: 前缀,但服务器实际只支持 turn: 协议。
  2. 修复配置:改成 turn:stun.example.com:3478 后,srflx 和 relay 都出来了。
  3. 验证连通性:用 tcpdump 抓包,确认设备能正常发送 STUN 请求并收到响应。

嗯,问题就这么解决了。很多时候,ICE 失败的原因并不复杂,只是我们没按流程一步步查。

💡 避坑指南: 我曾经因为 STUN 服务器的域名解析超时,浪费了整整一天。后来我在代码里加了一个 DNS 预解析的步骤,在 ICE 启动前先解析好所有域名。这个小改动,帮我省了不少事。

4.6 总结

ICE 连接失败,说白了就是三个问题:Candidate 没收集全、STUN/TURN 服务器连不上、NAT 类型不匹配。每个问题都有对应的日志特征和排查方法。你只要按流程走,大部分问题都能在半小时内定位。

记住,日志是你最好的朋友。把日志级别调高,多看几遍,你很快就能找到规律。