【P2P音视频通信系统】之TURN 服务详解

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/
相关推荐
五号厂房2 小时前
Claude Code Agent Teams 实战指南
ai编程
三翼鸟数字化技术团队2 小时前
2025前端技术趋势:从智能到沉浸的新时代
前端·ai编程
xhyu612 小时前
【学习笔记】推荐系统 (5.排序:多目标模型、MMoE、融合预估分数、视频播放建模)
笔记·学习·音视频
小璐乱撞2 小时前
Serena MCP:给 AI 装上工程级导航,告别迷路式编程
人工智能·ai编程·mcp
子昕2 小时前
Claude Code新增远程控制功能
ai编程
灿宝宝lo2 小时前
阿里云OSS视频自动转码的配置详细步骤
阿里云·云计算·音视频
师范大学生2 小时前
cursor使用openSpec
ai编程
GeekyGuru3 小时前
入门与环境系列第二篇:环境准备:GPU 选型、conda、Docker、CUDA 一站式配置
ai编程
音视频牛哥3 小时前
Android终端如何用SmartGBD快速接入GB28181:把“非国标设备”变成“国标前端”
音视频·android gb28181·gb28181安卓端·smartgbd·gb28181对接·安卓对接gb28181·安卓gb28181记录仪