本篇是 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)
- SSE:
EventSource自動重連,並帶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,徒增連線管理與擴展成本。
最佳實踐
- 先問是不是只要單向:是 → SSE 優先(更簡單、自動重連)
- 一定要有心跳,偵測死連線、避免中間設備閒置斷線
- WebSocket 要自己做重連(指數退避);SSE 靠內建但仍要處理重複事件
- 認證 token 要可過期 / 撤銷,長連線考慮重新驗證
- 一開始就規劃水平擴展:sticky session + pub/sub backplane(Redis/Kafka)
- 連線數與資源設上限,防止連線洩漏與資源耗盡
- 瀏覽器外/服務間的串流改用 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