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

STUN 服务详解

1. STUN 概述

1.1 什么是 STUN

STUN (Session Traversal Utilities for NAT) 是一种网络协议,用于帮助位于 NAT (Network Address Translation) 后面的设备发现自己的公网 IP 地址和端口。

复制代码
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│                                    STUN 在 WebRTC 中的作用                                    │
└──────────────────────────────────────────────────────────────────────────────────────────────┘

     内网设备 A                                          STUN 服务器                    内网设备 B
    (192.168.1.100)                                    (公网 IP)                    (192.168.2.200)
           │                                                │                               │
           │  1. 发送 STUN Binding Request                  │                               │
           │     源地址: 192.168.1.100:5000                │                               │
           │ ───────────────────────────────────────────────>│                               │
           │                                                │                               │
           │  2. STUN 服务器看到的是 NAT 映射后的地址         │                               │
           │     公网地址: 203.0.113.10:12345               │                               │
           │                                                │                               │
           │  3. 返回 STUN Binding Response                 │                               │
           │     包含: MAPPED-ADDRESS = 203.0.113.10:12345  │                               │
           │ <───────────────────────────────────────────────│                               │
           │                                                │                               │
           │  4. 设备 A 知道了自己的公网地址                  │                               │
           │     可以通过 ICE 告知对方                       │                               │
           │                                                │                               │
           │                                                │  5. 设备 B 也通过 STUN 获取公网地址
           │                                                │                               │
           │  ═════════════════════════════════════════════════════════════════════════════════
           │  通过交换 ICE Candidates,双方尝试建立 P2P 连接                                    │
           │  ═════════════════════════════════════════════════════════════════════════════════
           │                                                │                               │
           │  <══════════════════════════════ P2P 连接建立 ═══════════════════════════════>   │
           │                                                │                               │

1.2 为什么需要 STUN

复制代码
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│                                    NAT 穿透问题                                               │
└──────────────────────────────────────────────────────────────────────────────────────────────┘

问题场景:
┌─────────────────┐                              ┌─────────────────┐
│   内网设备 A     │                              │   内网设备 B     │
│ 192.168.1.100   │                              │ 192.168.2.200   │
└────────┬────────┘                              └────────┬────────┘
         │                                                │
         │  设备 A 只知道自己的内网地址                      │
         │  设备 B 也只知道自己的内网地址                    │
         │                                                │
         │  问题: 双方无法直接通信,因为:                    │
         │  1. 不知道对方的公网地址                         │
         │  2. 即使知道公网地址,NAT 可能阻止入站连接        │
         │                                                │

STUN 解决方案:
┌─────────────────┐      ┌─────────────┐      ┌─────────────────┐
│   内网设备 A     │──────│  STUN 服务器  │──────│   内网设备 B     │
│ 192.168.1.100   │      │  (公网可达)   │      │ 192.168.2.200   │
└────────┬────────┘      └─────────────┘      └────────┬────────┘
         │                                            │
         │  1. 通过 STUN 获取公网地址                   │
         │  2. 通过信令服务器交换地址信息               │
         │  3. 尝试建立 P2P 连接                       │
         │                                            │

2. NAT 类型详解

2.1 NAT 类型分类

复制代码
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│                                    NAT 类型及其穿透难度                                       │
└──────────────────────────────────────────────────────────────────────────────────────────────┘

1. Full Cone NAT (完全圆锥型) - 最容易穿透
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│                                                                                             │
│   内网地址              NAT 映射                        外网                               │
│   192.168.1.100:5000 ────────> 203.0.113.10:12345 ────────> 任何外部地址都可以发送数据     │
│                                                                                             │
│   特点: 一旦内部地址映射到外部地址,任何外部主机都可以通过该映射发送数据                      │
│   穿透难度: ★☆☆☆☆ (容易)                                                                   │
│                                                                                             │
└─────────────────────────────────────────────────────────────────────────────────────────────┘

2. Restricted Cone NAT (限制圆锥型)
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│                                                                                             │
│   内网地址              NAT 映射                        外网                               │
│   192.168.1.100:5000 ────────> 203.0.113.10:12345 ────────> 只有内网主机曾经发送过数据的外部│
│                                                            IP 才能发送数据                   │
│                                                                                             │
│   特点: 外部主机 IP 必须匹配内部主机曾经发送过的目标 IP                                      │
│   穿透难度: ★★☆☆☆ (较容易)                                                                 │
│                                                                                             │
└─────────────────────────────────────────────────────────────────────────────────────────────┘

3. Port Restricted Cone NAT (端口限制圆锥型)
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│                                                                                             │
│   内网地址              NAT 映射                        外网                               │
│   192.168.1.100:5000 ────────> 203.0.113.10:12345 ────────> 只有内网主机曾经发送过数据的外部│
│                                                            IP:Port 才能发送数据             │
│                                                                                             │
│   特点: 外部主机 IP 和端口都必须匹配                                                         │
│   穿透难度: ★★★☆☆ (中等)                                                                   │
│                                                                                             │
└─────────────────────────────────────────────────────────────────────────────────────────────┘

4. Symmetric NAT (对称型) - 最难穿透
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│                                                                                             │
│   内网地址              NAT 映射                        外网                               │
│   192.168.1.100:5000 ────────> 203.0.113.10:12345 ────────> 目标 A (不同目标不同映射)       │
│   192.168.1.100:5000 ────────> 203.0.113.10:12346 ────────> 目标 B                          │
│                                                                                             │
│   特点: 每个目标地址都会创建新的映射,STUN 获取的地址无法用于其他目标                         │
│   穿透难度: ★★★★★ (困难,通常需要 TURN)                                                     │
│                                                                                             │
└─────────────────────────────────────────────────────────────────────────────────────────────┘

2.2 NAT 类型检测流程

复制代码
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│                                    NAT 类型检测 (RFC 3489)                                    │
└──────────────────────────────────────────────────────────────────────────────────────────────┘

     客户端                                    STUN 服务器 (IP1:Port1)              STUN 服务器 (IP2:Port2)
        │                                                │                                       │
        │  1. Binding Request (从 IP1:Port1 获取映射)     │                                       │
        │ ───────────────────────────────────────────────>│                                       │
        │                                                │                                       │
        │  2. Binding Response                           │                                       │
        │     MAPPED-ADDRESS = PublicIP:PortA            │                                       │
        │ <───────────────────────────────────────────────│                                       │
        │                                                │                                       │
        │  3. Binding Request (请求从 IP2:Port2 响应)     │                                       │
        │ ───────────────────────────────────────────────>│                                       │
        │                                                │                                       │
        │  4. Binding Response (从 IP2:Port2 返回)        │                                       │
        │ <───────────────────────────────────────────────┼───────────────────────────────────────│
        │                                                │                                       │
        │  5. Binding Request (改变 IP 和 Port)           │                                       │
        │ ───────────────────────────────────────────────>│                                       │
        │                                                │                                       │
        │  6. 分析响应判断 NAT 类型:                       │                                       │
        │                                                │                                       │
        │  ┌─────────────────────────────────────────────┤                                       │
        │  │ 如果映射地址与本地地址相同 → 无 NAT          │                                       │
        │  │ 如果两次映射相同 → Cone NAT                 │                                       │
        │  │ 如果两次映射不同 → Symmetric NAT            │                                       │
        │  │ 如果只有特定 IP 能响应 → Restricted Cone    │                                       │
        │  │ 如果只有特定 IP:Port 能响应 → Port Restricted│                                       │
        │  └─────────────────────────────────────────────┘                                       │
        │                                                │                                       │
        ▼                                                ▼                                       ▼

3. STUN 协议详解

3.1 STUN 消息格式

复制代码
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│                                    STUN 消息头格式 (20 字节)                                  │
└──────────────────────────────────────────────────────────────────────────────────────────────┘

  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
 ┌─────────────────────────────────────────────────────────────────┐
 │0 0|     STUN Message Type (14 bits)     │  Message Length (16) │
 ├─────────────────────────────────────────────────────────────────┤
 │                                                                 │
 │                    Magic Cookie (32 bits)                       │
 │                                                                 │
 ├─────────────────────────────────────────────────────────────────┤
 │                                                                 │
 │                                                                 │
 │                  Transaction ID (96 bits)                       │
 │                                                                 │
 │                                                                 │
 └─────────────────────────────────────────────────────────────────┘

字段说明:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│  STUN Message Type (2 字节):                                                                │
│    - 0x0001: Binding Request (绑定请求)                                                      │
│    - 0x0101: Binding Response (绑定响应)                                                     │
│    - 0x0111: Binding Error Response (绑定错误响应)                                           │
│                                                                                             │
│  Message Length (2 字节): 消息体的长度(不包括 20 字节的头部)                               │
│                                                                                             │
│  Magic Cookie (4 字节): 固定值 0x2112A442,用于标识 STUN 消息                                │
│                                                                                             │
│  Transaction ID (12 字节): 唯一标识符,用于匹配请求和响应                                    │
└─────────────────────────────────────────────────────────────────────────────────────────────┘

3.2 STUN 属性 (Attributes)

复制代码
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│                                    常用 STUN 属性                                             │
└──────────────────────────────────────────────────────────────────────────────────────────────┘

属性格式:
  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
 ┌───────────────────────┬───────────────────────┬───────────────┐
 │   Type (16 bits)      │   Length (16 bits)    │   Value ...   │
 └───────────────────────┴───────────────────────┴───────────────┘

常用属性类型:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│  属性名称              │ 类型值    │ 说明                                                  │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│  MAPPED-ADDRESS       │ 0x0001   │ NAT 映射后的公网地址和端口                             │
│  XOR-MAPPED-ADDRESS   │ 0x0020   │ XOR 加密后的映射地址(推荐使用)                       │
│  CHANGE-REQUEST       │ 0x0003   │ 请求服务器从不同地址/端口响应                          │
│  SOURCE-ADDRESS       │ 0x0004   │ 响应消息的源地址                                       │
│  CHANGED-ADDRESS      │ 0x0005   │ 服务器的备用地址                                       │
│  ERROR-CODE           │ 0x0009   │ 错误代码和原因                                         │
│  SOFTWARE             │ 0x8022   │ 软件名称和版本                                         │
│  FINGERPRINT          │ 0x8028   │ 消息指纹,用于验证消息完整性                           │
└─────────────────────────────────────────────────────────────────────────────────────────────┘

3.3 STUN 交互示例

复制代码
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│                                    STUN Binding 请求/响应示例                                 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘

Binding Request (客户端 → STUN 服务器):
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│  STUN Header:                                                                               │
│    Message Type: 0x0001 (Binding Request)                                                   │
│    Message Length: 0x0000 (无属性)                                                          │
│    Magic Cookie: 0x2112A442                                                                 │
│    Transaction ID: 0xB7E7A701BC34DA99563F1209 (随机生成)                                    │
│                                                                                             │
│  实际数据 (十六进制):                                                                        │
│  00 01 00 00              // Type + Length                                                  │
│  21 12 A4 42              // Magic Cookie                                                   │
│  B7 E7 A7 01 BC 34 DA 99  // Transaction ID (前 8 字节)                                     │
│  56 3F 12 09              // Transaction ID (后 4 字节)                                     │
└─────────────────────────────────────────────────────────────────────────────────────────────┘

Binding Response (STUN 服务器 → 客户端):
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│  STUN Header:                                                                               │
│    Message Type: 0x0101 (Binding Response)                                                  │
│    Message Length: 0x0018 (24 字节属性)                                                     │
│    Magic Cookie: 0x2112A442                                                                 │
│    Transaction ID: 0xB7E7A701BC34DA99563F1209 (与请求相同)                                  │
│                                                                                             │
│  XOR-MAPPED-ADDRESS 属性:                                                                   │
│    Type: 0x0020                                                                             │
│    Length: 0x0008                                                                           │
│    Family: 0x01 (IPv4)                                                                      │
│    Port: 0x3039 (XOR 后 = 12345)                                                            │
│    Address: 0xCB00710A (XOR 后 = 203.0.113.10)                                              │
│                                                                                             │
│  解析结果: 公网地址 = 203.0.113.10:12345                                                    │
└─────────────────────────────────────────────────────────────────────────────────────────────┘

4. STUN 服务器搭建

4.1 本项目 STUN 服务器实现

复制代码
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│                                    项目 STUN 服务器架构                                       │
└──────────────────────────────────────────────────────────────────────────────────────────────┘

服务器配置 (config.json):
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│  {                                                                                          │
│    "stun": {                                                                                │
│      "name": "stun服务器",                                                                  │
│      "enabled": true,                                                                       │
│      "server": "47.106.189.19",     // 服务器公网 IP                                        │
│      "port": 3478                   // STUN 服务端口                                         │
│    },                                                                                       │
│    "turn": {                                                                                │
│      "name": "turn服务器",                                                                  │
│      "enabled": true,                                                                       │
│      "server": "47.106.189.19",     // 服务器公网 IP                                        │
│      "port": 3479,                  // TURN 服务端口                                         │
│      "username": "videocall",       // TURN 认证用户名                                       │
│      "password": "turn123456"       // TURN 认证密码                                         │
│    }                                                                                        │
│  }                                                                                          │
└─────────────────────────────────────────────────────────────────────────────────────────────┘

服务器代码 (stun_turn.go):
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│  使用 pion/turn 库实现 STUN/TURN 服务器:                                                    │
│                                                                                             │
│  1. 解析公网 IP:                                                                            │
│     publicIP, err := resolvePublicIP(gConfig.Stun.Server)                                  │
│                                                                                             │
│  2. 创建 STUN 服务器:                                                                       │
│     stunListener, _ := net.ListenPacket("udp", "0.0.0.0:3478")                             │
│     stunServer, _ := turn.NewServer(turn.ServerConfig{                                     │
│       PacketConnConfigs: []turn.PacketConnConfig{{                                         │
│         PacketConn: stunListener,                                                          │
│         RelayAddressGenerator: &turn.RelayAddressGeneratorStatic{                          │
│           RelayAddress: net.ParseIP(publicIP),                                             │
│           Address: "0.0.0.0",                                                              │
│         },                                                                                 │
│       }},                                                                                  │
│     })                                                                                     │
│                                                                                             │
│  3. 创建 TURN 服务器 (带认证):                                                              │
│     turnServer, _ := turn.NewServer(turn.ServerConfig{                                     │
│       Realm: "videocall",                                                                  │
│       AuthHandler: func(username, realm string, srcAddr net.Addr) ([]byte, bool) {         │
│         if username == gConfig.Turn.Username {                                             │
│           return turn.GenerateAuthKey(username, realm, gConfig.Turn.Password), true        │
│         }                                                                                  │
│         return nil, false                                                                  │
│       },                                                                                   │
│       ...                                                                                  │
│     })                                                                                     │
└─────────────────────────────────────────────────────────────────────────────────────────────┘

4.2 公网 STUN 服务器列表

复制代码
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│                                    常用公共 STUN 服务器                                       │
└──────────────────────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│  提供商              │ STUN 服务器地址                    │ 端口                             │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│  Google              │ stun.l.google.com                  │ 19302                            │
│  Google              │ stun1.l.google.com                 │ 19302                            │
│  Google              │ stun2.l.google.com                 │ 19302                            │
│  Google              │ stun3.l.google.com                 │ 19302                            │
│  Google              │ stun4.l.google.com                 │ 19302                            │
│  Twilio              │ stun:stun.twilio.com               │ 3478                             │
│  Nextcloud           │ stun.nextcloud.com                 │ 443                              │
│  OpenVPN            │ stun.openvpn.net                   │ 3478                             │
│  Vodafone           │ stun.vodafone.net                  │ 3478                             │
│  StunProtocol.org   │ stun.stunprotocol.org              │ 3478                             │
└─────────────────────────────────────────────────────────────────────────────────────────────┘

使用建议:
1. 生产环境建议搭建自己的 STUN 服务器,避免依赖公共服务
2. 可以配置多个 STUN 服务器作为备份
3. 公共 STUN 服务器可能存在不稳定或被限制的情况

5. ICE 候选者 (ICE Candidates)

5.1 ICE 候选者类型

复制代码
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│                                    ICE 候选者类型详解                                         │
└──────────────────────────────────────────────────────────────────────────────────────────────┘

1. Host Candidate (主机候选者) - 优先级最高
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│                                                                                             │
│   特点: 设备本地的 IP 地址                                                                   │
│   格式: candidate:1 1 UDP 2122260223 192.168.1.100 5000 typ host                           │
│                                                                                             │
│   ┌─────────────────┐                                                                       │
│   │   内网设备 A     │  Host: 192.168.1.100:5000                                            │
│   │ 192.168.1.100   │                                                                       │
│   └─────────────────┘                                                                       │
│                                                                                             │
│   适用场景: 同一局域网内的设备可以直接使用 Host Candidate 通信                              │
│                                                                                             │
└─────────────────────────────────────────────────────────────────────────────────────────────┘

2. Server Reflexive Candidate (srflx) - 通过 STUN 获取
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│                                                                                             │
│   特点: NAT 映射后的公网地址,通过 STUN 服务器获取                                           │
│   格式: candidate:2 1 UDP 1686052607 203.0.113.10 12345 typ srflx raddr 192.168.1.100 ...  │
│                                                                                             │
│   ┌─────────────────┐      ┌─────────────┐                                                 │
│   │   内网设备 A     │──────│  STUN 服务器  │                                                │
│   │ 192.168.1.100   │      │             │                                                 │
│   └─────────────────┘      └─────────────┘                                                 │
│          │                                                                                        │
│          │ NAT 映射                                                                               │
│          ▼                                                                                        │
│   ┌─────────────────┐                                                                       │
│   │   公网地址       │  srflx: 203.0.113.10:12345                                           │
│   │ 203.0.113.10    │                                                                       │
│   └─────────────────┘                                                                       │
│                                                                                             │
│   适用场景: 不同 NAT 后的设备尝试通过公网地址通信                                            │
│                                                                                             │
└─────────────────────────────────────────────────────────────────────────────────────────────┘

3. Peer Reflexive Candidate (prflx) - 动态发现
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│                                                                                             │
│   特点: 在 ICE 连接检查过程中动态发现的地址                                                  │
│   格式: candidate:3 1 UDP 1686052606 198.51.100.5 54321 typ prflx raddr 203.0.113.10 ...   │
│                                                                                             │
│   场景: 当 Symmetric NAT 导致 STUN 获取的地址与实际通信地址不同时发现                        │
│                                                                                             │
└─────────────────────────────────────────────────────────────────────────────────────────────┘

4. Relay Candidate (relay) - 通过 TURN 中继
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│                                                                                             │
│   特点: 通过 TURN 服务器中继的地址,优先级最低但成功率最高                                   │
│   格式: candidate:4 1 UDP 41885439 198.51.100.10 60000 typ relay raddr 198.51.100.10 ...   │
│                                                                                             │
│   ┌─────────────────┐      ┌─────────────┐      ┌─────────────────┐                        │
│   │   内网设备 A     │──────│ TURN 服务器  │──────│   内网设备 B     │                        │
│   │ 192.168.1.100   │      │ 198.51.100.10│      │ 192.168.2.200   │                        │
│   └─────────────────┘      └─────────────┘      └─────────────────┘                        │
│                                                                                             │
│   适用场景: P2P 连接失败时,通过 TURN 服务器中继所有流量                                    │
│                                                                                             │
└─────────────────────────────────────────────────────────────────────────────────────────────┘

5.2 ICE 候选者优先级

复制代码
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│                                    ICE 候选者优先级排序                                       │
└──────────────────────────────────────────────────────────────────────────────────────────────┘

优先级从高到低:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│                                                                                             │
│  1. Host Candidate (主机候选者)                                                             │
│     - 同一局域网内通信                                                                       │
│     - 延迟最低,带宽最大                                                                     │
│     - 不经过 NAT,最稳定                                                                     │
│                                                                                             │
│  2. Peer Reflexive Candidate (对等反射候选者)                                               │
│     - ICE 检查过程中动态发现                                                                │
│     - 通常比 srflx 更可靠                                                                   │
│                                                                                             │
│  3. Server Reflexive Candidate (服务器反射候选者)                                           │
│     - 通过 STUN 获取的公网地址                                                              │
│     - 依赖 NAT 类型                                                                         │
│                                                                                             │
│  4. Relay Candidate (中继候选者)                                                            │
│     - 通过 TURN 服务器中继                                                                  │
│     - 延迟最高,消耗服务器带宽                                                              │
│     - 但成功率最高,兼容所有 NAT 类型                                                       │
│                                                                                             │
└─────────────────────────────────────────────────────────────────────────────────────────────┘

ICE 连接建立流程:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│                                                                                             │
│  1. 收集所有类型的候选者                                                                    │
│  2. 按优先级排序                                                                            │
│  3. 成对检查 (Connectivity Check)                                                           │
│  4. 选择最高优先级的有效候选者对                                                            │
│  5. 如果所有候选者对都失败,使用 TURN 中继                                                  │
│                                                                                             │
└─────────────────────────────────────────────────────────────────────────────────────────────┘

6. STUN 与 TURN 对比

复制代码
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│                                    STUN 与 TURN 对比                                         │
└──────────────────────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│  特性                │ STUN                           │ TURN                               │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│  全称               │ Session Traversal              │ Traversal Using Relay              │
│                     │ Utilities for NAT              │ around NAT                         │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│  主要功能           │ 获取公网地址                   │ 中继媒体数据                       │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│  数据流向           │ 仅信令交互                     │ 所有媒体数据经过服务器             │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│  服务器负载         │ 低 (仅处理少量请求)            │ 高 (处理所有媒体流量)              │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│  带宽消耗           │ 几乎无                         │ 大量消耗服务器带宽                 │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│  NAT 穿透成功率     │ 约 86% (无法穿透 Symmetric NAT)│ 100% (所有 NAT 类型)               │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│  延迟               │ 低 (P2P 直连)                  │ 较高 (经过服务器中继)              │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│  需要认证           │ 否                             │ 是 (用户名/密码)                   │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│  部署成本           │ 低                             │ 高 (需要更多带宽和计算资源)        │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│  使用场景           │ 首选方案                       │ 备选方案 (STUN 失败时)             │
└─────────────────────────────────────────────────────────────────────────────────────────────┘

选择建议:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│                                                                                             │
│  1. 始终配置 STUN 服务器                                                                    │
│     - 成本低,能解决大部分 NAT 穿透问题                                                     │
│                                                                                             │
│  2. 配置 TURN 服务器作为备选                                                                │
│     - 确保 100% 连接成功率                                                                  │
│     - 对于企业级应用必不可少                                                                │
│                                                                                             │
│  3. 配置多个 STUN/TURN 服务器                                                               │
│     - 提高可用性                                                                            │
│     - 负载均衡                                                                              │
│                                                                                             │
└─────────────────────────────────────────────────────────────────────────────────────────────┘

7. 调试与测试

7.1 STUN 服务器测试工具

复制代码
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│                                    STUN 测试工具                                             │
└──────────────────────────────────────────────────────────────────────────────────────────────┘

1. 使用 stunclient 命令行工具:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│  # 安装 (Ubuntu/Debian)                                                                     │
│  sudo apt-get install stuntman-client                                                      │
│                                                                                             │
│  # 测试 STUN 服务器                                                                         │
│  stunclient stun.l.google.com 19302                                                        │
│                                                                                             │
│  # 输出示例:                                                                                │
│  Local address: 192.168.1.100:5000                                                         │
│  Mapped address: 203.0.113.10:12345                                                        │
└─────────────────────────────────────────────────────────────────────────────────────────────┘

2. 使用 turnutils_uclient 测试 TURN:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│  # 安装 coturn                                                                              │
│  sudo apt-get install coturn                                                               │
│                                                                                             │
│  # 测试 TURN 服务器                                                                         │
│  turnutils_uclient -u videocall -p turn123456 47.106.189.19 -p 3479                       │
└─────────────────────────────────────────────────────────────────────────────────────────────┘

3. 使用 Python 测试:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│  import socket                                                                              │
│  import struct                                                                              │
│                                                                                             │
│  def test_stun(stun_host, stun_port):                                                       │
│      # 创建 STUN Binding Request                                                            │
│      msg_type = 0x0001  # Binding Request                                                   │
│      msg_len = 0x0000                                                                       │
│      magic_cookie = 0x2112A442                                                              │
│      transaction_id = b'\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c'                  │
│                                                                                             │
│      request = struct.pack('>HHI12s', msg_type, msg_len, magic_cookie, transaction_id)      │
│                                                                                             │
│      # 发送请求                                                                             │
│      sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)                               │
│      sock.sendto(request, (stun_host, stun_port))                                          │
│                                                                                             │
│      # 接收响应                                                                             │
│      response, addr = sock.recvfrom(1024)                                                  │
│      print(f"Received response from {addr}")                                               │
│                                                                                             │
│      # 解析响应                                                                             │
│      resp_type = struct.unpack('>H', response[0:2])[0]                                     │
│      print(f"Response type: {hex(resp_type)}")                                             │
│                                                                                             │
│  test_stun('stun.l.google.com', 19302)                                                     │
└─────────────────────────────────────────────────────────────────────────────────────────────┘

7.2 WebRTC ICE 连接调试

复制代码
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│                                    WebRTC ICE 调试日志                                       │
└──────────────────────────────────────────────────────────────────────────────────────────────┘

Android 端日志示例:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│  // ICE 候选者收集日志                                                                      │
│  [webrtc][observer] 收到ICE候选: 0                                                         │
│  [webrtc] ICE Candidate: candidate:1 1 UDP 2122260223 192.168.1.100 5000 typ host          │
│  [webrtc] ICE Candidate: candidate:2 1 UDP 1686052607 203.0.113.10 12345 typ srflx         │
│                                                                                             │
│  // ICE 连接状态变化                                                                        │
│  [webrtc][observer] ICE连接状态变化: CHECKING                                              │
│  [webrtc][observer] ICE连接状态变化: CONNECTED                                             │
│  [webrtc][observer] ICE收集状态变化: COMPLETE                                              │
└─────────────────────────────────────────────────────────────────────────────────────────────┘

服务器端日志示例:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│  // STUN 服务器启动                                                                         │
│  STUN server started on 0.0.0.0:3478 (public: 47.106.189.19:3478)                          │
│                                                                                             │
│  // TURN 服务器启动                                                                         │
│  TURN server started on 0.0.0.0:3479 (public: 47.106.189.19:3479)                          │
└─────────────────────────────────────────────────────────────────────────────────────────────┘

7.3 常见问题排查

复制代码
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│                                    常见问题及解决方案                                        │
└──────────────────────────────────────────────────────────────────────────────────────────────┘

问题 1: ICE 连接一直处于 CHECKING 状态
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│  可能原因:                                                                                  │
│  - STUN/TURN 服务器不可达                                                                   │
│  - 防火墙阻止 UDP 流量                                                                      │
│  - NAT 类型不支持 P2P                                                                       │
│                                                                                             │
│  解决方案:                                                                                  │
│  1. 检查 STUN/TURN 服务器是否正常运行                                                       │
│  2. 确认防火墙允许 UDP 端口 3478/3479                                                       │
│  3. 配置 TURN 服务器作为中继                                                                │
└─────────────────────────────────────────────────────────────────────────────────────────────┘

问题 2: 只能获取 Host Candidate,无法获取 srflx
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│  可能原因:                                                                                  │
│  - STUN 服务器地址配置错误                                                                  │
│  - 网络无法访问 STUN 服务器                                                                 │
│                                                                                             │
│  解决方案:                                                                                  │
│  1. 检查 STUN 服务器配置是否正确                                                            │
│  2. 使用 stunclient 测试 STUN 服务器                                                        │
│  3. 尝试使用其他公共 STUN 服务器                                                            │
└─────────────────────────────────────────────────────────────────────────────────────────────┘

问题 3: ICE 连接频繁断开重连
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│  可能原因:                                                                                  │
│  - 网络不稳定                                                                               │
│  - NAT 映射超时                                                                             │
│                                                                                             │
│  解决方案:                                                                                  │
│  1. 启用 ICE Continual Gathering                                                            │
│  2. 增加心跳频率                                                                            │
│  3. 配置 TURN 服务器保持连接                                                                │
└─────────────────────────────────────────────────────────────────────────────────────────────┘

问题 4: TURN 认证失败
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│  可能原因:                                                                                  │
│  - 用户名或密码错误                                                                         │
│  - Realm 配置不匹配                                                                         │
│                                                                                             │
│  解决方案:                                                                                  │
│  1. 检查 TURN 用户名和密码配置                                                              │
│  2. 确认服务器端 Realm 配置                                                                 │
│  3. 使用 turnutils_uclient 测试认证                                                         │
└─────────────────────────────────────────────────────────────────────────────────────────────┘

8. 安全考虑

复制代码
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│                                    STUN/TURN 安全配置                                        │
└──────────────────────────────────────────────────────────────────────────────────────────────┘

1. TURN 认证配置:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│  // 服务器端配置                                                                            │
│  AuthHandler: func(username, 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                                                                      │
│  }                                                                                          │
│                                                                                             │
│  // 客户端配置                                                                              │
│  PeerConnection.IceServer.builder("turn:server:port")                                      │
│      .setUsername("videocall")                                                              │
│      .setPassword("turn123456")                                                             │
│      .createIceServer()                                                                     │
└─────────────────────────────────────────────────────────────────────────────────────────────┘

2. 网络安全:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│  - 使用 DTLS 加密 WebRTC 媒体流                                                             │
│  - 限制 TURN 服务器的带宽使用                                                               │
│  - 配置防火墙规则,只允许必要的端口                                                          │
│  - 定期更新 TURN 认证凭据                                                                   │
└─────────────────────────────────────────────────────────────────────────────────────────────┘

3. DDoS 防护:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│  - 限制单个 IP 的连接数                                                                     │
│  - 实现请求频率限制                                                                         │
│  - 监控异常流量模式                                                                         │
│  - 配置 IP 黑名单                                                                           │
└─────────────────────────────────────────────────────────────────────────────────────────────┘

9. 性能优化

复制代码
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│                                    STUN/TURN 性能优化                                        │
└──────────────────────────────────────────────────────────────────────────────────────────────┘

1. ICE 候选者收集优化:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│  // 配置持续收集策略                                                                        │
│  rtcConfig.continualGatheringPolicy = PeerConnection.ContinualGatheringPolicy.GATHER_CONTINUALLY
│                                                                                             │
│  // 启用 TCP 候选者 (作为 UDP 的备选)                                                       │
│  rtcConfig.tcpCandidatePolicy = PeerConnection.TcpCandidatePolicy.ENABLED                  │
└─────────────────────────────────────────────────────────────────────────────────────────────┘

2. 连接建立优化:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│  - 预先收集 ICE 候选者                                                                      │
│  - 使用 ICE Renomination 提高连接质量                                                       │
│  - 配置多个 STUN/TURN 服务器提高可用性                                                      │
└─────────────────────────────────────────────────────────────────────────────────────────────┘

3. 服务器部署优化:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│  - STUN/TURN 服务器部署在靠近用户的数据中心                                                 │
│  - 使用 Anycast IP 实现就近接入                                                            │
│  - 配置负载均衡分担流量                                                                    │
│  - 监控服务器性能指标                                                                      │
└─────────────────────────────────────────────────────────────────────────────────────────────┘

10. 参考资料

复制代码
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│                                    相关 RFC 文档                                             │
└──────────────────────────────────────────────────────────────────────────────────────────────┘

- RFC 5389: STUN - Session Traversal Utilities for NAT
- RFC 5766: TURN - Traversal Using Relays around NAT
- RFC 5245: ICE - Interactive Connectivity Establishment
- RFC 3489: STUN - Simple Traversal of User Datagram Protocol (已废弃,被 RFC 5389 取代)

开源项目:
- pion/turn: Go 语言实现的 STUN/TURN 服务器
- coturn: 功能完整的 TURN/STUN 服务器
- libnice: GLib 实现的 ICE 库
相关推荐
YYDataV数据可视化12 小时前
WebRTC ICE 候选类型详解:对等反射候选者(Peer Reflexive Candidate)
webrtc·实时音视频·ai编程
YYDataV数据可视化13 小时前
【音视频通话系统】架构详解
音视频·webrtc·实时音视频
Remember_9931 天前
一文吃透Java WebSocket:原理、实现与核心特性解析
java·开发语言·网络·websocket·网络协议·http·p2p
wenzhangli72 天前
OoderAgent AI 能力分发与自动化协作框架白皮书(V0.7.3 )
网络·去中心化·p2p
REDcker2 天前
RTP、RTCP 与 SRTP 协议详解
网络·音视频·webrtc·实时音视频·rtp·rtcp
熙熙他爹5 天前
webrtc中的线程
webrtc
hit_waves5 天前
程序人生-Hello’s P2P 哈尔滨工业大学计算机系统大作业
c语言·程序人生·课程设计·p2p·大作业
2401_876381925 天前
程序人生-Hello’s P2P
数据库·程序人生·p2p
wxgl_xyx5 天前
程序人生-Hello’s P2P(2025)
程序人生·职场和发展·p2p