ICE 总览 · 怎么在两个 NAT 后打通一条路
两个都藏在 NAT 后的端点,怎么找到一条能互通的路?这就是 ICE 要解决的核心难题。这一章讲清 NAT 穿越的困境、ICE 的「收集候选→配对→连通性检查→选路」四步思路,以及 p2p/ 里那张核心类地图:P2PTransportChannel、Port、Candidate、Connection、PortAllocator、IceController。
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 四步走:① 收集候选 → ② 交换 → ③ 配对做连通性检查 → ④ 选一条能通的用。
- 收集候选(candidates) —— 每一端尽可能多地找出「我可能被联系到的地址」:本机网卡地址、经 STUN 问出的公网地址、TURN 中继地址……(下一章细说四种候选)。
- 交换候选 —— 通过信令把候选发给对端(和 SDP 一样,信令是应用的事)。
- 配对 + 连通性检查 —— 把「我的候选」和「对端的候选」两两配对,对每一对发 STUN 探测包试试通不通。
- 选路 —— 从通了的配对里,选一条最优的用来传媒体。之后还持续探测,以防路况变化。
核心洞察:与其猜哪条路能通,不如把所有可能的路都试一遍,让「能通」这件事自己浮现出来。 这是一种务实的、面向真实互联网(充满 NAT 和防火墙)的工程哲学。
核心类地图
ICE 的实现,是 p2p/ 里一组类的协作。项目文档 g3doc/ice.md 给出了权威的角色划分,记住这张地图,整个第 2 部分你都不会迷路:
| 类 | canonical 路径 | 角色 |
|---|---|---|
P2PTransportChannel | p2p/base/p2p_transport_channel.h | 总指挥(实现 IceTransportInternal) |
PortAllocator | p2p/base/port_allocator.h | 创建 Port、采集本地候选 |
Port(4 种实现) | p2p/base/port.h | 一种本地通信机制,产出候选 |
Candidate | api/candidate.h | 一个可达地址(本地或远端) |
Connection | p2p/base/connection.h | 管理一个候选对,发 STUN 探测 |
IceController | p2p/base/ice_controller_interface.h | 决定用哪条 Connection 发数据 |
它们的协作关系:
一句话概括各自的活:PortAllocator 造 Port、Port 产 Candidate;P2PTransportChannel 把本地和远端候选配成 Connection;Connection 发 STUN 探测;IceController 从中选路。 而 P2PTransportChannel 是把这一切串起来的总指挥——它实现了 IceTransportInternal 接口,是上一部分 JsepTransport 里那个 ICE 传输的真身。
建连五步(后续详解)
g3doc/ice.md 还给出了一次成功建连的五步时序,它们全由 PeerConnection 的动作触发。这里先给个骨架,后面各章逐个展开:
MaybeStartGathering——SetLocalDescription触发,开始采集候选。SignalCandidateGathered—— 采到一个本地候选,通过回调报给应用(OnIceCandidate),应用经信令发给对端。AddRemoteCandidate—— 收到对端候选,和本地候选配对成Connection,交给IceController。SignalCandidatePairChanged—— 某个Connection的 STUN 探测得到回应,IceController选它来传数据。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 规范。