P2P连接基础:NAT穿透原理、STUN/TURN/ICE协议的基本概念与工作流程

做WebRTC开发,第一个绕不开的坎就是NAT穿透。说白了,就是两个设备在不同的局域网里,怎么找到对方并建立直接连接。我刚开始接触这块时也觉得挺玄乎的,但搞懂了背后的几个协议,其实没那么复杂。

为什么需要NAT穿透?

先想想现实情况。现在大部分设备都在路由器后面,路由器会做网络地址转换(NAT)。你设备的内网IP是192.168.1.x这种,公网上根本认不了。两个设备想直接通信,得先知道对方的公网IP和端口。

但问题来了——路由器不会随便让外网的数据包闯进来。它会建一张映射表,只有你主动发过请求的对端,回来的数据才放行。这就是NAT的基本逻辑。

核心矛盾:WebRTC需要端到端直连,但NAT把两端隔开了。不解决这个,音视频数据就只能绕远路。

NAT的四种类型

不是所有NAT都一样。我踩过坑才知道,不同类型的NAT,穿透难度天差地别。简单分一下:

NAT类型 映射行为 穿透难度
完全锥形(Full Cone) 内网IP:端口映射到固定公网IP:端口,谁都能通过这个公网地址访问 容易
受限锥形(Restricted Cone) 只允许内网曾发过数据的目标IP访问 中等
端口受限锥形(Port Restricted Cone) 限制更严,必须IP和端口都匹配 较难
对称NAT(Symmetric NAT) 每次发往不同目标,公网端口都可能变 极难

对称NAT基本没法用常规手段穿透。我遇到过一家客户,他们的办公网络就是对称NAT,折腾了半天最后还是走了TURN中继。

STUN协议:帮设备找到自己的公网地址

STUN(Session Traversal Utilities for NAT)是最基础的穿透工具。它的工作流程很简单:

  1. 客户端向公网STUN服务器发请求
  2. STUN服务器看到请求的源IP和端口,原样返回给客户端
  3. 客户端就知道自己在公网上的地址了

这个地址叫「反射地址」(Reflexive Address)。拿到之后,客户端就可以把它通过信令服务器发给对端。

我的经验:STUN只帮你发现地址,不保证能穿透。尤其对称NAT下,STUN拿到的地址可能根本用不了。所以别把STUN当万能药。

TURN协议:最后的保底方案

当STUN穿透失败时,TURN(Traversal Using Relays around NAT)就上场了。它在中转服务器上分配一个公网地址,所有数据都经过这个服务器转发。

代价很明显——带宽和延迟都增加了。我做过压测,一路TURN中继的延迟比直连高30-50ms,服务器带宽成本也翻倍。所以我的原则是:能直连绝不走TURN。

注意:TURN服务器需要消耗大量带宽。一个720p视频流大约需要1.5Mbps,如果100个用户同时走TURN,服务器出口带宽得准备150Mbps以上。预算要提前算好。

ICE协议:把一切串起来

ICE(Interactive Connectivity Establishment)是WebRTC里的「交通指挥官」。它不自己干活,而是协调STUN和TURN,帮两端找到最优路径。

ICE的工作流程分三步:

  1. 收集候选地址(Candidate Gathering):每个端收集自己的主机地址、反射地址(STUN获取)、中继地址(TURN分配)
  2. 交换候选地址:通过信令服务器把候选列表发给对端
  3. 连通性检查(Connectivity Check):两端互相发STUN探测包,看哪条路径能通

ICE会按优先级排序:主机地址 > 反射地址 > 中继地址。能直连就直连,不行再走中继。

ICE的完整工作流程

下面这张图把整个流程串起来了,我建议你多看几遍:

ICE完整工作流程 ICE协议完整工作流程 Peer A(发起端) Peer B(应答端) 信令服务器 ① 收集候选地址 ① 收集候选地址 STUN TURN STUN TURN ② 交换候选地址 SDP Offer SDP Answer ③ 连通性检查 ③ 连通性检查 STUN探测(优先级排序) ✅ 选择最优路径:直连 > 反射 > TURN中继

候选地址优先级与选择策略

ICE给每种候选地址定了优先级,数值越高越优先:

候选类型 优先级范围 说明
主机候选(Host) 2130706431 本机网卡IP,延迟最低
反射候选(Server Reflexive) 1694498815 STUN获取的公网地址
中继候选(Relay) 33554431 TURN分配的中继地址

ICE会按优先级从高到低尝试连通性检查。一旦某条路径通了,就立刻用起来。如果后面发现更高优先级的路径也通了,还会切换——这叫「ICE重启」。

避坑指南:我曾经遇到一个情况,ICE选了反射地址但实际延迟比中继还高。后来发现是运营商做了某种流量整形。所以别完全迷信优先级,实际生产中建议做一下路径质量评估,必要时手动调整策略。

实际开发中的ICE配置

在WebRTC里配置ICE很简单,但有几个参数值得注意:

// 创建RTCPeerConnection时配置ICE
const config = {
  iceServers: [
    { urls: 'stun:stun.l.google.com:19302' },
    {
      urls: 'turn:turn.example.com:3478',
      username: 'user',
      credential: 'pass'
    }
  ],
  iceTransportPolicy: 'all' // 'all' 或 'relay'
};

const pc = new RTCPeerConnection(config);

iceTransportPolicy设为'relay'会强制所有流量走TURN。调试时可以用,但线上别这么干——成本扛不住。

NAT穿透的成功率

根据我多年的实测数据,不同场景下的穿透成功率大致如下:

场景 STUN穿透成功率 STUN+TURN兜底成功率
双方都在公网 100% 100%
一方在锥形NAT后 ~85% ~99%
双方在锥形NAT后 ~70% ~98%
一方在对称NAT后 ~30% ~95%
双方在对称NAT后 <5% ~90%

看到没?对称NAT对STUN几乎是绝杀。但加上TURN兜底,整体成功率能拉到90%以上。这就是为什么ICE一定要同时配STUN和TURN。

重要提醒:别以为配了TURN就万事大吉。TURN服务器本身可能成为瓶颈。我见过一个案例,TURN服务器部署在AWS us-east-1,但用户都在东南亚,延迟飙到300ms以上。TURN服务器的地理位置一定要靠近用户。

小结

NAT穿透是WebRTC的基石。STUN帮你找地址,TURN给你兜底,ICE负责统筹决策。搞懂这三者的关系,P2P连接这块就算入门了。

嗯,这里要注意一点:实际开发中别把ICE配置写死。不同网络环境的最佳策略不一样。我习惯在客户端做一次网络探测,根据NAT类型动态调整ICE参数。这个后面讲到高级调优时会细说。