信令机制与房间概念
信令机制与房间概念
信令是 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[音视频数据]
为什么信令与媒体分离?
- 灵活性:可以使用任何信令协议,不影响媒体传输
- 兼容性:便于与传统通信系统(如 SIP)集成
- 安全性:信令走服务器,媒体走 P2P,减少隐私风险
- 扩展性:信令服务器可以独立扩展
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
...
解读要点:
- 两个 m= 行:音频和视频各一个媒体描述
- Payload Type 列表:111, 103… 表示支持的编码优先级
- a=recvonly:表示只接收(查看远端)
- a=rtcp-fb:声明支持的反馈机制
- ICE 凭证:ice-ufrag 和 ice-pwd 用于 ICE 验证
- 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 |
| 房间管理 | 用户分组、状态维护 | 创建/加入/离开/销毁 |
关键点:
- SDP 结构:会话级描述 + 媒体级描述,属性行定义能力
- ICE 协商:优先使用 Host,其次 Srflx,最后 Relay
- Trickle ICE:收集一个发送一个,加快连接建立
- 房间管理:维护用户列表,广播用户加入/离开
- 重连机制:断线重连 + 状态同步 + ICE Restart
- 安全考虑:身份验证、权限控制、加密传输
下一章将详细介绍 ICE、STUN/TURN 等协议的工作原理。
