13 KiB
13 KiB
WebRTC 信令模式说明:私有模式 vs 公有模式
📊 核心对比
| 特性 | 私有模式 (private) | 公有模式 (public) |
|---|---|---|
| 连接关系 | 1对多 (Host-Participants) | 多对多 (广播) |
| 角色分配 | 有明确的 host 和 participant 角色 | 所有客户端平等,无角色区分 |
| 信令路由 | 定向转发 (host ↔ participants) | 全局广播 (除发送者外所有人) |
| 连接组管理 | 使用 connectionGroup 管理 |
使用 clients 全局列表 |
| 适用场景 | 视频会议、主控-从控 | 直播、公开房间 |
🔒 私有模式 (Private Mode)
工作原理
客户端A (Host) ←→ 服务器 ←→ 客户端B (Participant 1)
←→ 客户端C (Participant 2)
←→ 客户端D (Participant 3)
关键特性
1. 角色分配机制
- 第一个连接到服务器的客户端成为 Host
- 后续连接的客户端成为 Participants
- 通过
polite参数区分角色:host:polite = falseparticipant:polite = true
代码实现 (src/class/websockethandler.ts 第 150-176 行):
function onConnect(ws: WebSocket, connectionId: string): void {
let polite = true;
// 处理私有模式
if (isPrivate) {
if (connectionGroup.has(connectionId)) {
const group = connectionGroup.get(connectionId);
// 已有host,新连接作为participant加入
group.participants.add(ws);
console.log(`Participant joined connectionId: ${connectionId}, total participants: ${group.participants.size}`);
} else {
// 第一个连接成为host
connectionGroup.set(connectionId, { host: ws, participants: new Set<WebSocket>() });
polite = false;
console.log(`Host created connectionId: ${connectionId}`);
}
}
// 发送连接成功消息(包含角色信息)
const role = polite ? 'participant' : 'host';
ws.send(JSON.stringify({ type: "connect", connectionId: connectionId, polite: polite, role: role }));
}
2. 信令转发规则
- Host 发送 → 转发给所有 participants
- Participant 发送 → 只转发给 host
- Participants 之间不能直接通信
代码实现 (src/class/websockethandler.ts 第 96-108 行):
function broadcastToGroup(connectionId: string, senderWs: WebSocket, message: any): void {
const group = connectionGroup.get(connectionId);
if (!group) return;
// 如果发送者是host,转发给所有participants
if (senderWs === group.host) {
group.participants.forEach(participantWs => {
participantWs.send(JSON.stringify(message));
});
} else {
// 如果发送者是participant,转发给host
group.host.send(JSON.stringify(message));
}
}
3. Offer 信令处理
代码实现 (src/class/websockethandler.ts 第 220-243 行):
function onOffer(ws: WebSocket, message: any): void {
const connectionId = message.connectionId as string;
const newOffer = new Offer(message.sdp, Date.now(), false);
// 处理私有模式
if (isPrivate) {
if (connectionGroup.has(connectionId)) {
const group = connectionGroup.get(connectionId);
if (group.host === ws) {
// host发送offer,转发给所有participants
newOffer.polite = true;
group.participants.forEach(participantWs => {
participantWs.send(JSON.stringify({ from: connectionId, to: "", type: "offer", data: newOffer }));
});
} else {
// participant发送offer,转发给host
newOffer.polite = true;
group.host.send(JSON.stringify({ from: connectionId, to: "", type: "offer", data: newOffer }));
}
}
return;
}
// ... 公有模式处理
}
4. 断开连接处理
- Host 断开 → 通知所有 participants,删除整个连接组
- Participant 断开 → 只通知 host,从 participants 中移除
代码实现 (src/class/websockethandler.ts 第 114-142 行):
function remove(ws: WebSocket): void {
const connectionIds = clients.get(ws);
if (!connectionIds) return;
connectionIds.forEach(connectionId => {
const group = connectionGroup.get(connectionId);
if (group) {
if (group.host === ws) {
// host断开连接,通知所有participants
group.participants.forEach(participantWs => {
participantWs.send(JSON.stringify({ type: "disconnect", connectionId: connectionId }));
});
// 删除整个连接组
connectionGroup.delete(connectionId);
} else {
// participant断开连接,从participants中移除并通知host
group.participants.delete(ws);
group.host.send(JSON.stringify({ type: "disconnect", connectionId: connectionId }));
}
}
console.log(`Remove connectionId: ${connectionId}`);
});
clients.delete(ws);
}
数据结构
interface ConnectionGroup {
host: WebSocket; // 主机(第一个连接)
participants: Set<WebSocket>; // 参与者集合(后续连接)
}
const connectionGroup: Map<string, ConnectionGroup> = new Map<string, ConnectionGroup>();
应用场景
- ✅ 视频会议 (1个主播 + 多个观众)
- ✅ 远程桌面控制 (1个主控 + 多个观察者)
- ✅ 直播互动 (主播与观众连麦)
- ✅ 教学系统 (教师 + 多个学生)
- ✅ 主从控制 (Unity 应用为主机,多个 Web 客户端为参与者)
🌐 公有模式 (Public Mode)
工作原理
客户端A ←→ 服务器 ←→ 客户端B
↕ (广播) ↕
客户端C ←────────────→ 客户端D
关键特性
1. 无角色区分
- 所有客户端地位平等
- 没有 host/participant 的概念
polite始终为true
2. 全局广播机制
- 任何客户端发送的消息 → 广播给所有其他客户端
- 支持 多对多 通信
- 每个客户端既是发送者也是接收者
代码实现 (src/class/websockethandler.ts 第 245-256 行):
function onOffer(ws: WebSocket, message: any): void {
const connectionId = message.connectionId as string;
const newOffer = new Offer(message.sdp, Date.now(), false);
// ... 私有模式处理
// 公共模式:创建新的连接组(如果不存在)
if (!connectionGroup.has(connectionId)) {
connectionGroup.set(connectionId, { host: ws, participants: new Set<WebSocket>() });
}
// 向所有其他客户端广播offer
clients.forEach((_v, k) => {
if (k == ws) {
return; // 跳过发送者
}
k.send(JSON.stringify({ from: connectionId, to: "", type: "offer", data: newOffer }));
});
}
3. 对等通信
消息流转示例:
A 发送消息 → B、C、D 都收到
B 发送消息 → A、C、D 都收到
C 发送消息 → A、B、D 都收到
D 发送消息 → A、B、C 都收到
应用场景
- ✅ 多人聊天室 (所有用户平等)
- ✅ 公开游戏房间 (多玩家对战)
- ✅ 协作编辑 (多人实时编辑)
- ✅ 广播直播 (单向推流,多人观看)
- ✅ P2P 网状网络 (Full Mesh 拓扑)
💻 启动方式
私有模式
# 使用 npm 脚本
npm start -- -m private
# 或直接运行
node ./build/index.js -s -p 8080 -m private -k ./server.key -c ./server.cert
# 开发模式
npm run dev -- -m private
公有模式
# 使用 npm 脚本(默认模式)
npm start
# 或明确指定
npm start -- -m public
# 或直接运行
node ./build/index.js -s -p 8080 -m public -k ./server.key -c ./server.cert
参数说明
| 参数 | 说明 | 示例 |
|---|---|---|
-m |
通信模式 | -m private 或 -m public |
-p |
端口号 | -p 8080 |
-s |
启用 HTTPS | -s |
-k |
密钥文件 | -k ./server.key |
-c |
证书文件 | -c ./server.cert |
-t |
信令类型 | -t websocket 或 -t http |
📝 实际例子
私有模式示例
假设有 3 个客户端连接到同一个 connectionId: "room1":
连接顺序:
- 客户端A 先连接 → 成为 Host (
polite: false) - 客户端B 连接 → 成为 Participant 1 (
polite: true) - 客户端C 连接 → 成为 Participant 2 (
polite: true)
消息流转:
A (Host) 发送消息
→ B 收到
→ C 收到
B (Participant) 发送消息
→ A 收到
→ C 收不到 ❌
C (Participant) 发送消息
→ A 收到
→ B 收不到 ❌
信令流程图:
B --[offer]--> 服务器 --[offer]--> A (Host)
A --[answer]--> 服务器 --[answer]--> B
A --[offer]--> 服务器 --[offer]--> C
C --[answer]--> 服务器 --[answer]--> A
公有模式示例
假设有 3 个客户端连接:
连接顺序:
- 客户端A 连接 (
polite: true) - 客户端B 连接 (
polite: true) - 客户端C 连接 (
polite: true)
消息流转:
A 发送消息
→ B 收到 ✅
→ C 收到 ✅
B 发送消息
→ A 收到 ✅
→ C 收到 ✅
C 发送消息
→ A 收到 ✅
→ B 收到 ✅
信令流程图:
A --[offer]--> 服务器 --[offer]--> B
A --[offer]--> 服务器 --[offer]--> C
B --[offer]--> 服务器 --[offer]--> A
B --[offer]--> 服务器 --[offer]--> C
C --[offer]--> 服务器 --[offer]--> A
C --[offer]--> 服务器 --[offer]--> B
🔍 代码中的模式判断
初始化模式
src/class/websockethandler.ts 第 62-65 行:
function reset(mode: string): void {
// 设置是否为私有模式
isPrivate = mode == "private";
}
模式判断逻辑
在各个信令处理函数中,通过 isPrivate 变量判断:
// Offer 处理
if (isPrivate) {
// 私有模式逻辑:定向转发
} else {
// 公有模式逻辑:全局广播
}
// Candidate 处理
if (isPrivate) {
// 私有模式逻辑
return;
}
// Message 处理
if (connectionGroup.has(connectionId)) {
const group = connectionGroup.get(connectionId);
if (group.host === ws) {
// host 发送 → 转发给 participants
} else {
// participant 发送 → 转发给 host
}
}
🎯 模式选择建议
选择私有模式的场景
- ✅ 需要明确的主从关系
- ✅ 中心节点需要控制所有通信
- ✅ 参与者之间不需要直接通信
- ✅ 需要管理连接层级结构
- ✅ 资源优化(减少不必要的 P2P 连接)
选择公有模式的场景
- ✅ 所有参与者地位平等
- ✅ 需要多对多通信
- ✅ Full Mesh P2P 拓扑
- ✅ 每个客户端都需要相互连接
- ✅ 去中心化的应用场景
📊 性能对比
| 指标 | 私有模式 | 公有模式 |
|---|---|---|
| 连接数 | O(n) - 线性增长 | O(n²) - 平方增长 |
| 服务器负载 | 中等(需要路由转发) | 较低(主要是广播) |
| 客户端负载 | 较低(只与 host 建立连接) | 较高(需要与所有客户端建立连接) |
| 网络带宽 | 节省(定向传输) | 较高(广播传输) |
| 扩展性 | 好(适合大量 participants) | 受限(连接数随用户数平方增长) |
🔧 测试方式
单元测试
项目中已包含完整的单元测试:
后端测试 (test/websockethandler.test.ts):
// 公有模式测试
describe('websocket signaling test in public mode', () => {
beforeAll(async () => {
wsHandler.reset("public");
// ...
});
});
// 私有模式测试
describe('websocket signaling test in private mode', () => {
beforeAll(async () => {
wsHandler.reset("private");
// ...
});
});
前端测试 (client/test/signaling.test.js):
// 公有模式测试
describe.each([
{ mode: "mock" },
{ mode: "http" },
{ mode: "websocket" },
])('signaling test in public mode', ({ mode }) => {
// ...
});
// 私有模式测试
describe.each([
{ mode: "mock" },
{ mode: "http" },
{ mode: "websocket" },
])('signaling test in private mode', ({ mode }) => {
// ...
});
运行测试
# 运行后端测试
npm test
# 运行前端测试
cd client
npm test
📚 相关文件
- WebSocket 处理器:
src/class/websockethandler.ts - HTTP 处理器:
src/class/httphandler.ts - WebSocket 服务:
src/websocket.ts - 前端信令:
client/src/signaling.js - 主入口:
src/index.ts - 后端测试:
test/websockethandler.test.ts - 前端测试:
client/test/signaling.test.js
💡 总结
| 模式 | 核心特点 | 最佳用途 |
|---|---|---|
| 私有模式 | 1对多、主从架构、定向转发 | 视频会议、远程控制、教学系统 |
| 公有模式 | 多对多、对等网络、全局广播 | 聊天室、多人游戏、协作编辑 |
选择合适的模式取决于你的应用架构需求和通信拓扑结构。
文档生成时间: 2026-04-22
项目版本: 3.1.0