25、STUN/TURN:NAT穿透原理、STUN服务器配置、TURN中继服务器配置

说到WebRTC,绕不开的一个话题就是NAT穿透。我刚开始做音视频的时候,以为只要两端都连上公网就能直接P2P通信。结果第一次联调就翻车了——两边都在公司内网,互相看不见。嗯,那时候我才真正意识到,NAT这堵墙有多烦人。

说白了,WebRTC的底层逻辑是尽量走P2P,因为延迟低、带宽省。但现实是,大部分设备都躲在NAT后面。这时候就需要STUN和TURN这两位“救兵”了。今天我们就来聊聊它们是怎么工作的,以及怎么配置。

NAT穿透:为什么需要STUN和TURN?

先看一个最简单的场景。你在家里用笔记本,连的是路由器。路由器分配给你一个内网IP,比如192.168.1.5。你想和另一个同样在内网的朋友建立视频通话。你们俩的电脑都不知道对方的公网IP和端口。这就是NAT带来的问题。

STUN(Session Traversal Utilities for NAT)的作用很简单:帮你找到自己的公网IP和端口。你向STUN服务器发个请求,服务器看到你的请求是从哪个公网IP和端口来的,然后把这个信息返回给你。这样你就知道了自己的“外网身份”。

但STUN不是万能的。我在项目中遇到过一种情况:对称型NAT。这种NAT下,每次你向不同目标发请求,它都会分配不同的端口。STUN拿到的映射关系只对STUN服务器有效,换一个目标就不行了。这时候STUN就失效了。

那怎么办?TURN(Traversal Using Relays around NAT)上场。TURN服务器充当一个中继。你的媒体数据先发给TURN服务器,再由它转发给对方。虽然延迟高了,但至少能通。说白了,TURN是最后的保底方案。

核心结论: 能P2P就P2P(STUN帮忙找路),P2P不通就走中继(TURN兜底)。

STUN服务器配置实战

配置STUN服务器其实不复杂。WebRTC里,你只需要在创建RTCPeerConnection时传入一个iceServers数组。我个人习惯把STUN和TURN都配好,这样ICE框架会自动选择最优路径。

下面是一个典型的STUN配置示例:

const pc = new RTCPeerConnection({
  iceServers: [
    {
      urls: 'stun:stun.l.google.com:19302'
    }
  ]
});

这里用的是Google的公共STUN服务器。你可能会问:生产环境能用这个吗?我的建议是,开发测试可以,但正式项目最好自建STUN服务器,或者用云服务商的。因为公共STUN服务器不稳定,而且有频率限制。

自建STUN服务器也很简单。我常用coturn这个开源项目。安装后,配置一下监听端口和网卡就行。下面是一个最小配置:

# coturn 配置文件示例
listening-port=3478
fingerprint
lt-cred-mech
user=myuser:mypassword
realm=myrealm

启动后,把urls改成你自己的服务器地址:

iceServers: [
  {
    urls: 'stun:your-server-ip:3478'
  }
]
小技巧: 调试时可以在浏览器控制台看ICE候选者日志。如果看到"srflx"类型的候选者,说明STUN生效了。如果只有"host"类型,那说明没穿透成功。

TURN中继服务器配置

TURN的配置比STUN稍微复杂一点,因为需要认证。毕竟TURN会消耗服务器带宽,不能随便让人用。coturn同样支持TURN,配置时加上中继相关的参数即可。

下面是一个完整的coturn配置,同时支持STUN和TURN:

listening-port=3478
tls-listening-port=5349
fingerprint
lt-cred-mech
user=myuser:mypassword
realm=myrealm
total-quota=100
stale-nonce
no-loopback-peers
no-multicast-peers
mobility
relay-device=eth0
relay-ip=your-public-ip
min-port=49152
max-port=65535

注意这里的relay-ip要填服务器的公网IP。端口范围建议开大一点,因为每个连接会占用一个端口。我在生产环境遇到过端口耗尽的问题,后来把范围扩到10000个端口才解决。

前端配置时,需要加上用户名和密码:

iceServers: [
  {
    urls: 'turn:your-server-ip:3478',
    username: 'myuser',
    credential: 'mypassword'
  }
]
注意: 不要把明文密码硬编码在前端代码里。生产环境应该通过接口动态获取临时凭证。coturn支持REST API生成短期token,这样更安全。

ICE流程与NAT穿透决策

ICE(Interactive Connectivity Establishment)是WebRTC的“交通指挥官”。它收集所有可能的候选者(host、srflx、relay),然后按优先级排序,逐个尝试连接。一旦找到可用的路径,就停止尝试。

下面这张图展示了ICE的决策流程:

ICE 候选者收集与穿透决策流程 开始收集候选者 host 候选者(本机IP) srflx 候选者(STUN反射地址) relay 候选者(TURN中继地址) 优先级

ICE会先尝试host候选者(局域网直连)。如果失败,再尝试srflx候选者(STUN穿透)。最后才尝试relay候选者(TURN中继)。这个顺序是固定的,因为P2P延迟最低,中继延迟最高。

我曾经遇到过一个坑:明明STUN拿到了公网地址,但就是连不上。后来发现是防火墙把UDP端口封了。ICE会检测到UDP不通,自动回退到TCP,甚至TLS。所以配置TURN时,最好同时支持UDP和TCP。

避坑指南

  • STUN服务器不要只配一个。 我建议至少配两个,一个挂了还有备用。Google的公共STUN服务器虽然好用,但偶尔会超时。
  • TURN服务器的带宽要算好。 每个视频通话大约需要1-2Mbps的上行带宽。如果有100个用户同时走中继,服务器带宽至少要200Mbps。
  • 认证凭证要定期更换。 我见过有人把TURN密码写死在客户端,结果被刷了几百G流量。用短期token,比如有效期1小时。
  • 测试时打开chrome://webrtc-internals。 这里面能看到ICE候选者、连接状态、丢包率。排查问题必备。
一句话总结: STUN帮你找到路,TURN帮你铺条路。ICE负责选哪条路。配置好这三样,WebRTC的连通性就稳了。

好了,关于STUN和TURN的配置就聊到这里。实际项目中,你可能还需要考虑服务器部署位置、负载均衡、带宽监控等问题。但核心原理和配置方法,就是上面这些。动手搭一个试试,你会发现NAT穿透其实没那么神秘。

公众号:蓝海资料掘金营,微信deep3321