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)是最基础的穿透工具。它的工作流程很简单:
- 客户端向公网STUN服务器发请求
- STUN服务器看到请求的源IP和端口,原样返回给客户端
- 客户端就知道自己在公网上的地址了
这个地址叫「反射地址」(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的工作流程分三步:
- 收集候选地址(Candidate Gathering):每个端收集自己的主机地址、反射地址(STUN获取)、中继地址(TURN分配)
- 交换候选地址:通过信令服务器把候选列表发给对端
- 连通性检查(Connectivity Check):两端互相发STUN探测包,看哪条路径能通
ICE会按优先级排序:主机地址 > 反射地址 > 中继地址。能直连就直连,不行再走中继。
ICE的完整工作流程
下面这张图把整个流程串起来了,我建议你多看几遍:
候选地址优先级与选择策略
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参数。这个后面讲到高级调优时会细说。