信令机制与房间概念

信令是 WebRTC 中交换会话控制信息的机制,用于建立连接前的协商。房间是多人通信中组织用户的逻辑概念。本章将详细介绍信令的工作原理、SDP 的组成结构以及房间管理。

1. 信令概述

1.1 为什么需要信令

WebRTC 本身只处理媒体传输,不定义信令协议。这是因为信令涉及:

  • 会话协商:交换双方支持的媒体能力(编码格式、分辨率等)
  • 网络信息:交换网络可达地址(ICE Candidate)
  • 控制消息:加入/离开房间、静音通知、状态同步

信令的核心目的是让两个或多个端点在建立 P2P 连接之前,能够互相了解对方的能力和网络位置。

sequenceDiagram
    participant A as 用户A
    participant S as 信令服务器
    participant B as 用户B

    A->>S: 加入房间
    B->>S: 加入房间
    S->>A: 通知:B加入
    S->>B: 通知:A已在线

    A->>S: Offer SDP
    S->>B: 转发 Offer
    B->>S: Answer SDP
    S->>A: 转发 Answer

    A->>S: ICE Candidate
    S->>B: 转发 ICE
    B->>S: ICE Candidate
    S->>A: 转发 ICE

    Note over A,B: P2P连接建立

1.2 信令与媒体的关系

信令通道和媒体通道是分离的:

flowchart TB
    subgraph 信令通道
        A[WebSocket/HTTP]
    end

    subgraph 媒体通道
        B[UDP/RTP]
    end

    A --> |交换能力信息| C[连接协商]
    C --> D[建立 P2P 连接]
    D --> B
    B --> |直接传输| E[音视频数据]

为什么信令与媒体分离?

  1. 灵活性:可以使用任何信令协议,不影响媒体传输
  2. 兼容性:便于与传统通信系统(如 SIP)集成
  3. 安全性:信令走服务器,媒体走 P2P,减少隐私风险
  4. 扩展性:信令服务器可以独立扩展

1.3 信令的时序要求

信令消息的时序对连接建立至关重要:

阶段 消息 时序要求
加入 join 无严格要求
协商 Offer/Answer Answer 必须在 Offer 之后
连接 ICE Candidate 必须在 SDP 交换后,但可 Trickle
控制 自定义 根据业务需求

2. SDP(Session Description Protocol)

2.1 SDP 是什么

SDP 是描述多媒体会话的文本协议,由 RFC 4566 定义。在 WebRTC 中,SDP 用于:

  • 描述支持的媒体类型(音频、视频)
  • 列出支持的编码格式
  • 指定传输地址和端口
  • 协商媒体能力

2.2 SDP 整体结构

SDP 由多个行组成,每行格式为:

<类型>=<值>

类型是单个字符,值的结构取决于类型。SDP 必须按特定顺序出现:

flowchart TB
    A[会话级描述] --> B[v= 协议版本]
    B --> C[o= 会话起源]
    C --> D[s= 会话名称]
    D --> E[t= 时间描述]
    E --> F[其他会话属性]

    F --> G[媒体级描述 1]
    G --> H[m= 媒体行]
    H --> I[c= 连接信息]
    I --> J[a= 属性行]

    J --> K[媒体级描述 2]
    K --> L[...]

2.3 会话级描述

协议版本(v=)

v=0

SDP 版本号,目前只有版本 0。

会话起源(o=)

o=<username> <sess-id> <sess-version> <nettype> <addrtype> <unicast-address>
字段 说明 示例
username 用户名 - 或用户 ID
sess-id 会话 ID 随机数
sess-version 会话版本 递增数字
nettype 网络类型 IN (Internet)
addrtype 地址类型 IP4 / IP6
unicast-address 地址 127.0.0.1

示例:

o=- 4611731400430057753 2 IN IP4 127.0.0.1

会话名称(s=)

s=<session name>

会话的显示名称,WebRTC 中通常为空或 “ - “。

时间描述(t=)

t=<start-time> <stop-time>

使用 NTP 时间戳,0 0 表示永久会话。

连接信息(c=)

c=<nettype> <addrtype> <connection-address>

在 WebRTC 中通常为:

c=IN IP4 0.0.0.0

实际地址通过 ICE Candidate 协商。

2.4 媒体级描述(m=)

媒体行是最重要的部分,描述一个媒体流:

m=<media> <port> <proto> <fmt> ...
字段 说明 示例
media 媒体类型 audio / video / application
port 端口 9(使用 ICE 时)
proto 传输协议 UDP/TLS/RTP/SAVPF
fmt 格式列表 Payload Type 列表

示例:

m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100

这表示视频媒体,支持 Payload Type 96-100 的编码格式。

2.5 属性行(a=)

属性是 SDP 中最丰富的部分,用于描述各种能力:

rtpmap:编码格式映射

a=rtpmap:<payload-type> <encoding-name>/<clock-rate>[/<encoding-params>]

示例:

a=rtpmap:96 VP8/90000
a=rtpmap:111 opus/48000/2

fmtp:格式参数

a=fmtp:<payload-type> <format-specific-params>

示例:

a=fmtp:96 profile-level-id=42e01f;level-asymmetry-allowed=1
a=fmtp:111 minptime=10;useinbandfec=1

常用属性列表

属性 说明 示例
a=recvonly 只接收 单向接收
a=sendonly 只发送 单向发送
a=sendrecv 收发双向 默认值
a=inactive 不活跃 暂停
a=rtcp-mux RTP/RTCP 复用 同一端口
a=rtcp-rsize 减少 RTCP 带宽优化
a=candidate ICE 候选 网络地址
a=ssrc 同步源标识 媒体源
a=mid 媒体标识 对应 BUNDLE
a=msid 媒体流标识 关联 MediaStream

2.6 WebRTC 扩展属性

WebRTC 在标准 SDP 基础上定义了扩展属性:

BUNDLE(多路复用)

将多个媒体流复用到同一个传输通道:

a=group:BUNDLE 0 1
a=mid:0
a=mid:1

BUNDLE 的意义

  • 减少端口占用
  • 简化 NAT 穿透
  • 节省资源

RTCP Feedback

声明支持的 RTCP 反馈机制:

a=rtcp-fb:<pt> <feedback-type> [<feedback-parameter>]

常用类型:
| 反馈类型 | 说明 |
|———-|——|
| nack | 负确认(重传) |
| nack pli | 请求关键帧 |
| ccm fir | 完整帧请求 |
| goog-remb | 接收端带宽估计 |

SSRC 相关

a=ssrc:<ssrc-id> cname:<cname>
a=ssrc:<ssrc-id> msid:<stream-id> <track-id>

SSRC(Synchronization Source)用于标识媒体源,确保同一源的包可以正确同步。

2.7 SDP 协商流程

sequenceDiagram
    participant A as 呼叫方
    participant B as 被呼叫方

    Note over A: 创建 Offer
    A->>A: 生成 SDP Offer
    Note over A: 包含支持的所有<br>编码格式、参数
    A->>B: 发送 Offer

    B->>B: 解析 Offer
    Note over B: 选择支持的编码<br>确定参数
    B->>B: 创建 Answer
    Note over B: Answer 只包含<br>选定的编码格式
    B->>A: 发送 Answer

    A->>A: 应用 Answer
    Note over A,B: 协商完成<br>开始传输媒体

2.8 SDP 示例解读

一个完整的 WebRTC SDP Offer 示例结构:

v=0
o=- 4611731400430057753 2 IN IP4 127.0.0.1
s=-
t=0 0
a=msid-semantic: WMS

m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:abcd
a=ice-pwd:abcdefghijklmnopqrstuvwx
a=fingerprint:sha-256 ABCD...
a=setup:actpass
a=mid:0
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=recvonly
a=rtcp-mux
a=rtpmap:111 opus/48000/2
a=fmtp:111 minptime=10;useinbandfec=1
...

m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:abcd
a=ice-pwd:abcdefghijklmnopqrstuvwx
a=fingerprint:sha-256 ABCD...
a=setup:actpass
a=mid:1
a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
a=recvonly
a=rtcp-mux
a=rtpmap:96 VP8/90000
a=rtcp-fb:96 goog-remb
a=rtcp-fb:96 ccm fir
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
...

解读要点

  1. 两个 m= 行:音频和视频各一个媒体描述
  2. Payload Type 列表:111, 103… 表示支持的编码优先级
  3. a=recvonly:表示只接收(查看远端)
  4. a=rtcp-fb:声明支持的反馈机制
  5. ICE 凭证:ice-ufrag 和 ice-pwd 用于 ICE 验证
  6. DTLS 指纹:fingerprint 用于加密验证

3. ICE Candidate

3.1 Candidate 的作用

ICE Candidate 描述了一个可能的连接地址:

a=candidate:<foundation> <component-id> <transport> <priority> <connection-address> <port> <candidate-attributes>

3.2 Candidate 类型

类型 缩写 来源 优先级
Host host 本地网卡 最高
Server Reflexive srflx STUN 服务器
Peer Reflexive prflx 对端观察到的
Relay relay TURN 服务器 最低

3.3 Candidate 示例

a=candidate:1 1 UDP 2122260223 192.168.1.100 54321 typ host
a=candidate:2 1 UDP 1686052607 203.0.113.1 12345 typ srflx raddr 192.168.1.100 rport 54321
a=candidate:3 1 UDP 922175487 198.51.100.1 50000 typ relay raddr 203.0.113.1 rport 12345

解读

  • 第一个:本地地址,优先级最高
  • 第二个:通过 STUN 获得的公网地址
  • 第三个:TURN 中继地址,优先级最低

3.4 Trickle ICE

传统方式是收集完所有 Candidate 后一次性发送,Trickle ICE 则是收集到一个就发送一个:

flowchart LR
    subgraph 传统方式
        A1[收集所有] --> B1[一次性发送]
    end

    subgraph Trickle ICE
        A2[收集一个] --> B2[立即发送]
        A2 --> C2[收集一个]
        C2 --> D2[立即发送]
    end

Trickle ICE 的优势

  • 更快的连接建立
  • 可以提前尝试连接
  • 减少首帧延迟

4. 信令协议选择

4.1 协议对比

协议 优点 缺点 适用场景
WebSocket 实时双向、简单 需维护连接 大多数 RTC 应用
HTTP/SSE 简单、兼容性好 实时性差 低频信令
SIP 标准化、互通性好 复杂 传统电信集成
MQTT 轻量、支持推送 需额外组件 IoT 场景
gRPC 高效、类型安全 浏览器支持弱 服务间通信

4.2 WebSocket 信令流程

WebSocket 是最常用的信令传输方式:

sequenceDiagram
    participant C as 客户端
    participant S as WebSocket 服务器

    C->>S: 建立连接
    S->>C: 连接确认

    loop 消息交换
        C->>S: JSON 消息
        S->>C: JSON 响应/转发
    end

    Note over C,S: 断开时自动重连

4.3 消息格式设计

信令消息通常使用 JSON 格式,包含以下字段:

字段 类型 说明
type string 消息类型
roomId string 房间标识
userId string 用户标识
targetId string 目标用户(点对点)
payload object 消息内容

常用消息类型:

类型 方向 说明
join 客户端→服务器 加入房间
leave 客户端→服务器 离开房间
offer 客户端→服务器→客户端 SDP Offer
answer 客户端→服务器→客户端 SDP Answer
ice-candidate 客户端→服务器→客户端 ICE 候选
user-joined 服务器→客户端 用户加入通知
user-left 服务器→客户端 用户离开通知

5. 房间概念

5.1 房间的作用

房间是多人通信的逻辑分组单元:

flowchart TB
    subgraph 房间A
        A1[用户1]
        A2[用户2]
        A3[用户3]
    end

    subgraph 房间B
        B1[用户4]
        B2[用户5]
    end

    A1 -.-> |P2P| A2
    A1 -.-> |P2P| A3
    A2 -.-> |P2P| A3

    B1 -.-> |P2P| B2

    Note1[房间A内用户互相连接]
    Note2[房间B内用户互相连接]

5.2 房间管理功能

功能 说明
创建房间 生成唯一房间 ID,设置房间属性
加入房间 用户进入并通知其他用户
离开房间 用户退出并通知其他用户
房间状态 维护在线用户列表、媒体状态
房间销毁 所有用户离开后清理资源

5.3 网状拓扑 vs MCU vs SFU

拓扑 描述 优点 缺点
Mesh(网状) 每两人之间建立 P2P 简单、无服务器成本 人数受限(≤4人)
MCU 服务器混合所有流 客户端简单 服务器压力大、延迟高
SFU 服务器转发原始流 平衡性能和复杂度 需要服务器
flowchart TB
    subgraph Mesh
        M1[用户A] <--> M2[用户B]
        M1 <--> M3[用户C]
        M2 <--> M3
    end

    subgraph SFU
        S1[用户A] --> SFU1[SFU服务器]
        S2[用户B] --> SFU1
        S3[用户C] --> SFU1
        SFU1 --> S1
        SFU1 --> S2
        SFU1 --> S3
    end

5.4 房间状态同步

房间内需要同步的状态:

状态类型 说明
用户列表 在线用户及其角色
媒体状态 音视频开启/关闭
网络状态 连接质量、延迟
自定义状态 举手、发言权等

6. 完整信令流程

6.1 建立连接流程

sequenceDiagram
    participant A as 用户A
    participant S as 信令服务器
    participant B as 用户B

    Note over A,B: 1. 加入房间阶段
    A->>S: join(roomId, userIdA)
    S->>A: join-ack(users: [])
    B->>S: join(roomId, userIdB)
    S->>B: join-ack(users: [A])
    S->>A: user-joined(userIdB)

    Note over A,B: 2. SDP 协商阶段
    A->>A: createOffer()
    A->>A: setLocalDescription()
    A->>S: offer(targetId: B, sdp)
    S->>B: offer(fromId: A, sdp)
    B->>B: setRemoteDescription()
    B->>B: createAnswer()
    B->>B: setLocalDescription()
    B->>S: answer(targetId: A, sdp)
    S->>A: answer(fromId: B, sdp)
    A->>A: setRemoteDescription()

    Note over A,B: 3. ICE 收集阶段(Trickle)
    A->>S: ice-candidate(targetId: B, candidate)
    S->>B: ice-candidate(fromId: A, candidate)
    B->>S: ice-candidate(targetId: A, candidate)
    S->>A: ice-candidate(fromId: B, candidate)

    Note over A,B: 4. 连接建立
    A->>B: P2P 媒体传输

6.2 重连机制

网络断开后的重连策略:

flowchart TB
    A[连接断开] --> B{WebSocket 可用?}
    B -->|是| C[重连信令服务器]
    B -->|否| D[等待网络恢复]
    D --> C

    C --> E{重连成功?}
    E -->|是| F[重新加入房间]
    E -->|否| G[指数退避重试]

    F --> H[同步房间状态]
    H --> I{P2P 连接可用?}
    I -->|是| J[恢复媒体传输]
    I -->|否| K[ICE Restart]
    K --> J

6.3 状态同步策略

重连后需要同步的状态:

状态 同步方式 优先级
用户列表 服务器推送
媒体状态 重新协商
聊天记录 可选同步
自定义状态 业务决定

7. 信令服务器架构

7.1 单服务器架构

flowchart TB
    subgraph 客户端
        A[用户A]
        B[用户B]
        C[用户C]
    end

    subgraph 服务器
        D[WebSocket Server]
        E[房间管理器]
        F[用户状态]
    end

    A --> D
    B --> D
    C --> D
    D --> E
    E --> F

适用场景:小规模应用(<1000 并发)

7.2 分布式架构

flowchart TB
    subgraph 客户端
        A[用户A]
        B[用户B]
    end

    subgraph 接入层
        C[WS Server 1]
        D[WS Server 2]
    end

    subgraph 协调层
        E[Redis Pub/Sub]
        F[消息队列]
    end

    subgraph 存储层
        G[Redis]
        H[数据库]
    end

    A --> C
    B --> D
    C --> E
    D --> E
    C --> G
    D --> G
    E --> F

跨服务器通信

  • 使用 Redis Pub/Sub 广播房间消息
  • 使用消息队列处理异步任务
  • 使用 Redis 存储房间状态

7.3 高可用设计

组件 高可用方案
WebSocket 服务器 负载均衡 + 多副本
Redis 主从复制 + Sentinel
消息队列 集群模式

8. 安全考虑

8.1 身份验证

信令连接建立时的验证流程:

sequenceDiagram
    participant C as 客户端
    participant S as 信令服务器
    participant A as 认证服务

    C->>S: 连接请求 + Token
    S->>A: 验证 Token
    A->>S: 验证结果
    alt 验证成功
        S->>C: 连接接受
    else 验证失败
        S->>C: 连接拒绝
    end

8.2 房间权限控制

权限类型 说明
加入权限 谁可以加入房间
发言权限 谁可以发送音视频
管理权限 踢人、禁言等
录制权限 是否允许录制

8.3 消息安全

威胁 防护措施
窃听 TLS 加密传输
篡改 DTLS 验证
重放 时间戳 + Nonce
伪造 身份验证

9. 性能优化

9.1 消息优化

优化点 方法 效果
消息压缩 gzip / binary 减少 50-70% 流量
批量发送 合并多个 ICE Candidate 减少消息数
增量更新 只发送变化部分 减少数据量

9.2 连接优化

优化点 方法
心跳保活 定期 ping/pong
断线重连 指数退避算法
负载均衡 一致性哈希

9.3 服务器性能指标

指标 说明 目标值
消息延迟 消息从发送到接收 < 50ms
并发连接 同时在线连接数 根据业务
消息吞吐 每秒处理消息数 根据业务
CPU 使用率 服务器负载 < 70%

10. 最佳实践

10.1 设计清单

  • 选择合适的信令协议(推荐 WebSocket)
  • 设计清晰的消息类型和格式
  • 实现完整的房间管理功能
  • 处理断线重连和状态同步
  • 添加身份验证和权限控制
  • 考虑分布式扩展方案
  • 实现监控和日志记录

10.2 常见问题

问题 原因 解决方案
SDP 协商失败 编码格式不匹配 检查支持的编码列表
ICE 连接失败 NAT 穿透问题 检查 STUN/TURN 配置
频繁断线重连 网络不稳定 优化重连策略
消息延迟高 服务器负载高 扩容或优化

11. 总结

组件 职责 关键点
SDP 描述媒体能力 会话级 + 媒体级描述
ICE Candidate 描述网络地址 Host > Srflx > Relay
信令服务器 消息路由、房间管理 WebSocket + JSON
房间管理 用户分组、状态维护 创建/加入/离开/销毁

关键点:

  1. SDP 结构:会话级描述 + 媒体级描述,属性行定义能力
  2. ICE 协商:优先使用 Host,其次 Srflx,最后 Relay
  3. Trickle ICE:收集一个发送一个,加快连接建立
  4. 房间管理:维护用户列表,广播用户加入/离开
  5. 重连机制:断线重连 + 状态同步 + ICE Restart
  6. 安全考虑:身份验证、权限控制、加密传输

下一章将详细介绍 ICE、STUN/TURN 等协议的工作原理。