跳至主要內容
Skip to content

WebRTC 實戰 (7) —— 信令機制的正式封裝與房間系統

上一篇使用 PeerJS 快速實現連線。本篇則回到自建信令路線:當應用需要大廳、房間列表、權限或多人狀態時,必須由應用伺服器管理房間成員與訊息路由。

這一篇我們將回歸伺服器端,探討如何設計一個具備 「房間 (Room)」 概念的正式信令架構。


一、 房間系統的邏輯抽象

在社交或會議軟體中,使用者通常加入房間,而不是手動輸入對方的 Peer ID。信令伺服器必須維護房間成員、驗證訊息接收者,並在連線中斷時清理狀態。


二、 設計擴展性的信令協定

1. 指令規範清單

  • join:使用者加入房間。
  • peer-joined:通知房間內其他使用者有新人加入。
  • offer / answer / candidate:沿用前幾篇的信令格式並轉送 payload
  • leave / peer-left:處理使用者主動離開或意外斷線。

三、 實作:房間制信令伺服器 (Node.js)

javascript
const WebSocket = require("ws");
const wss = new WebSocket.Server({ port: 8080 });

const rooms = new Map(); // roomId -> Set<userId>
const clients = new Map(); // userId -> WebSocket

function sendTo(userId, message) {
  const target = clients.get(userId);
  if (target?.readyState === WebSocket.OPEN) {
    target.send(JSON.stringify(message));
  }
}

function leaveRoom(ws) {
  const { roomId, userId } = ws;
  if (!roomId || !userId) return;

  const members = rooms.get(roomId);
  members?.delete(userId);
  clients.delete(userId);

  members?.forEach((memberId) => {
    sendTo(memberId, { type: "peer-left", from: userId });
  });

  if (members?.size === 0) rooms.delete(roomId);
}

wss.on('connection', (ws) => {
  ws.on('message', (msg) => {
    let message;

    try {
      message = JSON.parse(msg.toString());
    } catch {
      ws.send(JSON.stringify({ type: "error", payload: "Invalid JSON" }));
      return;
    }

    const { type, roomId, from, to, payload } = message;

    switch (type) {
      case 'join':
        if (!roomId || !from || clients.has(from)) return;

        ws.userId = from;
        ws.roomId = roomId;
        clients.set(from, ws);

        if (!rooms.has(roomId)) rooms.set(roomId, new Set());
        rooms.get(roomId).forEach((memberId) => {
          sendTo(memberId, { type: "peer-joined", from });
        });
        rooms.get(roomId).add(from);
        break;

      case 'offer':
      case 'answer':
      case 'candidate':
        if (from !== ws.userId) return;
        if (!rooms.get(ws.roomId)?.has(to)) return;
        sendTo(to, { type, from, to, payload });
        break;

      case 'leave':
        leaveRoom(ws);
        break;
    }
  });

  ws.on('close', () => leaveRoom(ws));
});

這個版本補上了接收者索引、同房驗證與斷線清理,但仍是教學骨架。正式服務還需要登入驗證、房間權限、訊息結構驗證、心跳、速率限制,以及多節點部署時的共享狀態或訊息代理。


總結

透過建立房間的概念,我們成功解耦了使用者 ID 與連線邏輯,並設計了一套標準的信令協定,為具備產品級水準的通訊軟體打下基礎。

下一章,我們將學習如何使用 WebRTC-Internals 工具剖析通訊中的數據細節。


進階挑戰

試著在你的信令伺服器增加「密碼房間」功能。當使用者發起 join 時,必須帶上正確的 password 參數,否則伺服器會回傳一個 JOIN_DENIED 的訊息。


延伸閱讀與資源

← 上一章:PeerJS 實戰 | 返回專題首頁 | 下一章:WebRTC-Internals 除錯監控