ICE 总览 · 怎么在两个 NAT 后打通一条路

两个都藏在 NAT 后的端点,怎么找到一条能互通的路?这就是 ICE 要解决的核心难题。这一章讲清 NAT 穿越的困境、ICE 的「收集候选→配对→连通性检查→选路」四步思路,以及 p2p/ 里那张核心类地图:P2PTransportChannel、Port、Candidate、Connection、PortAllocator、IceController。

作者 David更新于 第 16 篇(共 112 篇)

ICE 总览 · 怎么在两个 NAT 后打通一条路

第 2 部分,进入 WebRTC 最硬核的子系统之一:p2p/。会话协商谈定后,一个艰巨的任务摆在面前——两个可能都藏在 NAT / 防火墙后的端点,怎么找到一条能互相发包的路? 解决它的框架叫 ICE。这一章先建立全局观:难在哪、思路是什么、有哪些核心类。

难题:NAT 让直连几乎不可能

家里、公司、手机网络,几乎每台设备都在 NAT(网络地址转换) 后面——你的设备只有个私网地址(如 192.168.1.5),对外共享一个公网地址。NAT 带来两个麻烦:

  • 你不知道自己的公网地址:应用只看得到私网 IP,把它写进 SDP 发给对端也没用——对端没法用你的私网地址找到你。
  • NAT 默认拒绝主动入站:对端想连你,包到了你的 NAT 就被丢弃(没有对应的出站记录)。防火墙更严。

于是两个 NAT 后的端点直连,默认是通不了的。ICE 就是为攻克这个难题而生的框架。

ICE 的思路:多备选,逐个试

ICE(Interactive Connectivity Establishment,交互式连通性建立) 不指望「一条路走到黑」,而是广撒网、逐个试:

ICE 四步走:① 收集候选 → ② 交换 → ③ 配对做连通性检查 → ④ 选一条能通的用。

  1. 收集候选(candidates) —— 每一端尽可能多地找出「我可能被联系到的地址」:本机网卡地址、经 STUN 问出的公网地址、TURN 中继地址……(下一章细说四种候选)。
  2. 交换候选 —— 通过信令把候选发给对端(和 SDP 一样,信令是应用的事)。
  3. 配对 + 连通性检查 —— 把「我的候选」和「对端的候选」两两配对,对每一对发 STUN 探测包试试通不通。
  4. 选路 —— 从通了的配对里,选一条最优的用来传媒体。之后还持续探测,以防路况变化。

核心洞察:与其猜哪条路能通,不如把所有可能的路都试一遍,让「能通」这件事自己浮现出来。 这是一种务实的、面向真实互联网(充满 NAT 和防火墙)的工程哲学。

核心类地图

ICE 的实现,是 p2p/ 里一组类的协作。项目文档 g3doc/ice.md 给出了权威的角色划分,记住这张地图,整个第 2 部分你都不会迷路:

类canonical 路径角色
P2PTransportChannelp2p/base/p2p_transport_channel.h总指挥(实现 IceTransportInternal)
PortAllocatorp2p/base/port_allocator.h创建 Port、采集本地候选
Port(4 种实现)p2p/base/port.h一种本地通信机制,产出候选
Candidateapi/candidate.h一个可达地址(本地或远端)
Connectionp2p/base/connection.h管理一个候选对,发 STUN 探测
IceControllerp2p/base/ice_controller_interface.h决定用哪条 Connection 发数据

它们的协作关系:

图表加载中…

一句话概括各自的活:PortAllocator 造 Port、Port 产 Candidate;P2PTransportChannel 把本地和远端候选配成 Connection;Connection 发 STUN 探测;IceController 从中选路。 而 P2PTransportChannel 是把这一切串起来的总指挥——它实现了 IceTransportInternal 接口,是上一部分 JsepTransport 里那个 ICE 传输的真身。

建连五步(后续详解)

g3doc/ice.md 还给出了一次成功建连的五步时序,它们全由 PeerConnection 的动作触发。这里先给个骨架,后面各章逐个展开:

  1. MaybeStartGathering —— SetLocalDescription 触发,开始采集候选。
  2. SignalCandidateGathered —— 采到一个本地候选,通过回调报给应用(OnIceCandidate),应用经信令发给对端。
  3. AddRemoteCandidate —— 收到对端候选,和本地候选配对成 Connection,交给 IceController。
  4. SignalCandidatePairChanged —— 某个 Connection 的 STUN 探测得到回应,IceController 选它来传数据。
  5. SignalIceTransportStateChanged —— 选定连接使状态变化,触发 DTLS 握手开始(第 3 部分)。

小结与下一章

ICE 解决「两个 NAT 后的端点怎么直连」:因为设备不知道自己的公网地址、NAT 又拒绝主动入站,直连默认通不了。ICE 的思路是收集候选 → 交换 → 配对做连通性检查 → 选一条能通的。核心类:P2PTransportChannel(总指挥)用 PortAllocator 造 Port、Port 产 Candidate,把本地 × 远端候选配成 Connection(发 STUN 探测),再由 IceController 选路。

第一件事是候选——它到底有哪四种,分别怎么来?下一章细看。👉 候选:host / srflx / prflx / relay。

参考资料

  • [1] libwebrtc 源码.g3doc/ice.md(ICE 类地图与建连五步)。
  • [2] libwebrtc 源码.p2p/base/p2p_transport_channel.h、p2p/base/port.h、api/candidate.h、p2p/base/connection.h。
  • [3] IETF RFC 8445「Interactive Connectivity Establishment (ICE)」—— ICE 规范。

本页目录