LOGO 首页 OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 技术文档 其他文档  
 
网站管理员

图解网络穿透:NAT、STUN、TURN

admin
2026年7月18日 23:29 本文热度 35

网络穿透这块知识,看着零散琐碎,实则逻辑闭环极强。

今天这篇文章,我想从NAT底层机制,到四种NAT类型的穿透差异,再到STUN打洞、TURN中继、ICE智能选路,最后落地WebRTC实战和coturn生产部署,整个网络穿透流程和大家走一遍。

估摸着,看完你应该能搞定90%的内网穿透、P2P连接异常问题了吧。

一、网络穿透解决了什么问题?

先问大家一个问题:为什么两台都连路由器、处于内网的手机或电脑,没法直接点对点通信?

很多人知道“内网设备不能被外网访问”,但从来没深究过背后的逻辑。

这就是网络穿透要解决的终极问题:打破内网隔离,让两个无公网IP的内网设备,实现双向通信

1.1 两端位于内网的设备为何无法直接通信?

你家的电脑连了Wi-Fi,你朋友的电脑也连了Wi-Fi。你们都打开了一个P2P聊天软件,想直接传文件。软件界面上显示"连接中......",然后超时了。

这不就是发消息吗,为什么这么难?

你家路由器有一个公网IP(比如203.0.113.5),你的电脑内网IP192.168.1.100。你朋友家类似,公网IP203.0.113.8,电脑内网IP192.168.1.101

你的电脑给朋友发数据包——数据包先到你家路由器,路由器把源地址从192.168.1.100:12345改成203.0.113.5:54321,然后发出去。朋友的路由器收到后,一看目标端口是54321——这个端口压根没开过啊——直接扔掉。

问题出在这儿:NAT只允许内网主动发起连接时建立映射表,不允许外部主动发起连接。

1.2 公网 IP、私网 IP 与端口的三层地址结构

要搞懂这个问题,咱们得先聊聊IP地址。其实工程里真正要看的地址,至少有三层。

第一层是内网 IP。比如:

192.168.1.8
10.0.2.15
172.16.8.20

这些地址在局域网里活得很好,但放到公网就不行。全世界不知道有多少台机器都叫 192.168.1.8。这就像公司里十个部门都有一个“张伟”,你在楼下喊一嗓子张伟,谁来?不好说。

第二层是公网 IP。路由器、企业出口网关、运营商网关对外暴露的地址,可能是:

203.0.113.10
198.51.100.20

公网 IP 才是互联网路由系统能识别的地址。

第三层是端口。NAT 设备真正做事时,通常不是只改 IP,而是改 IP 加端口。比如内网的:

192.168.1.8:50000

出网以后可能变成:

203.0.113.10:41001

于是一个完整的通信端点,不是“IP”,而是:

传输协议 + IP + 端口
UDP 192.168.1.8:50000
TCP 10.12.3.9:60000

STUN 要发现的,通常就是 NAT 映射后的公网反射地址。RFC 5389 把 STUN 定位为一种帮助其他协议处理 NAT 穿越的工具,可以让端点获知 NAT 为它分配的 IP 和端口,也可用于连通性检查和保活;它不是一个单独包治百病的穿透方案。

1.3 网络穿透的核心矛盾是 NAT 阻断了外部主动发起的连接

NAT 的核心行为很像门卫。核心矛盾就一句话:NAT是为"内网主动访问外网"设计的,不是为"外网主动访问内网"设计的。

内网设备主动出去,门卫说:“可以,我登记一下。你是 192.168.1.8:50000,出去以后我给你挂成 203.0.113.10:41001。”

外面的服务器回包,门卫查登记表:“哦,这个 203.0.113.10:41001 对应里面的 192.168.1.8:50000,放行。”

但外部陌生主机突然来一句:

我要访问 203.0.113.10:41001

门卫就会皱眉。你谁?里面那台机器有没有先联系过你?这个端口是不是给你开的?如果没有记录,不放。

P2P 通信最难的地方,不是发包,而是让双方的 NAT 都提前出现“这个外部地址可以进来”的状态。后面 UDP 打洞、ICE 连通性检查,干的都是这件事。听起来像很牛,其实就是趁 NAT 门卫还记得你,赶紧把双方的洞都戳开。

2. NAT 的工作原理与映射表

2.1 NAT 的定义与存在原因

为啥要有NAT?三个原因:

第一,IPv4地址不够用。IPv4总共42亿多个地址,地球上70亿人,每人分一个都不够。NAT让一个公网IP能养活一个局域网几十上百台设备。

第二,网络隔离。 内网设备不直接暴露在公网上,安全系数高一些。黑客想扫你家电脑,得先过路由器这一关。

第三,灵活组网。 企业内网随便加设备,不用跟运营商申请公网IP

有得必有失。NAT给了你地址复用和安全边界,代价就是——外部无法主动连接内网设备

2.2 NAT 基本工作流程

看一个最普通的 UDP 请求。

内网客户端 A
192.168.1.8:50000

目标服务器 S
8.8.8.8:3478

A 发包:

src = 192.168.1.8:50000
dst = 8.8.8.8:3478

包到 NAT 后,NAT 改写源地址:

src = 203.0.113.10:41001
dst = 8.8.8.8:3478

同时建一条映射表:

192.168.1.8:50000  <->  203.0.113.10:41001  ->  8.8.8.8:3478

服务器回包:

src = 8.8.8.8:3478
dst = 203.0.113.10:41001

NAT 查表,把目标地址改回内网地址:

src = 8.8.8.8:3478
dst = 192.168.1.8:50000

A 收到响应。

这条链路能成立,是因为连接由内网主动发起。NAT 有表可查。没有表时,NAT 一般不会猜“这个包应该给谁”。

工程里排查穿透问题,我最爱看的就是这张“表”。你在抓包工具里看不到完整 NAT 内部表,但可以通过 STUN 返回、客户端日志、服务端看到的源地址,大概拼出它。拼出来以后,问题通常就清楚一半了。

2.3 NAT 映射表结构解析

映射表长什么样?大概是这样:

+---------------------+----------------------+----------------------+
| 内网地址             | NAT 外侧映射           | 目标地址              |
+---------------------+----------------------+----------------------+
| 192.168.1.8:50000   | 203.0.113.10:41001   | 8.8.8.8:3478         |
| 192.168.1.9:50000   | 203.0.113.10:41002   | 8.8.4.4:3478         |
| 192.168.1.8:50001   | 203.0.113.10:41003   | 1.1.1.1:443          |
+---------------------+----------------------+----------------------+

每一行就是一个会话映射。回包来了,NAT查这个表决定转发给谁。表项有生命周期,超时了就删掉——这就是为啥你的P2P连接如果长时间没数据,会被NAT"遗忘"。

2.4 NAPT:端口复用的核心机制

严格说,家庭路由器最常见的不是只做地址转换的 NAT,而是 NAPT,也就是 Network Address Port Translation

内网一百台设备同时访问外网,公网IP只有一个。怎么区分回包该给谁?靠端口。

设备A用192.168.1.100:12345访问GoogleNAT分配公网端口54321。设备B用192.168.1.101:12345访问FacebookNAT分配公网端口54322。回包到了,看目标端口就知道该转给谁。

这就是端口复用的本质:用不同的公网端口区分不同的内网会话。

有些老路由器只支持几百个并发会话,现在的企业级设备动辄支持几万甚至几十万。但不管支持多少,本质都一样——NAT设备维护着一张映射表,表项有寿命

3. NAT 的四种类型

这章是重点。NAT穿透能不能成,很大程度上取决于两端的NAT是什么类型。搞不懂这个,后面STUNTURNICE全是白搭。

3.1 完全锥型 NAT

完全锥型 NAT,英文 Full Cone NAT。最友好的NAT,没有之一。

只要内网地址端口产生了一个外部映射,例如:

192.168.1.8:50000 -> 203.0.113.10:41001

此后——任何外部主机都可以向203.0.113.10:41001发包,NAT都会转给内网设备

"完全锥型"这名字挺形象——就像一个锥形漏斗,图是这样:

Peer X ----\
Peer Y -----+----> 203.0.113.10:41001 ----> 192.168.1.8:50000
Peer Z ----/

这种 NAT 对 UDP 打洞很友好。A 只要通过 STUN 拿到自己的公网映射地址,把它告诉 B,B 发过来就有机会进来。

这玩意儿穿透成功率最高,STUN一把梭就行。但现实中完全锥型NAT并不多见,家用路由器偶尔能碰上,企业网络基本别想。

3.2 受限锥型 NAT

受限锥型 NAT,英文 Restricted Cone NAT。比完全锥型严格一点。也是比较容易穿透的类型。

它的映射仍然固定:

192.168.1.8:50000 -> 203.0.113.10:41001

但外部主机不是谁都能进。只有内网主机曾经发过包给某个外部 IP,这个外部 IP 才能从任意端口发回来。

A 先发给 Peer X:9000

之后允许:
Peer X:任意端口 -> 203.0.113.10:41001

不允许:
Peer Y:任意端口 -> 203.0.113.10:41001

它限制 IP,不限制端口。

P2P 打洞时,双方都要先向对方公网 IP 发包。哪怕第一批包互相丢掉,也没关系。关键是 NAT 记录了“我里面这台机器刚刚联系过对方 IP”。下一批包就可能进来了。

这也是为什么 UDP 打洞不是一次发包完事,而是要连续发一小段时间。你要给双方 NAT 一个“认人”的机会。

3.3 端口受限锥型 NAT

端口受限锥型 NAT,英文 Port Restricted Cone NAT

它也保持固定映射:

192.168.1.8:50000 -> 203.0.113.10:41001

但过滤更严格。内网主机必须先发包给某个外部 IP 加端口,外部这个 IP 加端口才允许回包。

A 先发给 Peer X:9000

允许:
Peer X:9000 -> 203.0.113.10:41001

不允许:
Peer X:9001 -> 203.0.113.10:41001
Peer Y:9000 -> 203.0.113.10:41001

它不只认人,还认门牌号。

这种 NAT 下,候选地址交换必须准确。端口不能猜错。两端最好都用同一个 socket 持续发 STUN、发探测包、发业务包。你要是中途换了 socket,本地端口变了,映射也可能变。然后你就会看到那种很恶心的 bug:日志上地址都对,但就是连不上。

3.4 对称型 NAT

最狠的来了。

前面三种锥型NAT,映射关系是固定的——内网X:port永远映射到公网Y:port,不管跟谁通信。

对称型NAT不一样:每跟一个不同的目标通信,NAT就分配一个新的公网端口

192.168.1.8:50000 -> 8.8.8.8:3478    映射为 203.0.113.10:41001
192.168.1.8:50000 -> 9.9.9.9:3478    映射为 203.0.113.10:41002
192.168.1.8:50000 -> PeerB:60000     映射为 203.0.113.10:41099

这就麻烦了。

A 通过 STUN 服务器拿到的是:

203.0.113.10:41001

然后 A 把这个地址告诉 B。B 往 203.0.113.10:41001 发包。可 A 真正发给 B 时,NAT 可能给 A 生成的是 203.0.113.10:41099。B 拿着旧地址敲门,当然没人开。

老 STUN RFC 3489 曾按 Full ConeRestricted ConePort Restricted ConeSymmetric 来描述 NAT 类型,并给出类型发现流程;后来的规范更倾向于把 NAT 行为拆成“映射行为”和“过滤行为”来看,因为现实设备没那么乖,不一定严格落在四个盒子里。

对称型NAT面前,STUN基本等于废了。

3.5 四种 NAT 类型的映射行为与过滤行为对比图解

用一张图粗暴归纳:

再换成打洞视角:

Full Cone
A 打一个洞,大家都能从这个洞进来。

Restricted Cone
A 打一个洞,但只认 A 主动联系过的外部 IP。

Port Restricted Cone
A 打一个洞,只认 A 主动联系过的外部 IP 和端口。

Symmetric
A 对每个目标打出来的洞都可能不一样。

注意啊,这只是理论模型。真实设备会混搭。比如映射是 endpoint-independent,过滤却是 address-and-port-dependentRFC 4787 讨论 UDP NAT 行为时,就把过滤行为拆成 endpoint-independentaddress-dependentaddress-and-port-dependent 等类型,并指出更严格的地址加端口过滤可能导致 ICE 需要 UDP relay

3.6 NAT 类型对 P2P 穿透成功率的影响分析

P2P 穿透能不能成,最关键看两边 NAT 的组合。

如果两边都是 Full Cone,成功率很高。你甚至会觉得网络穿透没啥难的。别高兴太早,那是环境宠你。

如果一边 Full Cone,一边 Restricted 或 Port Restricted,也大概率能通过同时发包打洞。

如果两边都是 Port Restricted,还是有机会。关键是双方要准确交换公网 IP 和端口,并在 NAT 映射过期前同时发包。

如果有一边是 Symmetric,情况开始变糟。尤其两边都是 SymmetricSTUN 拿到的反射地址很可能对真正的 peer 没用。此时不要硬装。该上 TURN 就上 TURN。省那点服务器带宽,最后花在客服、投诉、事故复盘上的钱更多。

4. STUN 协议,公网地址发现机制

搞懂了NAT,再看STUN就完全没难度了。STUN是所有穿透方案的基础,没有STUN,就没法获取设备的真实公网映射地址,打洞也就无从谈起。

4.1 STUN 的定义与协议定位

STUN 现在的名字是 Session Traversal Utilities for NAT。注意是 Utilities,工具集。这个名字很重要。

早期很多文章把 STUN 讲成“穿透协议”。这话不算完全错,但容易误导新人。

STUN 本身主要帮你发现:我从公网看起来是谁?我的 NAT 给我映射成了哪个 IP 和端口?它也可用于连通性检查和 NAT binding 保活,但它不保证你能和任意对端直连。RFC 5389 就明确说,STUN 不是独立的 NAT 穿透解决方案,而是在 NAT 穿透方案上下文中使用的工具。

一个最小 STUN 过程:

Client ---- Binding Request ----> STUN Server
Client <--- Binding Response ---- STUN Server
             XOR-MAPPED-ADDRESS = 203.0.113.10:41001

客户端看到这个结果,就知道:

我本地是 192.168.1.8:50000
公网看我是 203.0.113.10:41001

这个公网地址就叫 server reflexive address,后面 ICE 里会继续用。

4.2 STUN 的客户端-服务器架构与传输层

STUN 是典型客户端-服务器架构。

客户端通常嵌在 App、浏览器、软电话、游戏客户端里。服务器部署在公网,有稳定 IP 和端口。客户端向 STUN Server 发 Binding Request,服务端根据收到包的源地址,回一个 Binding Response

STUN 可以跑在 UDP 上,也可以跑在 TCPTLS 等传输之上。WebRTC 里最常见的是 UDP,因为实时音视频最关心延迟。TCP 也能用,但遇到阻塞、重传、队头阻塞,体验就可能打折。TLS 适合被某些网络策略要求加密传输的环境。

一条典型配置长这样:

const pc = new RTCPeerConnection({
  iceServers
: [
    { urls: "stun:stun.example.com:3478" },
    {
      urls
: "turn:turn.example.com:3478",
      username
: "user",
      credential
: "pass"
    }
  ]
});

MDN 对 RTCPeerConnection 的说明里也把 iceServers 描述为 ICE agent 使用的服务器列表,通常就是 STUN 或 TURN 服务器。

4.3 STUN 消息结构解析

STUN消息分两部分:消息头 + 消息体(属性列表)。

4.3.1 消息头

STUN 消息是二进制格式。所有 STUN 消息都从 20 字节头开始,后面跟 0 个或多个 Attributes;头部包含 message typemessage lengthmagic cookie 和 transaction ID

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|    类型 (16位)  |     长度 (16位)   |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|           Magic Cookie (32位,固定0x2112A442)                |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    事务ID (96位 / 12字节)                     |
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  • • 类型Binding Request0x0001Binding Response0x0101
  • • 长度:消息体长度(不含头)
  • • Magic Cookie:固定值0x2112A442,用来区分STUN消息和其他协议
  • • 事务ID:96位随机数,请求和响应配对用的

4.3.2 消息体:Attributes

消息体是一串TLVType-Length-Value)属性。常见属性:

  • • MAPPED-ADDRESS:客户端的公网映射地址(兼容旧版,已不推荐)
  • • XOR-MAPPED-ADDRESS:异或编码的公网映射地址(推荐使用)
  • • SOFTWARE:服务器软件信息
  • • CHANGE-REQUEST:请求服务器改变源IP/端口,用于NAT类型检测

4.4 STUN 事务模型

STUN 事务模型很像一次带事务 ID 的请求响应。

Client                            STUN Server
  |                                    |
  | ---- Binding Request ------------> |
  |                                    |
  | <--- Binding Success Response ---- |
  |                                    |

如果失败:

Client                            STUN Server
  |                                    |
  | ---- Binding Request ------------> |
  |                                    |
  | <--- Binding Error Response ------ |
  |        401 Unauthorized            |
  |        438 Stale Nonce             |

UDP 上没有连接状态,所以客户端要负责重传、超时、匹配 Transaction IDTCP 上可靠性由传输层兜一部分,但 STUN 自己的事务语义还在。

4.5 XOR-MAPPED-ADDRESS 的设计目的

你可能会问:MAPPED-ADDRESS不就能告诉客户端公网地址吗?为啥还要搞个XOR版本?

因为NAT会修改数据包的有效载荷。

有些变态的NAT设备不仅改IP头,连应用层数据都敢动。MAPPED-ADDRESS里的IP地址是明文的,NAT看到了可能顺手就给改了。XOR-MAPPED-ADDRESSMagic Cookie和事务ID做异或编码。NAT看不懂这串东西,就不会乱改。

客户端收到后用同样的方式解码,得到真实的公网地址。这就是XOR的意义——防止NAT篡改。

4.6 STUN 的 NAT 类型检测流程

STUN不仅能告诉你公网地址,还能帮你判断NAT类型。方法是多次请求,改变请求参数,观察响应变化

标准流程(RFC 5780)大致如下:

  1. 1. 第一次请求:普通Binding RequestSTUN服务器。看响应里有没有XOR-MAPPED-ADDRESS。如果有,说明你可能在NAT后面(或者你在公网上,但STUN返回的就是你本身的地址)。
  2. 2. 第二次请求:带CHANGE-REQUEST属性,请求服务器从不同的IP和端口回响应。如果这次收不到响应,说明NAT对源地址有过滤——可能是受限锥型或更严格的类型。
  3. 3. 后续测试:通过组合不同的请求参数,观察映射地址是否变化、外部包能否进入,最终判断出NAT的具体类型。

这套检测流程在实际项目中用得不多——大部分应用直接用ICE的连通性检查来判断,不专门做NAT类型检测。但了解这个流程有助于理解NAT的行为模式。

4.7 STUN 的局限性

STUN 最容易被误用的地方,就是“拿到了公网地址,所以一定能连”。

不一定。

STUN 只能告诉你:你访问 STUN Server 时,公网看你是什么地址。它不能保证你访问 Peer 时还是这个地址。对称型 NAT 下,同一个本地地址端口访问不同目标,外部映射可能不同。

它也不能保证防火墙允许对端流量进来。就算 NAT 映射对了,企业防火墙一句“UDP 禁止”,你也没办法。校园网、酒店 Wi-Fi、某些移动网络,都能给你上一课。

所以 STUN 是探路灯,不是桥。路通不通,还要 ICE 检查。桥搭不起来,就走 TURN 中继。工程上千万别抱着“我只配 STUN,省钱”这种侥幸心理。小流量 demo 可以,生产环境会教你做人。

5. UDP 打洞,P2P 直连的核心机制

学会了STUN拿公网地址,下一步就是最核心的UDP打洞。所有P2P音视频通话、点对点传输,核心全是这套机制。

5.1 打洞的本质

UDP 打洞这个名字,听着像黑客攻击。其实它很规矩。

它的本质是:让内网主机主动向外发包,促使 NAT 建立映射和过滤许可。然后对端趁这个映射还活着,把包发回来。

A 内网地址 192.168.1.8:50000
A 公网映射 203.0.113.10:41001

B 内网地址 10.0.0.9:60000
B 公网映射 198.51.100.20:52001

A 要先给 B 的公网地址发 UDP

A -> 198.51.100.20:52001

B 也要给 A 的公网地址发 UDP

B -> 203.0.113.10:41001

第一轮包很可能丢。没事。我们要的不是第一轮就成功,而是让 NAT 建表。

NAT-A 看到 A 发给 B,于是允许 B 回来
NAT-B 看到 B 发给 A,于是允许 A 回来

双方洞都打开后,后续包就能进了。

5.2 双方同时向对方公网地址发送 UDP 包的并发策略

为什么要“同时”?

因为 NAT 映射有超时时间。UDP 没连接,NAT 不知道你聊完没聊完。它只能过一段时间没看到流量就清表。

如果 A 先打洞,等了 30 秒 B 才开始发,A 那边洞可能已经没了。尤其某些移动网络,UDP 映射超时很短,短得让人想骂街。

所以工程里通常这样做:

1. A、B 分别向 STUN Server 获取公网反射地址
2. A、B 通过信令服务器交换候选地址
3. A、B 收到对方地址后,立即开始周期性发送探测 UDP 包
4. 一旦收到对方包,确认直连成功
5. 继续发送 keepalive,防止 NAT 映射过期

探测包不要只发一次。可以短时间内按节奏发,例如 20ms、50ms、100ms,持续几秒。别太猛,别把网络打爆。你不是 DDoS 自己。

5.3 UDP打洞完整时序流程

看图:

信令服务器只负责交换信息,不转发媒体包。它像媒婆,撮合双方认识。真正过日子,还是 A 和 B 自己聊。

这里有个坑。很多新人以为 STUN Server 会参与后续通信。不会。STUN 帮你发现地址,信令帮你交换地址,打洞成功后,业务流量直接走 P2P

除非失败了,才 TURN 上场。

5.4 打洞成功的条件

不是所有场景都能打洞成功,核心取决于四个条件:

NAT 类型要相对友好。Full ConeRestrictedPort Restricted 都还有得打。Symmetric 就难。

映射要稳定。你从 STUN 拿到的公网端口,最好和你发给 peer 时用的公网端口一致。否则对方拿着错地址打你。

时间窗口要抓住。NAT 映射过期前,双方探测包要到。

防火墙不能太狠。很多企业网会允许 TCP 443,但限制 UDPWebRTC 走 UDP 很香,但如果 UDP 被拦,ICE 会尝试 TCP 或 TURN/TCP/TLS 等候选。延迟就上来了。

5.5 TCP 打洞与 UDP 打洞的差异

UDP 无连接。发就完了。NAT 看到出站 UDP 包,建个映射。对端发回来,有机会进。

TCP 不一样。TCP 有三次握手,有 SYNSYN-ACKACK,有状态机。NAT 和防火墙也会跟踪 TCP 状态。你想让两个都在 NAT 后的端点同时发 SYN,双方还要处理 simultaneous open。理论可以,工程上更难。

TCP 打洞还容易被中间设备干扰。有些 NAT 不喜欢奇怪的 TCP 状态。有些防火墙只允许客户端主动连服务器,不允许这种互相 SYN 的玩法。

所以实时通信里,能用 UDP 优先 UDPTCP 更多是兜底。WebRTC 也不是不用 TCP,但 P2P 直连路径里,UDP 通常更好用。QUIC 也是基于 UDP,这也是后面会说它对 NAT 穿透有点东西的原因。

6. TURN 协议,中继兜底方案

有了STUNUDP打洞,为什么还要TURN

很简单,前面说过,对称NAT、严格企业防火墙环境下,打洞基本必败。想要保证100%连接成功率,必须上中继方案,TURN就是唯一的工业级选择。

6.1 TURN 的定义与设计目标

TURN全称Traversal Using Relays around NATRFC 5766定义(2010年)。

名字翻译就是:用中继绕过NAT

STUN搞不定、打洞打不通的时候,TURN上场了。它不试图"穿透"NAT,而是直接绕过去——在公网上搭一个中继服务器,两边都把数据发给中继,中继帮你转发。

这是兜底方案,不是首选。 因为所有流量都经过中继服务器,延迟增加、带宽成本飙升。

6.2 TURN 与 STUN 的关系

TURN不是独立发明的新协议——它构建在STUN消息框架上,使用STUN的消息格式、事务机制、属性机制,是STUN的扩展

TURN的消息格式跟STUN完全一样(除了ChannelData消息),端口也一样用3478TURNSTUN的基础上增加了一组新方法(AllocateSendChannelBind等)和新属性。

ICE的候选地址收集流程中,STUNTURN是串起来的——先试STUN拿反射地址,不行再找TURN要中继地址。

这不是说 RFC 5766 没价值。它仍然是理解 TURN 的经典入口。

6.3 TURN 协议的核心工作流程

6.3.1 Allocate 请求:让客户端向 TURN 服务器申请中继资源

TURN 的第一步是 Allocate

客户端对 TURN Server 说:

我想申请一个中继地址,帮我收发数据。

时序:

Client                         TURN Server
  |                                 |
  | ---- Allocate Request --------> |
  |                                 |
  | <--- Allocate Success Response |
  |      Relay Transport Address   |

这一步需要认证。因为 TURN 很贵,谁都能用你的 TURN,那就是给全世界送带宽。

6.3.2 Allocate 成功响应:中服务器分配 Relay Transport Address

Allocate 成功后,服务器返回一个 Relay Transport Address

Relay = 203.0.113.200:55000

这个地址是公网可达的。对端往这个地址发包,TURN Server 会根据权限和绑定关系,把包转给客户端。

客户端也可以通过 TURN Server 向 peer 发数据。

Client A
  |
  | Send Indication / ChannelData
  v
TURN Server Relay Address
  |
  v
Peer B

从 peer 视角看,它在和 TURN Server 的 relay address 通信。A 的内网地址、NAT 状态,都被 TURN 藏在后面。

6.3.3 资源生命周期管理

中继资源不是永久分配给你的——有生命周期

默认生命周期是10分钟(600秒),但服务器可以给一个更短的时间。客户端必须在过期之前发Refresh Request来续期。

如果发Refresh Request时把LIFETIME设为0,就是告诉服务器"不用续了,释放资源"。

生产环境中,TURN客户端通常每5分钟刷新一次(留点余量,别卡在最后几秒)。

6.4 TURN 的权限管理机制

TURN是公网中继服务,如果不做权限控制,会被恶意滥用、打流量攻击,所以自带两层权限机制。

6.4.1 权限许可机制

TURN 不是开放代理。它有 Permission 机制。

客户端必须告诉 TURN Server

允许这个 peer IP 通过 relay 给我发数据。

Permission 是按 peer IP 控制,不是按完整 IP:端口。它的意义是防止 relay address 变成谁都能打进来的洞。

Permission:
  Peer IP = 198.51.100.20
  Lifetime = 300s

没有 permission 的 peer,往 relay address 发包,TURN Server 应该丢掉。这个设计很重要。否则 TURN 服务器就是一个昂贵又危险的反射放大工具。

6.4.2 通道绑定机制

TURN 发送数据有两种常见方式:Send Indication 和 ChannelData

Send Indication 比较通用,但 STUN 头部开销较大。ChannelData 更轻。要用 ChannelData,需要先 ChannelBind

Client                         TURN Server                         Peer
  |                                  |                               |
  | ---- ChannelBind Request ------> |                               |
  |      Peer A -> 0x4001           |                               |
  | <--- ChannelBind Success ------- |                               |
  |                                  |                               |
  | ---- ChannelData[0x4001] ------> | ===== data =================> |

RFC 5766 规定 ChannelBind 会创建或刷新 channel binding,同时创建或刷新对应 peer 的 permissionChannel Number 范围也有约束,比如 0x4000 到 0x7FFE

这玩意儿看着细,但高并发音视频里很实用。少几个字节头部,乘上几十万并发流量,就不是小钱了。

6.5 TURN 数据转发路径是

TURN 转发路径很好理解:

A Client  <---- NAT-A ---->  TURN Server  <---- NAT-B ---->  B Client

如果 A 和 B 都在 NAT 后面,最稳妥的路径可能是:

A -> TURN -> B
B -> TURN -> A

甚至双方都各自使用 relay candidate,最终路径可能更绕:

A -> TURN-A -> TURN-B -> B

ICE 会尽量避免这么贵的路径,但有时没办法。企业网络、防火墙、代理环境一复杂,能通就已经不错了。别嫌丑,能跑的代码有时候就是比漂亮架构值钱。

6.6 TURN 的代价

我做项目时,最头疼的就是TURN的成本问题,完全是烧钱的功能。

延迟会增加。原本 A 到 B 可能 30ms,绕 TURN 后可能 60ms、100ms,跨地域更糟。

服务器带宽压力巨大。音视频是双向流量。一个 1Mbps 的视频流,TURN 中继时服务器要收一份、发一份。粗暴算就是 2Mbps 服务器带宽。再乘并发,账单会让财务沉默。

可用性要求也高。TURN 挂了,原本依赖中继的用户就连不上。它不是边缘小组件,是实时通信系统的生命线之一。

所以生产里要做区域调度、限流、监控、带宽预估、认证、滥用防护。TURN 不是“docker run 一下就完事”。能跑 demo,不代表能扛线上。

6.7 TURN 适用场景

TURN虽然贵,但有些场景不得不用:

对称型NATSTUN搞不定,打洞打不通,只能上TURN

企业防火墙。 很多企业防火墙连UDP打洞的包都拦,TURNTCP/443端口能混过去。

高可靠性要求。 如果你的SLA要求连接成功率99.9%以上,TURN是必须的——作为兜底方案。

移动网络。 运营商级NATCGNAT)通常是对称型的,移动设备在4G/5G网络下经常需要TURN

7. ICE,多候选路径的智能选择

有了STUN打洞、TURN中继,还需要ICE做整合。ICE是整套穿透体系的调度大脑。

如果没有ICE,我们只能手动尝试直连、中继,效率极低。ICE的作用就是自动收集所有可用路径、自动校验、自动选最优通路、自动优雅降级

7.1 ICE 的设计目标与

ICE,全称 Interactive Connectivity Establishment。它不是单个传输协议,而是一套候选地址收集、交换、配对、检查、选路的框架。

一句话讲:

ICE = 把所有可能的连接路径列出来,然后用 STUN 检查哪条能通,优先选最好的。

RFC 8445 是 ICE 的核心规范。它定义了 NAT 穿透时如何收集候选、形成 candidate pairs、执行 connectivity checks、选择最终可用路径。RFC 8445 中也明确说,连通性检查是从本地 candidate 向远端 candidate 发送 STUN Binding request

你可以把 ICE 想成一个很有耐心的司机。

能走小路直达,就走小路。 小路封了,就走主路。 主路堵了,就上高速。 都不行,就绕远路走收费桥。

这个收费桥就是 TURN

7.2 ICE Agent 的三种候选地址类型

ICE会收集三类候选地址,优先级从高到低排序,优先用最优路径。

7.2.1 Host Candidate:本地网卡地址

Host Candidate 来自本地网卡。

192.168.1.8:50000
10.0.0.9:60000
fe80::xxxx

同一个局域网内,host candidate 可能直接可用。比如两台设备在同一个 Wi-Fi,甚至不需要 STUN

但如果跨公网,私网地址通常没用。你把 192.168.1.8 发给地球另一端的 peer,它只会一脸无辜。它那边也可能有个 192.168.1.8,但不是你。

7.2.2 Server Reflexive Candidate:STUN获取的公网反射地址

Server Reflexive Candidate,简称 srflx candidate。它来自 STUN

本地地址:
192.168.1.8:50000

STUN 看到:
203.0.113.10:41001

这个 203.0.113.10:41001 就是 srflx candidate

它是 UDP 打洞的关键材料。没有它,对端不知道该往哪个公网地址发探测包。

7.2.3 Relay Candidate:TURN分配的中继地址

Relay Candidate 来自 TURN Allocate

TURN Relay:
203.0.113.200:55000

它最贵,但最稳。ICE 不会无脑优先 relay,因为 relay 延迟高、成本高。通常优先级是:

Host > Server Reflexive > Relay

——甭管什么NAT类型、什么防火墙,只要客户端能访问TURN服务器,中继就能通。

7.3 候选地址收集流程 Gathering

ICE Gathering 就是收集候选。

普通方式是先收集完,再把完整列表通过 SDP 发给对端。这叫常规 ICE。缺点是慢。尤其 TURN AllocateDNS 查询、网络抖动时,用户会看到连接前卡一下。

Trickle ICE 更聪明。候选一出来,就先发给对端。后续候选继续“滴灌”过去。RFC 8838 定义的 Trickle ICE 允许候选一旦可用就增量交换,并且可以在候选对形成后立即开始连通性检查,从而加速会话建立。

就像点菜。常规 ICE 是等全桌菜做好,一起端上来。Trickle ICE 是凉菜先上,热菜边做边上。用户先吃起来,体验就不一样。

7.4 候选地址交换 Signaling

A收集完候选地址,得告诉B;B收集完,得告诉A。

这个交换过程通过信令通道完成。在WebRTC里,信令通道是应用自己实现的(WebSocketHTTP长轮询、SIP等),不是WebRTC协议的一部分。

交换的格式通常是SDPSession Description Protocol),里面用a=candidate行描述每个候选地址。

7.5 候选配对 Candidate Pairing

A有3个候选,B有3个候选,两两配对就是9对:

  • • A的Host候选 + B的Host候选
  • • A的Host候选 + B的Server Reflexive候选
  • • A的Host候选 + B的Relay候选
  • • ...以此类推

每一对就是一个"候选对"(Candidate Pair)。ICE Agent会给每一对算一个优先级,然后按优先级从高到低排序。

7.6 连通性检查

排序完了,开始检查。

7.6.1 检查流程与有序性

ICE 连通性检查,就是对 candidate pair 发 STUN Binding Request

Local Candidate ----------------> Remote Candidate
              STUN Binding Request

Local Candidate <---------------- Remote Candidate
              STUN Binding Response

如果响应回来,这个 candidate pair 就是可用路径候选。

ICE 不会无限并发乱打。它有 check list,有状态,有 pacing。否则候选多一点,就会把网络打成烟花。

7.6.2 优先级计算与排序算法

ICE 会给 candidate 和 candidate pair 算优先级。大方向是:

类型偏好
本地偏好
组件 ID
控制角色

Host 通常高于 srflxsrflx 高于 relayIPv6IPv4、本地接口也可能有不同偏好。具体公式不是本文重点,真要实现协议栈再去啃 RFC。做业务配置,理解“优先直连,失败再中继”更重要。

不过别手贱乱改优先级。尤其是强制 relay 或禁用 relay,要知道后果。把 iceTransportPolicy 改成 "relay",可以只走 TURN,但成本会飙。它适合测试 TURN 是否可用,或某些强隐私场景,不适合无脑全量开。

7.6.3 角色机制:Controlling 与 Controlled

ICE会话中双方分两个角色:

  • • Controlling:负责"做决定"——当多个候选对都能通时,由Controlling方选择最终用哪一个
  • • Controlled:听从Controlling方的决定

角色通过STUN请求里的ICE-CONTROLLEDICE-CONTROLLING属性来协商。谁先发起谁当Controlling,一般是这样。

为啥要角色?因为两边都想拍板会乱。总得有一个人说“就这条路吧”。不然 A 选了路 1,B 选了路 2,大家各走各的,场面就像两个后端开会没人写结论。

7.7 候选对的状态机与最终选路

每个候选对经历几个状态:

  1. 1. Frozen:还没轮到检查(优先级太低,前面还有更高优先级的在等)
  2. 2. Waiting:在检查队列里等着
  3. 3. In-Progress:正在发STUN请求检查
  4. 4. Succeeded:收到了STUN Response,通了!
  5. 5. Failed:超时或收到错误响应,不通

当至少一个候选对进入Succeeded状态,Controlling方从中选一个"最佳"的——通常是优先级最高的那个。选完后发一个Nomination请求确认。

至此,连接建立完成。

7.8 ICE 的优雅降级策略

总结一句贯穿始终的降级逻辑:能内网绝不外网,能P2P绝不中继

同局域网:
Host <-> Host

不同 NAT 但可打洞:
Srflx <-> Srflx

直连失败:
Relay <-> Peer
或 Relay <-> Relay

这比你自己写一堆 if-else 判断 NAT 类型靠谱多了。

工程建议:

默认允许 all candidates
配置可用 STUN
必须配置 TURN 兜底
开启 Trickle ICE
监控 selected candidate pair type

8. WebRTC 中的 ICE 实践

理论讲完,落地到最常用的WebRTC场景。所有WebRTC音视频通话、屏幕共享、点对点传输,底层全是这套NAT+STUN+TURN+ICE逻辑。

8.1 WebRTC 连接建立的全链路

WebRTC 建连流程大概如下:

1. 创建 RTCPeerConnection
2. 添加音视频轨道或 data channel
3. 创建 SDP Offer / Answer
4. 通过信令交换 SDP
5. 收集并交换 ICE Candidate
6. ICE Connectivity Checks
7. 选出可用 candidate pair
8. DTLS 握手
9. SRTP / RTP / RTCP 或 SCTP DataChannel 开始传输

画成环:

Signaling
   |
   v
ICE Gathering -> Candidate Exchange -> Connectivity Checks
   |                                      |
   v                                      v
Selected Pair ----------------------> DTLS
                                          |
                                          v
                                   RTP / RTCP / DataChannel

ICE 只负责把路打通。媒体安全靠 DTLS-SRTP。音视频包通常走 RTP/RTCPDataChannel 通常基于 SCTP over DTLS

这条链任何一段坏了,用户看到的都是“连不上”。所以排查时别只盯 ICEICE connected 后没画面,可能是 DTLS、编解码、权限、轨道、播放器,全都有可能。工程现场就是这么烦人。

8.2 信令服务器的职责

注意!STUNTURNICE都不负责信令交互,必须自己搭建轻量信令服务器。

信令服务器是WebRTC架构里的"媒人"——帮双方交换信息,但不参与媒体传输。

具体职责:

  • • 转发SDP Offer/Answer(包含媒体能力、编解码器、ICE候选等)
  • • 转发ICE CandidateTrickle ICE模式下,候选地址一个个地发)

信令服务器可以用任何技术实现——WebSocketSocket.ioSIPXMPP都行。WebRTC规范不规定信令协议,留给你自己选。

一个极简信令消息:

{
  "type"
: "candidate",
  "roomId"
: "room-123",
  "from"
: "alice",
  "to"
: "bob",
  "candidate"
: {
    "candidate"
: "candidate:842163049 1 udp 1677729535 203.0.113.10 41001 typ srflx raddr 192.168.1.8 rport 50000",
    "sdpMid"
: "0",
    "sdpMLineIndex"
: 0
  }
}

8.3 SDP 中的 Candidate 属性解析

一行 candidate 大概长这样:

a=candidate:842163049 1 udp 1677729535 203.0.113.10 41001 typ srflx raddr 192.168.1.8 rport 50000 generation 0 ufrag abc network-id 1

拆一下:

foundation       842163049
component        1
transport        udp
priority         1677729535
address          203.0.113.10
port             41001
type             srflx
related address  192.168.1.8
related port     50000

typ host 是本地地址。 typ srflx 是 STUN 反射地址。 typ relay 是 TURN 中继地址。 raddr 和 rport 表示相关地址,srflx 通常会带本地 base 地址,relay 也有自己的关联信息。

排查时重点看:

有没有 srflx
有没有 relay
最终 selected pair 是什么类型
本地和远端端口是否符合预期

如果你只看到 host candidate,没有 srflx 和 relay,多半 STUN/TURN 没通,或配置没生效。

8.4 RTCPeerConnection 的 ICE 配置参数

常见配置:

const pc = new RTCPeerConnection({
  iceServers
: [
    { urls: ["stun:stun.example.com:3478"] },
    {
      urls
: [
        "turn:turn.example.com:3478?transport=udp"
,
        "turns:turn.example.com:5349?transport=tcp"

      ],
      username
: "webrtc-user",
      credential
: "webrtc-pass"
    }
  ],
  iceTransportPolicy
: "all",
  iceCandidatePoolSize
: 2
});

iceServers 是 STUN/TURN 列表。 iceTransportPolicy 默认通常是 all,也就是 hostsrflxrelay 都可用。设为 relay 时,只使用中继候选。 iceCandidatePoolSize 可以预先收集 ICE candidates,降低后续建连等待。MDN 提到它默认是 0,设置后可让 ICE agent 在连接尝试前预取候选,从而可能更快连接。

但别把 iceCandidatePoolSize 当神。它会提前消耗资源,也可能让 TURN Allocate 更早发生。移动端尤其要考虑电量、网络切换、后台状态。

8.5 Trickle ICE 与常规 ICE 的差异

常规 ICE

收集所有候选 -> 放进 SDP -> 发给对端 -> 开始检查

Trickle ICE

先发 SDP -> 候选出来一个发一个 -> 边收集边检查

WebRTC 里通常通过 onicecandidate 事件把候选发给对端:

pc.onicecandidate = (event) => {
  if
 (event.candidate) {
    signaling.send({
      type
: "candidate",
      candidate
: event.candidate
    });
  } else {
    signaling.send({ type: "end-of-candidates" });
  }
};

远端收到后:

await pc.addIceCandidate(candidate);

MDN 说明 addIceCandidate() 会把远端通过信令通道收到的新 ICE candidate 添加到远端描述中。

Trickle ICE 的体验提升很明显。用户不用等所有候选收集完。Host candidate 先出来就先试,srflx 后出来继续试,relay 再出来兜底。像赶地铁,能先上车就别在站台等所有朋友买完奶茶。

9. coturn 服务部署与配置

9.1 coturn 项目概述是开源 STUN/TURN 服务器实现

coturn 是现在很常用的开源 STUN/TURN Server。官方项目介绍里,它是一个免费的开源 TURN 和 STUN Server 实现,可作为 VoIP 媒体流量 NAT traversal server 和 gateway

它的优点是成熟、资料多、Docker 部署方便、WebRTC 生态常用。缺点也很朴素:配置项多,新人容易配到怀疑人生。

coturn 不是“启动就安全”。默认项、认证、端口范围、TLS、日志、带宽控制,都要认真看。尤其公网服务器,别偷懒。

9.2 基础部署方式

源码编译适合你需要定制、调试、打补丁的场景。Docker方式更省心,生产环境也推荐用容器编排(Kubernetes)来管理coturn集群。

Docker 示例:

docker run -d --name coturn \
  --network host \
  -v /etc/turnserver.conf:/etc/coturn/turnserver.conf \
  coturn/coturn \
  -c /etc/coturn/turnserver.conf

如果不用 host 网络,要记得映射监听端口和 relay 端口范围:

docker run -d --name coturn \
  -p 3478:3478/udp \
  -p 3478:3478/tcp \
  -p 5349:5349/tcp \
  -p 49160-49200:49160-49200/udp \
  -v $(pwd)/turnserver.conf:/etc/coturn/turnserver.conf \
  coturn/coturn \
  -c /etc/coturn/turnserver.conf

这里最容易漏的是 relay 端口范围。只开放 3478 不够。TURN Allocate 后真正转发媒体可能用 min-port 到 max-port 范围内的端口。云安全组、防火墙、容器端口映射,都要一起放开。

9.3 核心配置项

一个基础 turnserver.conf

listening-port=3478
tls-listening-port=5349

listening-ip=0.0.0.0
relay-ip=0.0.0.0

external-ip=203.0.113.200

realm=example.com
server-name=turn.example.com

lt-cred-mech
user=webrtc-user:strong-password

fingerprint

min-port=49160
max-port=49200

log-file=/var/log/turnserver.log
simple-log

no-cli

listening-port 是 STUN/TURN 监听端口,常用 3478。 listening-ip 是监听地址。多网卡服务器要谨慎配置。 external-ip 在云主机或 NAT 后部署 coturn 时非常关键。服务器内网 IP 和公网 EIP 不一致时,不配置它,返回给客户端的 relay 地址可能是内网地址。那就尴尬了。 realm 和 user 配合长期凭证认证。 lt-cred-mech 开启 long-term credential mechanismcoturn 旧文档也强调 WebRTC 使用 long-term credentials,需要开启相应选项。 fingerprint 给 STUN 消息加 FINGERPRINT 属性,排查和兼容性更好。 min-portmax-port 限制 relay 端口范围,方便防火墙放行和容量管理。 no-tlsno-udpno-tcp 这种开关要慎用。你以为自己是在收敛攻击面,可能顺手把用户可用路径砍没了。

coturn 官方 Wiki 里也列了不少运行开关,例如 --stun-only--no-stun--secure-stun--no-cli 等;其中 --server-relay 还被明确标为非标准且危险,不理解就不要用。

9.4 TLS 和 DTLS 加密配置

如果要支持 turns:,需要 TLS 证书:

tls-listening-port=5349

cert=/etc/ssl/certs/turn.example.com.crt
pkey=/etc/ssl/private/turn.example.com.key

cipher-list="HIGH"

客户端配置:

const pc = new RTCPeerConnection({
  iceServers
: [
    {
      urls
: [
        "turn:turn.example.com:3478?transport=udp"
,
        "turns:turn.example.com:5349?transport=tcp"

      ],
      username
: "webrtc-user",
      credential
: "strong-password"
    }
  ]
});

TLS/TCP 通常是兜底。某些网络只允许 443 出口,这时可以把 TURN TLS 放在 443。但如果同机还有 HTTPS 服务,就要做端口规划,或者用独立 IP

DTLS 也可以用于 TURN over UDP 的安全传输,但实际部署中,WebRTC 场景更常见的是 UDP TURNTCP TURNTLS TURN 组合。你要根据用户网络决定,不要拍脑袋。

9.5 生产环境运维要点

TURN 运维最核心是带宽。

估算模型很粗:

TURN 出入总带宽 ≈ relay 会话数 × 单路媒体码率 × 2

如果 1000 个用户走 TURN,每个平均 800kbps:

1000 × 800kbps × 2 = 1600Mbps

这还没算协议开销、峰值、重传、屏幕共享。真上生产,至少按峰值、地域、冗余来估。

监控要看:

allocation 数量
permission 数量
relay candidate 使用比例
入站带宽
出站带宽
包丢失
CPU
内存
端口耗尽情况
认证失败率
区域连接成功率

日志别无脑 debug。线上高并发 debug 日志能把磁盘写爆。要有采样,要有轮转,要能按用户或 session ID 追踪。

横向扩展可以按区域部署多个 TURN

turn-cn-east.example.com
turn-cn-south.example.com
turn-sg.example.com
turn-us-west.example.com

客户端按用户地理位置或延迟测速下发 iceServers。别让上海用户绕到美国 TURN,除非你想让语音像从月球传回来。

10. 常见故障模式与排查方法

10.1 候选地址收集不完整

现象:

只有 host candidate
没有 srflx candidate
没有 relay candidate

可能原因:

STUN 服务器域名解析失败
UDP 3478 被防火墙拦截
TURN 认证失败
external-ip 配错
relay 端口范围没开放
浏览器策略或客户端配置没生效

排查:

1. 客户端能否解析 STUN/TURN 域名
2. UDP/TCP 3478 是否可达
3. TURN 用户名密码是否正确
4. coturn 日志有没有 Allocate 请求
5. 云安全组是否放行 relay 端口范围
6. WebRTC Internals 里候选是否出现

10.2 ICE 连通性检查超时

现象:

候选收集正常
candidate 已交换
ICE checking 很久
最后 failed 或 disconnected

可能原因:

双方 UDP 被拦
NAT 映射超时太短
双方没有同时探测
信令延迟导致候选过期
STUN Binding Request 被中间网络丢弃

排查可以看 selected pair 是否产生。没有 selected pair,说明检查没成功。看 candidate pair 状态,如果 srflx-srflx 全 Failedrelay-relay 成功,那就是直连失败但 TURN 兜住了。

如果连 relay 都失败,优先查 TURN。别继续纠结 NAT 类型。

10.3 仅 STUN 无 TURN 时高失败率场景

只配 STUN,在这些场景失败率会明显上升:

对称型 NAT
运营商级 NAT
企业网络限制 UDP
校园网出口策略复杂
酒店和机场 Wi-Fi
跨国网络路径不稳定

“我本地可以”没有说服力。开发环境太干净了。你要拿真实用户网络测。至少覆盖:

家庭宽带
4G/5G
公司网络
校园网
公共 Wi-Fi
不同运营商组合

有条件的话,把 NAT 类型、candidate pair 类型、ICE 失败原因都打进埋点。别上线后靠用户一句“连不上”猜。猜网络问题很痛苦,像在黑屋里找黑猫,猫还不一定在屋里。

10.4 企业网络、校园网、运营商级 NAT 的特殊问题

企业网络经常有代理、防火墙、DPIUDP 可能被限。TCP 也可能只允许 80/443。某些环境还会做 TLS inspection,虽然 WebRTC 媒体本身有 DTLS,但中间策略依旧可能影响连接。

校园网更神奇。不同宿舍楼、不同出口、不同时间段,策略都可能不一样。晚上高峰期丢包像下雨。

运营商级 NAT,也就是 CGNAT,会让大量用户共享运营商出口地址。映射行为更复杂,端口资源更紧,超时策略更激进。移动网络切换基站、切 Wi-Fi、息屏唤醒,也可能导致 ICE 连接断掉再重连。

这就是为什么实时通信系统要支持 ICE restart。网络换了,旧 candidate pair 可能没用了。不要硬撑。该重协商就重协商。

10.5 排查工具链

10.5.1 Chrome WebRTC Internals 用于查看浏览器内部状态

Chrome 里打开:

chrome://webrtc-internals

有些文章写 about:webrtc-internalsChrome 里常用的是 chrome://webrtc-internalsFirefox 则有自己的 about:webrtc

重点看:

ICE connection state
ICE gathering state
local candidates
remote candidates
candidate pair
selected candidate pair
bytesSent / bytesReceived
currentRoundTripTime
availableOutgoingBitrate
packetsLost

这工具看起来丑,但救过我命。线上复现不了的问题,让用户导一份 dump,常常比十句“你再试一下”有用。

10.5.2 Trickle ICE 独立测试工具用于验证 STUN/TURN 可用性

Trickle ICE 测试页可以输入你的 STUN/TURN 配置,看浏览器能不能收集到 hostsrflxrelay candidates

测试逻辑:

输入 iceServers
开始 gathering
观察候选类型
强制 relay 测试 TURN

如果测试页都拿不到 relay candidate,你的业务代码大概率也拿不到。别甩锅给前端。

10.5.3 coturn 日志与 tcpdump、Wireshark 抓包分析

coturn 日志看:

认证是否成功
Allocate 是否成功
CreatePermission 是否成功
ChannelBind 是否成功
relay 地址和端口
错误码

tcpdump 抓包:

tcpdump -i eth0 udp port 3478
tcpdump -i eth0 udp portrange 49160-49200

Wireshark 可以直接解析 STUN/TURN。过滤表达式:

stun
turnchannel
udp.port == 3478

抓包别只抓服务器。客户端、NAT 前后、服务端三边对比,才能知道包死在哪里。只看一边容易误判。

10.5.4 STUN/TURN 服务连通性测试脚本

用 Node.js 做个简单候选收集测试:

const pc = new RTCPeerConnection({
  iceServers
: [
    { urls: "stun:stun.example.com:3478" },
    {
      urls
: "turn:turn.example.com:3478?transport=udp",
      username
: "webrtc-user",
      credential
: "strong-password"
    }
  ]
});

pc.createDataChannel("test");

pc.onicecandidate = (e) => {
  if
 (e.candidate) {
    console
.log(e.candidate.candidate);
  } else {
    console
.log("gathering complete");
    pc.close();
  }
};

(async () => {
  const
 offer = await pc.createOffer();
  await
 pc.setLocalDescription(offer);
})();

想强测 TURN

const pc = new RTCPeerConnection({
  iceTransportPolicy
: "relay",
  iceServers
: [
    {
      urls
: "turn:turn.example.com:3478?transport=udp",
      username
: "webrtc-user",
      credential
: "strong-password"
    }
  ]
});

如果 relay candidate 出不来,别继续谈业务建连。基础设施先修。

11. STUN、TURN 与 ICE 的技术选型

11.1 三种方案的核心对比包括穿透能力、延迟、带宽成本、部署复杂度

来一张工程视角的对比:

STUN 便宜,但不保证通。 TURN 贵,但能兜底。 ICE 是调度大脑,把 hostsrflxrelay 候选组织起来,自动选路。

不要问“我该用 STUN 还是 TURN”。生产里通常是:

ICE + STUN + TURN

区别只在于 TURN 使用比例要控制,而不是完全不用。

11.2 选型决策矩阵

11.2.1 公网或完全锥型 NAT 环境下 STUN 即可

如果双方都有公网 IP,或者 NAT 很开放,STUN 获取 srflx 后直连成功率高。

适合:

内网测试
同云 VPC 特定网络
可控设备网络
部分家庭宽带

但公网环境也要注意安全。直接暴露端口不是没有风险。P2P 应用必须做好认证、加密、权限控制。别以为“只是 UDP”就没人打你。

11.2.2 受限锥型 NAT 环境下使用 STUN 加 UDP 打洞

这是最经典的 P2P 穿透路径。

STUN 获取公网反射地址
信令交换候选
双方同时 UDP 探测
ICE 检查成功
业务流量直连

适合:

实时语音
视频通话
P2P 游戏
远程协作
文件点对点传输

要点是保持同一个 socket、控制探测节奏、做好 keepalive、监控失败率。

11.2.3 对称型 NAT 和企业防火墙下必须启用 TURN 兜底

对称型 NAT 下,STUN 反射地址可能只对 STUN Server 有效。企业防火墙下,UDP 可能被干掉。别硬刚。

配置建议:

const pc = new RTCPeerConnection({
  iceServers
: [
    { urls: "stun:stun.example.com:3478" },
    {
      urls
: [
        "turn:turn.example.com:3478?transport=udp"
,
        "turn:turn.example.com:3478?transport=tcp"
,
        "turns:turn.example.com:5349?transport=tcp"

      ],
      username
: "user",
      credential
: "pass"
    }
  ]
});

UDP TURN 优先。TCP/TLS TURN 兜底。别只配一种路径。真实网络很会打脸。

11.2.4 对连接成功率有 SLA 要求时 ICE 配置中强制包含 TURN

如果你做的是在线会议、远程客服、云游戏、远程桌面、在线教育,连接成功率就是核心指标。

这种场景必须包含 TURN。甚至要做多 TURN 区域、多运营商线路、自动调度。

SLA 不是 PPT 上写出来的,是靠兜底路径和监控堆出来的。说难听点,用户不关心你遇到了什么 NAT。他只知道会议进不去,老师听不到,客户掉线了。

11.3 TURN 服务器的安全与成本控制依赖认证鉴权、流量限额和 IP 白名单

TURN 安全不要省。

必须开认证。长期账号密码可以,动态临时凭证更好。WebRTC 常见做法是业务服务端按用户生成短期 TURN credential

username = timestamp:userId
password = HMAC(secret, username)
ttl = 1 hour

coturn 支持 REST API 风格的临时凭证机制。这样即使凭证泄露,也只能短时间使用。

还要限制:

总带宽
单用户带宽
单 IP allocation 数
端口范围
可访问 peer 范围
日志告警
异常流量封禁

IP 白名单要谨慎。用户来自动态网络,白名单太死会误伤。但管理接口、运维端口必须只允许内网或堡垒机访问。coturn 的 CLI 管理也要注意,官方 Wiki 提到默认 CLI 可在 127.0.0.1 的 5766 端口接受 telnet 连接,生产里通常会关闭或严格保护。

11.4 成本估算模型按并发用户数与平均会话时长推算 TURN 带宽需求

估算从三个数开始:

峰值在线会话数
走 TURN 的比例
平均码率

公式:

TURN 峰值带宽 ≈ 会话数 × TURN 比例 × 单会话双向码率 × 2

假设:

峰值 10000 人
两人一通话,所以 5000 路会话
TURN 比例 20%
单端上行码率 800kbps

估算:

5000 × 20% × 800kbps × 2 = 1600Mbps

如果是多人会议 SFU 架构,模型更复杂。TURN 可能只负责客户端到 SFU 的可达性,不一定是 P2P 两端互通。但只要媒体经过 TURN,服务器带宽就是真金白银。

成本控制建议:

优化 ICE,让能直连的尽量直连
区域就近分配 TURN
控制视频码率上限
弱网下降级音频优先
监控 relay 使用比例
异常用户限流

别把 TURN 当 CDN 用。它不是为廉价大流量分发设计的。它是兜底,是救命绳。救命绳可以贵,但不能滥用。

12. 总结

12.1 NAT、STUN、TURN、ICE 的关系总览

把整篇文章压成一张图:

附录 A,关键 RFC 文档索引

附录 B,公共 STUN/TURN 测试服务器列表

注意:公共 STUN 只适合测试,生产环境请自建或使用云厂商服务。


附录 C,术语表


阅读原文:点击这里


该文章在 2026/7/18 23:29:50 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2026 ClickSun All Rights Reserved  粤ICP备13012886号-9  粤公网安备44030602007207号