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

13 KiB
Raw Blame History

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 行):

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":

连接顺序:

  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 行:

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