Files
video_socket-server/私有模式和公有模式区别.md
2026-04-29 15:18:30 +08:00

515 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 = false`
- `participant`: `polite = true`
**代码实现** (`src/class/websockethandler.ts` 第 150-176 行):
```typescript
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 行):
```typescript
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 行):
```typescript
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 行):
```typescript
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);
}
```
### 数据结构
```typescript
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 行):
```typescript
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 拓扑)
---
## 💻 启动方式
### 私有模式
```bash
# 使用 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
```
### 公有模式
```bash
# 使用 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"`:
**连接顺序**:
1. **客户端A** 先连接 → 成为 **Host** (`polite: false`)
2. **客户端B** 连接 → 成为 **Participant 1** (`polite: true`)
3. **客户端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 个客户端连接:
**连接顺序**:
1. **客户端A** 连接 (`polite: true`)
2. **客户端B** 连接 (`polite: true`)
3. **客户端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 行**:
```typescript
function reset(mode: string): void {
// 设置是否为私有模式
isPrivate = mode == "private";
}
```
### 模式判断逻辑
在各个信令处理函数中,通过 `isPrivate` 变量判断:
```typescript
// 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`):
```typescript
// 公有模式测试
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`):
```javascript
// 公有模式测试
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 }) => {
// ...
});
```
### 运行测试
```bash
# 运行后端测试
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