1. TURN 概述
1.1 什么是 TURN
TURN (Traversal Using Relays around NAT) 是一种网络协议,用于在无法建立 P2P 直连的情况下,通过中继服务器转发媒体数据。当 STUN 无法穿透 NAT 时,TURN 作为最后的解决方案确保连接成功。
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ TURN 在 WebRTC 中的作用 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
内网设备 A TURN 服务器 内网设备 B
(192.168.1.100) (公网 IP) (192.168.2.200)
│ │ │
│ │ │
│ ═════════════════════════════════════════════════════════════════════════════════
│ 场景: 双方都在 Symmetric NAT 后面,P2P 直连失败
│ ═════════════════════════════════════════════════════════════════════════════════
│ │ │
│ 1. A 无法直接连接 B (NAT 阻止) │ │
│ │ │
│ 2. A 向 TURN 服务器请求分配中继地址 │ │
│ ───────────────────────────────────────────────>│ │
│ │ │
│ 3. TURN 分配中继地址给 A │ │
│ Relay Address: 198.51.100.10:50000 │ │
│ <───────────────────────────────────────────────│ │
│ │ │
│ │ 4. B 也向 TURN 请求中继地址 │
│ │ <───────────────────────────────│
│ │ │
│ │ 5. TURN 分配中继地址给 B │
│ │ ───────────────────────────────>│
│ │ │
│ ═════════════════════════════════════════════════════════════════════════════════
│ 所有媒体数据通过 TURN 服务器中继转发
│ ═════════════════════════════════════════════════════════════════════════════════
│ │ │
│ 6. A 发送媒体数据到 TURN │ │
│ ───────────────────────────────────────────────>│ │
│ │ 7. TURN 转发给 B │
│ │ ───────────────────────────────>│
│ │ │
│ │ 8. B 发送媒体数据到 TURN │
│ │ <───────────────────────────────│
│ 9. TURN 转发给 A │ │
│ <───────────────────────────────────────────────│ │
│ │ │
│ <══════════════════ 通过 TURN 中继的双向通信 ══════════════════> │
│ │ │
1.2 为什么需要 TURN
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ STUN 无法解决的场景 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
Symmetric NAT 问题:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ 内网设备 A STUN 服务器 │
│ (Symmetric NAT) │
│ │ │ │
│ │ 1. A 向 STUN 发送请求 │ │
│ │ 映射: 192.168.1.100:5000 → 203.0.113.10:12345 │
│ │ ───────────────────────────────────────────────>│ │
│ │ │ │
│ │ 2. STUN 返回映射地址 │ │
│ │ <───────────────────────────────────────────────│ │
│ │ │ │
│ │ 3. A 尝试连接 B │ │
│ │ 新映射: 192.168.1.100:5000 → 203.0.113.10:12346 (端口变了!) │
│ │ │ │
│ │ 问题: STUN 返回的地址 12345 无法用于连接 B │
│ │ 因为连接 B 会创建新的映射 12346 │
│ │ │ │
│ │
│ 解决方案: 使用 TURN 服务器中继所有流量 │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
NAT 穿透成功率统计:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ ┌─────────────────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ STUN 成功率 (约 86%) TURN 成功率 (100%) │ │
│ │ ████████████████████████░░░░░░ ████████████████████████████████████████████████ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────────────────┘ │
│ │
│ - STUN 无法穿透: Symmetric NAT + Symmetric NAT 组合 (约 8-10%) │
│ - STUN 无法穿透: 企业级防火墙严格限制 │
│ - TURN 可以解决所有 NAT 穿透问题 │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
2. TURN 协议详解
2.1 TURN 消息类型
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ TURN 消息类型 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
TURN 基于 STUN 协议扩展,增加了以下消息类型:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ 消息类型 │ 类型值 │ 说明 │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│ Allocate Request │ 0x0003 │ 请求分配中继地址 │
│ Allocate Response │ 0x0103 │ 分配中继地址响应 │
│ Allocate Error Response │ 0x0113 │ 分配失败响应 │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│ Refresh Request │ 0x0004 │ 刷新分配的生命周期 │
│ Refresh Response │ 0x0104 │ 刷新响应 │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│ Send Request │ 0x0006 │ 发送数据到对端 │
│ Send Response │ 0x0106 │ 发送确认 │
│ Data Indication │ 0x0017 │ TURN 服务器转发数据给客户端 │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│ CreatePermission Request │ 0x0008 │ 创建对端权限 │
│ CreatePermission Response │ 0x0108 │ 权限创建响应 │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│ ChannelBind Request │ 0x0009 │ 绑定通道 │
│ ChannelBind Response │ 0x0109 │ 通道绑定响应 │
│ ChannelData │ 0x40XX │ 通道数据消息 │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
2.2 TURN 连接建立流程
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ TURN 连接建立流程 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
客户端 A TURN 服务器 客户端 B
│ │ │
│ 1. Allocate Request │ │
│ 请求分配中继地址 │ │
│ ───────────────────────────────────────────────>│ │
│ │ │
│ 2. Allocate Response │ │
│ XOR-RELAYED-ADDRESS: 198.51.100.10:50000 │ │
│ LIFETIME: 600 秒 │ │
│ <───────────────────────────────────────────────│ │
│ │ │
│ 3. CreatePermission Request │ │
│ 为 B 创建权限 │ │
│ ───────────────────────────────────────────────>│ │
│ │ │
│ 4. CreatePermission Response │ │
│ <───────────────────────────────────────────────│ │
│ │ │
│ 5. ChannelBind Request │ │
│ 绑定通道号 (如 0x4000) │ │
│ ───────────────────────────────────────────────>│ │
│ │ │
│ 6. ChannelBind Response │ │
│ <───────────────────────────────────────────────│ │
│ │ │
│ ═════════════════════════════════════════════════════════════════════════════════
│ 通道建立完成,可以开始发送数据
│ ═════════════════════════════════════════════════════════════════════════════════
│ │ │
│ 7. ChannelData (通道 0x4000) │ │
│ 发送媒体数据 │ │
│ ───────────────────────────────────────────────>│ │
│ │ 8. Data Indication │
│ │ ───────────────────────────────>│
│ │ │
│ │ 9. ChannelData (通道 0x4001) │
│ │ <───────────────────────────────│
│ 10. Data Indication │ │
│ <───────────────────────────────────────────────│ │
│ │ │
▼ ▼ ▼
2.3 TURN 数据传输模式
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ TURN 数据传输模式 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
模式一: Send/Data Indication (无通道模式)
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ 客户端 A TURN 服务器 │
│ │ │ │
│ │ Send Request │ │
│ │ + DATA 属性 (实际数据) │ │
│ │ + XOR-PEER-ADDRESS (目标地址) │ │
│ │ ──────────────────────────────────────────────────>│ │
│ │ │ │
│ │ Send Response │ │
│ │ <─────────────────────────────────────────────────│ │
│ │ │ │
│ │ (TURN 转发数据给对端) │ │
│ │ │ │
│ │ Data Indication │ │
│ │ + DATA 属性 (来自对端的数据) │ │
│ │ + XOR-PEER-ADDRESS (发送方地址) │ │
│ │ <─────────────────────────────────────────────────│ │
│ │ │ │
│ 优点: 简单,无需创建通道 │
│ 缺点: 每次发送都需要包含目标地址,开销较大 │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
模式二: ChannelData (通道模式) - 推荐
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ 客户端 A TURN 服务器 │
│ │ │ │
│ │ ChannelBind Request │ │
│ │ + CHANNEL-NUMBER (如 0x4000) │ │
│ │ + XOR-PEER-ADDRESS (目标地址) │ │
│ │ ──────────────────────────────────────────────────>│ │
│ │ │ │
│ │ ChannelBind Response │ │
│ │ <─────────────────────────────────────────────────│ │
│ │ │ │
│ │ ChannelData (通道 0x4000) │ │
│ │ 只需通道号 + 数据 │ │
│ │ ──────────────────────────────────────────────────>│ │
│ │ │ │
│ 优点: 数据包更小,效率更高,适合实时媒体传输 │
│ 缺点: 需要先创建通道绑定 │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
ChannelData 消息格式:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ 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 │
│ ┌───────────────────────┬───────────────────────┬─────────────────────────────────────────┐│
│ │ Channel Number │ Length (bytes) │ Application Data ││
│ │ (16 bits) │ (16 bits) │ ││
│ └───────────────────────┴───────────────────────┴─────────────────────────────────────────┘│
│ │
│ 通道号范围: 0x4000 - 0x7FFF (由客户端选择) │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
3. TURN 认证机制
3.1 长期凭证认证 (Long-Term Credential)
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ TURN 长期凭证认证 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
认证流程:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ 客户端 TURN 服务器 │
│ │ │ │
│ │ 1. Allocate Request │ │
│ │ (无认证信息) │ │
│ │ ─────────────────────────────────────────────────>│ │
│ │ │ │
│ │ 2. Allocate Error Response (401 Unauthorized) │ │
│ │ + REALM: "videocall" │ │
│ │ + NONCE: "随机字符串" │ │
│ │ <────────────────────────────────────────────────│ │
│ │ │ │
│ │ 3. 计算认证信息: │ │
│ │ key = MD5(username:realm:password) │ │
│ │ HMAC-SHA1(key, message) │ │
│ │ │ │
│ │ 4. Allocate Request │ │
│ │ + USERNAME: "videocall" │ │
│ │ + REALM: "videocall" │ │
│ │ + NONCE: "随机字符串" │ │
│ │ + MESSAGE-INTEGRITY: HMAC 值 │ │
│ │ ─────────────────────────────────────────────────>│ │
│ │ │ │
│ │ 5. 验证通过,返回 Allocate Response │ │
│ │ <────────────────────────────────────────────────│ │
│ │ │ │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
认证密钥计算:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ // 服务器端生成密钥 │
│ key := turn.GenerateAuthKey(username, realm, password) │
│ │
│ // 示例 │
│ username := "videocall" │
│ realm := "videocall" │
│ password := "turn123456" │
│ │
│ // key = MD5("videocall:videocall:turn123456") │
│ // 结果: 16 字节的二进制值 │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
3.2 本项目 TURN 认证配置
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ 项目 TURN 认证实现 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
服务器配置 (config.json):
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ { │
│ "turn": { │
│ "name": "turn服务器", │
│ "enabled": true, │
│ "server": "47.106.189.19", │
│ "port": 3479, │
│ "username": "videocall", │
│ "password": "turn123456" │
│ } │
│ } │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
服务器端认证代码 (stun_turn.go):
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ turnServer, err := turn.NewServer(turn.ServerConfig{ │
│ Realm: "videocall", │
│ AuthHandler: func(username string, realm string, srcAddr net.Addr) ([]byte, bool) { │
│ // 验证用户名 │
│ if username == gConfig.Turn.Username { │
│ // 生成认证密钥并返回 │
│ key := turn.GenerateAuthKey(username, realm, gConfig.Turn.Password) │
│ return key, true │
│ } │
│ return nil, false │
│ }, │
│ PacketConnConfigs: []turn.PacketConnConfig{{ │
│ PacketConn: turnListener, │
│ RelayAddressGenerator: &turn.RelayAddressGeneratorStatic{ │
│ RelayAddress: net.ParseIP(publicIP), │
│ Address: "0.0.0.0", │
│ }, │
│ }}, │
│ }) │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
客户端配置 (PeerConnectionManager.kt):
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ turnConfig?.let { turn -> │
│ if (turn.optBoolean("enabled", false)) { │
│ val server = turn.optString("server", "") │
│ val port = turn.optInt("port", 3479) │
│ val username = turn.optString("username", "") │
│ val password = turn.optString("password", "") │
│ val turnUrl = "turn:$server:$port" │
│ iceServers.add( │
│ PeerConnection.IceServer.builder(turnUrl) │
│ .setUsername(username) │
│ .setPassword(password) │
│ .createIceServer() │
│ ) │
│ } │
│ } │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
4. TURN 服务器搭建
4.1 使用 pion/turn 搭建 (本项目方案)
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ 使用 pion/turn 搭建 TURN 服务器 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
依赖安装:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ go get github.com/pion/turn/v2 │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
完整服务器代码:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ package main │
│ │
│ import ( │
│ "fmt" │
│ "net" │
│ "github.com/pion/turn/v2" │
│ ) │
│ │
│ func main() { │
│ // 公网 IP (需要替换为实际 IP) │
│ publicIP := "47.106.189.19" │
│ │
│ // TURN 监听地址 │
│ turnAddr := "0.0.0.0:3479" │
│ │
│ // 创建 UDP 监听器 │
│ turnListener, err := net.ListenPacket("udp", turnAddr) │
│ if err != nil { │
│ panic(err) │
│ } │
│ │
│ // 认证配置 │
│ realm := "videocall" │
│ username := "videocall" │
│ password := "turn123456" │
│ │
│ // 创建 TURN 服务器 │
│ turnServer, err := turn.NewServer(turn.ServerConfig{ │
│ Realm: realm, │
│ AuthHandler: func(username, realm string, srcAddr net.Addr) ([]byte, bool) { │
│ if username == username { │
│ key := turn.GenerateAuthKey(username, realm, password) │
│ return key, true │
│ } │
│ return nil, false │
│ }, │
│ PacketConnConfigs: []turn.PacketConnConfig{{ │
│ PacketConn: turnListener, │
│ RelayAddressGenerator: &turn.RelayAddressGeneratorStatic{ │
│ RelayAddress: net.ParseIP(publicIP), │
│ Address: "0.0.0.0", │
│ }, │
│ }}, │
│ }) │
│ if err != nil { │
│ panic(err) │
│ } │
│ │
│ fmt.Printf("TURN server started on %s (public: %s:3479)\n", turnAddr, publicIP) │
│ │
│ // 保持运行 │
│ select {} │
│ } │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
4.2 使用 coturn 搭建 (生产环境推荐)
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ 使用 coturn 搭建 TURN 服务器 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
安装 coturn:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ # Ubuntu/Debian │
│ sudo apt-get update │
│ sudo apt-get install coturn │
│ │
│ # CentOS/RHEL │
│ sudo yum install coturn │
│ │
│ # macOS │
│ brew install coturn │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
配置文件 (/etc/turnserver.conf):
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ # 监听端口 │
│ listening-port=3478 │
│ tls-listening-port=5349 │
│ │
│ # 监听地址 │
│ listening-ip=0.0.0.0 │
│ │
│ # 公网 IP (必须配置) │
│ external-ip=47.106.189.19 │
│ │
│ # Realm │
│ realm=videocall │
│ │
│ # 认证配置 │
│ user=videocall:turn123456 │
│ │
│ # 启用长期凭证认证 │
│ lt-cred-mech │
│ │
│ # 日志配置 │
│ log-file=/var/log/turnserver.log │
│ verbose │
│ │
│ # 安全配置 │
│ no-multicast-peers │
│ no-cli │
│ │
│ # TLS 配置 (可选) │
│ cert=/etc/ssl/certs/cert.pem │
│ pkey=/etc/ssl/private/key.pem │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
启动服务:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ # 启动 TURN 服务 │
│ sudo systemctl start coturn │
│ │
│ # 设置开机自启 │
│ sudo systemctl enable coturn │
│ │
│ # 检查状态 │
│ sudo systemctl status coturn │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
4.3 云服务商 TURN 服务
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ 云服务商 TURN 服务 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ 服务商 │ 服务名称 │ 特点 │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│ Twilio │ Network Traversal Service │ 全球节点,按使用量计费 │
│ Xirsys │ Xirsys TURN │ 多种套餐,支持 WebRTC │
│ Amazon │ Amazon KVS │ 与 AWS 服务集成 │
│ Google │ Google Cloud TURN │ 与 Google Cloud 集成 │
│ Agora │ Agora Cloud │ 实时音视频服务,包含 TURN │
│ 声网 │ 声网云 │ 国内服务商,低延迟 │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
选择建议:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ 1. 小规模应用: 使用公共 STUN + 自建 TURN │
│ 2. 中等规模: 自建 STUN/TURN 服务器集群 │
│ 3. 大规模/企业级: 使用云服务商方案或混合部署 │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
5. TURN 与 STUN 对比
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ TURN 与 STUN 详细对比 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ 特性 │ STUN │ TURN │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│ 全称 │ Session Traversal │ Traversal Using Relay │
│ │ Utilities for NAT │ around NAT │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│ RFC 文档 │ RFC 5389 │ RFC 5766 │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│ 主要功能 │ 获取公网映射地址 │ 中继所有媒体数据 │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│ 数据路径 │ P2P 直连 │ 所有数据经过服务器 │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│ 服务器负载 │ 极低 (仅信令) │ 高 (处理所有媒体) │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│ 带宽消耗 │ 几乎无 │ 大量消耗服务器带宽 │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│ 延迟 │ 低 (直连) │ 较高 (多一跳中继) │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│ NAT 穿透成功率 │ ~86% │ 100% │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│ 需要认证 │ 否 │ 是 (长期凭证) │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│ 部署成本 │ 低 │ 高 (带宽+计算) │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│ 使用场景 │ 首选方案 │ 备选方案 │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│ 适用 NAT 类型 │ 大部分 NAT │ 所有 NAT (包括 Symmetric) │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
成本对比示例:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ 假设: 1000 个并发通话,每路通话 2Mbps │
│ │
│ STUN 服务器: │
│ - CPU: 1 核 │
│ - 内存: 512MB │
│ - 带宽: 几乎无 (仅信令) │
│ - 月成本: ~$10 │
│ │
│ TURN 服务器 (假设 10% 需要中继): │
│ - CPU: 4 核 │
│ - 内存: 4GB │
│ - 带宽: 100 路通话 × 2Mbps = 200Mbps │
│ - 月成本: ~$200-500 (带宽为主) │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
6. ICE 候选者中的 TURN
6.1 Relay Candidate 详解
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ Relay Candidate 详解 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
Relay Candidate 格式:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ candidate:4 1 UDP 41885439 198.51.100.10 50000 typ relay raddr 198.51.100.10 rport 50000 │
│ │
│ 解析: │
│ - candidate:4 - 候选者 ID │
│ - 1 - 组件 ID (1 = RTP, 2 = RTCP) │
│ - UDP - 传输协议 │
│ - 41885439 - 优先级 (最低) │
│ - 198.51.100.10 - 中继地址 (TURN 服务器分配) │
│ - 50000 - 中继端口 │
│ - typ relay - 候选者类型 │
│ - raddr - 相关地址 (TURN 服务器地址) │
│ - rport - 相关端口 │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
优先级计算:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ 优先级公式: │
│ priority = (2^24) * (type preference) + (2^8) * (local preference) + (256 - component ID) │
│ │
│ 类型偏好值: │
│ - host: 126 (最高) │
│ - prflx: 110 │
│ - srflx: 100 │
│ - relay: 0 (最低) │
│ │
│ 示例计算 (relay): │
│ priority = (2^24) * 0 + (2^8) * 0 + (256 - 1) │
│ = 0 + 0 + 255 │
│ = 255 (非常低) │
│ │
│ 实际 WebRTC 库会给 relay 一个非零的 type preference,通常在 0-10 之间 │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
6.2 ICE 连接选择流程
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ ICE 连接选择流程 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
客户端 A 服务器 客户端 B
│ │ │
│ 1. 收集所有候选者 │ │
│ - host: 192.168.1.100:5000 │ │
│ - srflx: 203.0.113.10:12345 │ │
│ - relay: 198.51.100.10:50000 │ │
│ │ │
│ 2. 交换候选者 (通过信令服务器) │ │
│ ───────────────────────────────────────────────>│ │
│ │ ───────────────────────────────>│
│ │ │
│ 3. 按优先级排序候选者对 │ │
│ (host-host) > (host-srflx) > ... > (relay-relay) │
│ │ │
│ 4. 连接检查 (Connectivity Check) │ │
│ │ │
│ ┌─────────────────────────────────────────────┤ │
│ │ 检查顺序: │ │
│ │ │ │
│ │ ① host A ↔ host B (同局域网) │ │
│ │ ↓ 失败 │ │
│ │ ② host A ↔ srflx B (A直连B公网) │ │
│ │ ↓ 失败 │ │
│ │ ③ srflx A ↔ host B (B直连A公网) │ │
│ │ ↓ 失败 │ │
│ │ ④ srflx A ↔ srflx B (双方公网) │ │
│ │ ↓ 失败 (Symmetric NAT) │ │
│ │ ⑤ relay A ↔ relay B (TURN中继) │ │
│ │ ↓ 成功! │ │
│ └─────────────────────────────────────────────┘ │
│ │ │
│ 5. 选择成功候选者对建立连接 │ │
│ 使用 TURN 中继进行通信 │ │
│ │ │
▼ ▼ ▼
7. TURN 性能与优化
7.1 性能指标
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ TURN 服务器性能指标 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
关键指标:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ 1. 并发连接数 │
│ - 每个通话需要 1 个 TURN 分配 │
│ - 建议: 每核支持 500-1000 并发 │
│ │
│ 2. 带宽吞吐量 │
│ - 视频通话: 1-4 Mbps/路 │
│ - 音频通话: 50-100 Kbps/路 │
│ - 建议: 1Gbps 带宽支持 ~250 路视频通话 │
│ │
│ 3. 延迟 │
│ - 额外中继延迟: 10-50ms │
│ - 目标: 总延迟 < 200ms │
│ │
│ 4. CPU 使用率 │
│ - 主要消耗: 数据包转发、加密 │
│ - 建议: CPU < 70% │
│ │
│ 5. 内存使用 │
│ - 每连接: ~100KB │
│ - 1000 并发: ~100MB │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
7.2 优化建议
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ TURN 服务器优化建议 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
1. 网络优化:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ - 选择低延迟的数据中心 │
│ - 使用多线 BGP 接入 │
│ - 配置足够的带宽冗余 (建议 2x 峰值) │
│ - 启用网卡多队列 │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
2. 服务器优化:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ # 增加文件描述符限制 │
│ ulimit -n 1000000 │
│ │
│ # 内核参数优化 │
│ net.core.rmem_max = 134217728 │
│ net.core.wmem_max = 134217728 │
│ net.ipv4.tcp_rmem = 4096 87380 67108864 │
│ net.ipv4.tcp_wmem = 4096 65536 67108864 │
│ net.core.netdev_max_backlog = 5000 │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
3. 应用层优化:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ - 使用 ChannelData 模式减少开销 │
│ - 设置合理的分配生命周期 (LIFETIME) │
│ - 实现连接复用 │
│ - 启用 UDP 而非 TCP (性能更好) │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
4. 负载均衡:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ ┌─────────────┐ │
│ │ 负载均衡器 │ │
│ └──────┬──────┘ │
│ │ │
│ ┌────────────────┼────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ TURN Server │ │ TURN Server │ │ TURN Server │ │
│ │ #1 │ │ #2 │ │ #3 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ 方案: │
│ - DNS 轮询 │
│ - Anycast IP │
│ - 应用层负载均衡 │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
8. 监控与运维
8.1 监控指标
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ TURN 服务器监控指标 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
关键监控项:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ 1. 连接统计 │
│ - 当前活跃分配数 │
│ - 每秒新建分配数 │
│ - 分配成功率 │
│ │
│ 2. 流量统计 │
│ - 入站/出站带宽 │
│ - 数据包速率 │
│ - 平均包大小 │
│ │
│ 3. 性能指标 │
│ - 请求延迟 │
│ - 转发延迟 │
│ - 错误率 │
│ │
│ 4. 资源使用 │
│ - CPU 使用率 │
│ - 内存使用量 │
│ - 网络缓冲区使用 │
│ │
│ 5. 认证统计 │
│ - 认证成功/失败次数 │
│ - 认证失败原因分布 │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
8.2 日志配置
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ TURN 服务器日志配置 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
coturn 日志配置:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ # /etc/turnserver.conf │
│ │
│ # 日志文件 │
│ log-file=/var/log/turnserver/turnserver.log │
│ │
│ # 详细日志 │
│ verbose │
│ │
│ # 更详细的日志 │
│ Verbose │
│ │
│ # 日志轮转 │
│ log-binding │
│ log-session │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
日志分析示例:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ # 查看活跃连接 │
│ turnadmin -l -N videocall │
│ │
│ # 查看连接统计 │
│ turnadmin -S -N videocall │
│ │
│ # 实时监控日志 │
│ tail -f /var/log/turnserver/turnserver.log | grep -E "session|allocation" │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
9. 安全配置
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ TURN 服务器安全配置 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
1. 访问控制:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ # 限制允许的 IP 范围 │
│ allowed-peer-ip=203.0.113.0/24 │
│ allowed-peer-ip=198.51.100.0/24 │
│ │
│ # 禁止多播 │
│ no-multicast-peers │
│ │
│ # 禁止 CLI 管理 │
│ no-cli │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
2. 认证安全:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ # 使用长期凭证 │
│ lt-cred-mech │
│ │
│ # 配置用户名密码 │
│ user=videocall:turn123456 │
│ │
│ # 或使用数据库认证 │
│ userdb=/etc/turnuserdb.conf │
│ │
│ # 设置 Realm │
│ realm=videocall │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
3. TLS 加密:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ # TLS 证书配置 │
│ cert=/etc/ssl/certs/turn-server.pem │
│ pkey=/etc/ssl/private/turn-server.key │
│ │
│ # TLS 端口 │
│ tls-listening-port=5349 │
│ │
│ # 强制 TLS │
│ secure-stun │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
4. 资源限制:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ # 最大带宽限制 (bps) │
│ max-bps=1000000 │
│ │
│ # 分配生命周期 (秒) │
│ stale-nonce=600 │
│ │
│ # 通道超时 │
│ channel-lifetime=600 │
│ │
│ # 权限超时 │
│ permission-lifetime=300 │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
5. DDoS 防护:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ # 使用防火墙限制连接速率 │
│ iptables -A INPUT -p udp --dport 3478 -m limit --limit 100/s --limit-burst 200 -j ACCEPT │
│ iptables -A INPUT -p udp --dport 3478 -j DROP │
│ │
│ # 连接数限制 │
│ max-allocate-lifetime=3600 │
│ allocation-default-lifetime=1800 │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
10. 故障排查
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ TURN 常见问题排查 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
问题 1: TURN 分配失败
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ 现象: 客户端无法获取 TURN 中继地址 │
│ │
│ 可能原因: │
│ - 认证失败 (用户名/密码错误) │
│ - 服务器资源不足 │
│ - 防火墙阻止 │
│ │
│ 排查步骤: │
│ 1. 检查服务器日志: tail -f /var/log/turnserver.log │
│ 2. 验证认证信息: turnutils_uclient -u user -p pass server_ip │
│ 3. 检查端口: netstat -ulnp | grep 3478 │
│ 4. 测试连通性: nc -u server_ip 3478 │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
问题 2: 数据无法转发
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ 现象: TURN 连接成功但无法传输数据 │
│ │
│ 可能原因: │
│ - 权限未创建 │
│ - 通道未绑定 │
│ - peer IP 不在允许列表 │
│ │
│ 排查步骤: │
│ 1. 检查 CreatePermission 是否成功 │
│ 2. 检查 ChannelBind 是否成功 │
│ 3. 检查 allowed-peer-ip 配置 │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
问题 3: 连接频繁断开
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ 现象: TURN 连接建立后很快断开 │
│ │
│ 可能原因: │
│ - 分配超时未刷新 │
│ - 网络不稳定 │
│ - 服务器重启 │
│ │
│ 排查步骤: │
│ 1. 检查 LIFETIME 设置 │
│ 2. 确认客户端是否发送 Refresh │
│ 3. 检查服务器稳定性 │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
问题 4: 性能问题
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ 现象: TURN 服务器响应慢或丢包 │
│ │
│ 可能原因: │
│ - 带宽不足 │
│ - CPU 过载 │
│ - 网络拥塞 │
│ │
│ 排查步骤: │
│ 1. 检查带宽使用: iftop -i eth0 │
│ 2. 检查 CPU: top -p $(pgrep turnserver) │
│ 3. 检查网络延迟: ping server_ip │
│ 4. 检查丢包: mtr server_ip │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
测试工具:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ # 使用 turnutils_uclient 测试 │
│ turnutils_uclient -u videocall -p turn123456 -v 47.106.189.19 │
│ │
│ # 使用 turnutils_stun 测试 STUN │
│ turnutils_stun 47.106.189.19 │
│ │
│ # 使用 Wireshark 抓包分析 │
│ # 过滤器: stun || turn │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
11. 参考资料
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ 相关 RFC 文档 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
- RFC 5766: TURN - Traversal Using Relays around NAT
- RFC 6156: TURN Extension for IPv6
- RFC 7065: TURN URI Scheme
- RFC 7350: TURN Server Auto-Discovery
- RFC 8656: TURN (更新版本,取代 RFC 5766)
开源项目:
- pion/turn: https://github.com/pion/turn (Go 语言)
- coturn: https://github.com/coturn/coturn (C 语言)
- eturnal: https://github.com/processone/eturnal (Erlang)
在线测试工具:
- Trickle ICE: https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/
- ICE Test: https://icetest.info/