即時通訊完全指南:WebSocket 與 SSE

深入即時通訊:輪詢、SSE、WebSocket 的原理與差異,連線管理、心跳重連、認證與水平擴展

本篇是 API 設計範式總覽 中即時通訊的深入版;與 REST / GraphQL / gRPC 的整體比較看總覽篇。


目錄


為什麼需要即時通訊?

一般 REST/HTTP 是請求—回應:client 問、server 才答。但很多場景需要 server 主動把更新推給 client:聊天訊息、即時通知、股價、協作游標、AI 逐字輸出。

請求—回應:client 不問,就拿不到新資料
即時通訊:  server 有更新 → 主動推送給 client

達成方式有三種,由簡到強:輪詢 → SSE → WebSocket


輪詢(Polling)

短輪詢(Short Polling)

client 定時打 API 問「有沒有新資料」:

client ──GET /messages?since=...──► server(每 3 秒一次)
  • ✅ 最簡單,一般 REST 就能做、無特殊基礎設施
  • ❌ 多數請求是「沒有新資料」→ 浪費;更新有延遲(最差等一個間隔)

長輪詢(Long Polling)

client 發請求,server hold 住直到有資料或逾時才回,client 收到後立刻再發下一個:

client ──請求──► server(hold 住…有資料才回)──► client 收到 → 立刻再請求
  • ✅ 比短輪詢即時、相容性好(純 HTTP)
  • ❌ server 要維持大量掛起連線;仍有反覆建連開銷

輪詢是「沒有更好選項時」的相容退路。能用 SSE/WebSocket 就別長期靠輪詢。


SSE(Server-Sent Events)

基於 HTTP 的單向推送:連線開著,server → client 持續送事件流。瀏覽器用原生 EventSource

用法

// 瀏覽器端:原生 API,自動重連
const es = new EventSource("/stream");
es.onmessage = (e) => console.log(e.data);
es.addEventListener("price", (e) => console.log("price:", e.data));

事件格式(伺服器回應)

Content-Type: text/event-stream

event: price
data: {"symbol":"AAPL","price":195.3}
id: 1001

data: 純文字訊息(預設事件)
  • data: 事件內容、event: 事件名、id: 事件 ID、retry: 重連間隔
  • 空行分隔每個事件

特點

  • 瀏覽器原生支援、自動重連:斷線會自動重連,並用 Last-Event-ID 標頭告知上次位置,server 可補送
  • ✅ 純 HTTP(好過代理/防火牆)、實作簡單
  • 適合 AI 逐字輸出(OpenAI 等串流回應就用 SSE)、通知、即時報價
  • 單向(client 要傳資料仍走一般請求)
  • ⚠️ HTTP/1.1 下每網域同時連線數有限(約 6 條);HTTP/2 多工可緩解

WebSocket

從 HTTP 升級(Upgrade) 成的全雙工持久連線,兩端都能隨時主動送訊息。

握手(Handshake)

以一個 HTTP 請求帶 Upgrade 標頭發起,成功後協定切換成 WebSocket:

GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade

握手後連線變成雙向的 WebSocket(URL scheme 為 ws:// 或加密的 wss://)。

用法

const ws = new WebSocket("wss://example.com/chat");
ws.onopen = () => ws.send("hello");
ws.onmessage = (e) => console.log(e.data);
ws.onclose = (e) => console.log("closed", e.code);

特點

  • 真雙向、低延遲,連線存活期間隨時互傳
  • ✅ 可傳文字或二進位(frame)
  • ✅ 適合聊天、多人遊戲、協作編輯、即時對戰
  • ❌ 非 HTTP 請求—回應模型 → 需自己處理連線管理、重連、擴展
  • ❌ 瀏覽器原生 WebSocket 不能自訂標頭(認證較麻煩,見後文)

Socket.IO 不是 WebSocket:它是建在 WebSocket(及輪詢 fallback)之上的函式庫,加了自動重連、房間、廣播等,但兩端都要用 Socket.IO,與原生 WebSocket 不相容。


連線管理:心跳與重連

持久連線會遇到「連線其實已死、但雙方都不知道」的問題(NAT 逾時、網路中斷)。

心跳(Heartbeat / Ping-Pong)

定期互送 ping/pong 確認連線還活著,並避免中間設備因閒置斷線:

每 30 秒:client/server 互送 ping → 對方回 pong
逾時沒收到 pong → 判定斷線 → 關閉並重連
  • WebSocket 協定有內建 ping/pong frame
  • SSE 常以送一個 comment 行(: keep-alive)保活

重連(Reconnection)

  • SSEEventSource 自動重連,並帶 Last-Event-ID,server 可補送漏掉的事件
  • WebSocket要自己實作重連邏輯,建議用指數退避(exponential backoff) 避免斷線時瞬間湧入重連

認證

即時連線的認證比一般 REST 麻煩:

SSE

EventSource 原生不能設自訂標頭,但可改用支援 fetch 的方式帶 Authorization;或用 cookie 認證;或把 token 放 query string(注意會進日誌)。

WebSocket

瀏覽器原生 WebSocket 無法自訂標頭,常見做法:

做法 說明
Cookie 同源時握手會帶 cookie,最自然
token 放 query wss://...?token=...(簡單,但會進伺服器日誌)
Sec-WebSocket-Protocol 把 token 塞進 subprotocol 標頭
連線後第一則訊息驗證 連上後先送 auth 訊息,驗證前不給資料

共同原則:token 要能過期 / 可撤銷,並在連線存續期間考慮重新驗證(長連線可能跨越 token 失效)。認證基礎見 認證與授權


水平擴展

單機持久連線好做,多台 server 才是真正的挑戰:連線分散在不同機器上,A 機收到的訊息要怎麼推給連在 B 機的使用者?

                    使用者甲(連 server A)   使用者乙(連 server B)
                          │                        │
   訊息進到 A ── 要推給乙 ──┘  ❌ 乙不在 A 上,A 推不到

解法

問題 解法
連線要回到同一台(狀態) Sticky session(負載平衡依 client 固定導向同台)
跨機器廣播訊息 Pub/Sub backplane:用 Redis Pub/Sub、Kafka 等當訊息匯流排,任一台收到就廣播給所有 server,各自推給自己的連線
連線數爆量 水平加機器 + backplane;或用託管服務(如 Pusher、Ably、雲端 WebSocket 服務)

這是「即時功能上線後」最容易低估的工程量——單機 demo 很簡單,多機擴展才見真章。


三者深入比較

面向 輪詢 SSE WebSocket
方向 client 拉 server → client(單向) 雙向
底層 一般 HTTP HTTP(長連線事件流) TCP(HTTP 升級)
即時性 差(看間隔) 最好
瀏覽器支援 原生 原生 EventSource 原生 WebSocket
自動重連 N/A ✅ 內建 ❌ 自己實作
自訂標頭認證 ⚠️ 受限 ❌(原生不支援)
二進位 ❌(文字)
實作 / 擴展成本 低~中
招牌場景 相容退路 通知 / AI 串流 / 報價 聊天 / 遊戲 / 協作

怎麼選

只需 server 單向推(通知、即時報價、AI 逐字輸出)
  → SSE(簡單、原生、自動重連,多數即時需求其實只要這個)

需要雙向即時互動(聊天、多人遊戲、協作編輯)
  → WebSocket

環境老舊 / 受限、或只是偶爾更新
  → 輪詢(長輪詢較即時)

後端服務之間的串流(非瀏覽器)
  → gRPC streaming(見 grpc 篇),不必用 WebSocket

常見誤區:很多「即時」需求其實是單向推送,用 SSE 就夠,卻一律上 WebSocket,徒增連線管理與擴展成本。


最佳實踐

  1. 先問是不是只要單向:是 → SSE 優先(更簡單、自動重連)
  2. 一定要有心跳,偵測死連線、避免中間設備閒置斷線
  3. WebSocket 要自己做重連(指數退避);SSE 靠內建但仍要處理重複事件
  4. 認證 token 要可過期 / 撤銷,長連線考慮重新驗證
  5. 一開始就規劃水平擴展:sticky session + pub/sub backplane(Redis/Kafka)
  6. 連線數與資源設上限,防止連線洩漏與資源耗盡
  7. 瀏覽器外/服務間的串流改用 gRPC streaming,別硬套 WebSocket

常見問題

問題 1:SSE 和 WebSocket 到底怎麼選?

只要 server 單向推 → SSE(簡單、原生、自動重連);要雙向互動 → WebSocket。多數「即時」需求其實是單向,SSE 就夠。

問題 2:為什麼 AI 聊天逐字輸出用 SSE?

那是單向串流(server 把 token 一個個推給前端),SSE 剛好夠用、走標準 HTTP、又比 WebSocket 簡單。OpenAI 等串流回應多以 SSE 實作。

問題 3:WebSocket 認證為什麼麻煩?

瀏覽器原生 WebSocket 不能自訂標頭,沒法像 REST 那樣帶 Authorization。要改用 cookie、query token、subprotocol,或連線後第一則訊息驗證。

問題 4:Socket.IO 就是 WebSocket 嗎?

不是。Socket.IO 是建在 WebSocket(含輪詢 fallback)之上的函式庫,多了重連、房間、廣播等功能,但兩端都要用 Socket.IO,與原生 WebSocket 不相容。

問題 5:即時功能多台 server 要注意什麼?

連線分散在不同機器,跨機器推送要靠 pub/sub backplane(Redis/Kafka)廣播,搭配 sticky session。這是單機 demo 看不出、但上線必踩的擴展問題。


總結

核心要點

  • 即時通訊讓 server 主動推,三種:輪詢 → SSE → WebSocket(由簡到強)
  • SSE:單向、純 HTTP、瀏覽器原生 + 自動重連,適合通知 / AI 串流;多數即時需求其實只要這個
  • WebSocket:雙向全雙工,適合聊天 / 遊戲 / 協作;但要自己做重連、認證受限
  • 心跳偵測死連線;重連 SSE 內建、WebSocket 自己做(指數退避)
  • 認證:WebSocket 原生不能帶標頭,用 cookie/query/subprotocol
  • 水平擴展靠 sticky session + pub/sub backplane(Redis/Kafka)——上線必踩
  • 服務間串流用 gRPC streaming,不必硬套 WebSocket

快速參考

輪詢 SSE WebSocket
方向 單向推 雙向
自動重連 ❌ 自己做
二進位
成本 低中
場景 退路 通知/AI串流 聊天/遊戲

建立日期:2026-06-18

🔗相關文章