從建立連線到訊息推送:WebSocket 實作即時通訊功能教學與程式碼解析

用 WebSocket 實作即時通訊,從連線鑑權、訊息幀設計到伺服器端推送與斷線恢復,附可執行程式碼,並與現成 IM 軟體功能對比,幫你判斷自建是否划算。

從建立連線到訊息推送:WebSocket 實作即時通訊功能教學與程式碼解析

用 WebSocket 做即時通訊,demo 很容易跑通:瀏覽器連線伺服器端,雙向收發訊息。但上線後問題集中在三處:token 鑑權不嚴謹導致連線被濫用;斷線後離線訊息遺失;多端在線時訊息順序不一致。本文按連線建立、訊息幀設計、伺服器端推送、斷線恢復的順序提供可執行程式碼,並在每個環節標注與現成 IM 軟體的差距,幫你在動手前判斷自建是否划算。

建立 WebSocket 連線:用戶端與伺服器端的握手與鑑權

用戶端程式碼:

const socket = new WebSocket(
  `wss://example.com/ws?token=${encodeURIComponent(token)}`
);

socket.onopen = () => console.log('connected');
socket.onerror = (e) => console.error('ws error', e);

伺服器端用 Node.js 的 ws 程式庫處理升級:

const { WebSocketServer } = require('ws');
const wss = new WebSocketServer({ noServer: true });

server.on('upgrade', (req, socket, head) => {
  const url = new URL(req.url, 'http://localhost');
  const token = url.searchParams.get('token');
  if (!verifyToken(token)) {
    socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
    socket.destroy();
    return;
  }
  wss.handleUpgrade(req, socket, head, (ws) => {
    wss.emit('connection', ws, req);
  });
});

不要把 token 塞進 Sec-WebSocket-Protocol,部分代理和用戶端會丟棄或截斷。正式環境可在查詢參數中只放一次性 ticket,伺服器端校驗後綁定使用者身分並下發短時 session id;連線建立後用戶端再透過首個訊息攜帶簽名完成二次校驗。現成 IM SDK 通常已封裝握手、鑑權和重連,開發者不需要處理這些邊界。

定義統一訊息訊框:上行與下行結構

所有訊息使用同一 JSON 結構,seq" 由用戶端生成並單調遞增,用於確認與排序。"

{
  "type": "chat",
  "seq": 1024,
  "to": "user_42",
  "payload": { "text": "hello" }
}
{
  "type": "chat",
  "seq": 1024,
  "from": "user_7",
  "to": "user_42",
  "ts": 1710000000000,
  "payload": { "text": "hello" }
}

"伺服器端返回 ack 時攜帶相同 "seq";用戶端在收到 ack 前保留副本用於重試。群聊把 "to" 換成 "room_id",伺服器端負責展開接收人。不要依賴 WebSocket 的單一連線順序:同一使用者多端上線時,必須由伺服器端按 "seq" 排序後推送,否則多端可能顯示順序不一致。"

伺服器端推送:從單一連線到房間/使用者定向

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

function addClient(userId, ws) {
  if (!clients.has(userId)) clients.set(userId, new Set());
  clients.get(userId).add(ws);
  ws.on('close', () => {
    const set = clients.get(userId);
    if (!set) return;
    set.delete(ws);
    if (set.size === 0) clients.delete(userId);
  });
}

function sendToUser(userId, message) {
  const sockets = clients.get(userId);
  if (!sockets) return false;
  for (const ws of sockets) {
    if (ws.readyState === 1) {
      ws.send(JSON.stringify(message));
    }
  }
  return true;
}

"連線註冊表必須處理多端上線和異常斷線,否則 "Set" 會無限增長。單聊同時寫入傳送方與接收方;群聊先查詢房間成員再廣播。傳送時若連線非 OPEN,應把訊息寫入離線佇列,不能直接丟棄。伺服器端推送延遲升高時,按網路、伺服器端、用戶端分層的定位方法見"即時通訊延遲高怎麼排解?從網路、伺服器端到用戶端的分層定位步驟。

斷線重連與心跳:訊息不丟的邊界

  1. 用戶端每 30 秒傳送 {"type":"heartbeat"};伺服器端 35 秒內未收到任何幀,判定連線失效並關閉。
  2. 用戶端 onclose 後按 1s、2s、4s、8s、封頂 30s 退避重連;重連成功後先拉取本機最後 seq 之後的訊息。
  3. 伺服器端不為長時間離線使用者補發訊息,除非你在伺服器端持久化訊息並維護游標;若要求離線必達,必須落盤、補拉、冪等去重。

WebSocket 只解決雙向通道,不提供訊息必達、離線儲存或多端同步。這些能力的具體實作可參考保障訊息必達的關鍵機制:即時通訊訊息不丟失怎麼實作與離線訊息總丟失?即時通訊離線訊息收不到的常見原因與修復。

自建 WebSocket 與現成 IM 軟體功能對比評測

維度自建 WebSocket現成 IM 軟體
訊息可靠性需自己實作 ack、重試、冪等內建確認、重試與補發策略
離線訊息需持久化並維護游標預設提供離線佇列與補拉
多端同步需服務端按 seq 排序推送已處理多端游標與已讀狀態
後台管理需另建控制台提供成員、群組、審計等後台
維運成本長期監控、擴容、修復供應商維護,按訂閱付費

如果你的團隊只有前端,或需要在兩週內上線多端同步與離線訊息,自建通常不划算;可先按預算有限的中小企業即時通訊選型,重點核對這5個維度驗證需求。已經決定自建時,先算清長期成本,參考成本拆解:自建即時通訊需要多少錢,包含哪些隱性支出。

常見問題

WebSocket 能保證訊息必達嗎?

不能。WebSocket 只提供全雙工通道,斷線、伺服器端故障、用戶端未 ack 都可能丟訊息。必達需要持久化、seq 補拉、ack 重試和冪等去重。

心跳間隔多少合適?

桌面端通常 30 秒,伺服器端 35 秒無任何幀判定斷線;行動端可放寬到 60 秒,但必須監聽網路切換並主動重連。

沒有後端經驗能快速實作即時通訊嗎?

能跑通單頁面 demo,但處理離線訊息、多端同步和維運需要後端與儲存基礎。沒有後端建議直接使用現成 SDK 或商用 IM。

什麼時候該停止自建,直接採購?

當需求包含離線必達、多端同步、後台管理、稽核合規中的任意兩項以上,或上線時間少於兩週時,採購商用 IM 通常比自建更穩、更便宜。

瀏覽 1