Web 長連線
網頁應用要怎麼維持持久/長壽命的連線
目錄
- HTTP Keep-Alive(持久連線)
- HTTP Pipelining
- Long Polling 長輪詢
- 串流(分塊傳輸)
- Server-Sent Events(SSE)
- WebSocket
- HTTP/2 多工
- 總結
在 HTTP 的世界裡,「長連線」通常指的是讓一條 TCP 連線在多個請求之間保持開啟,而不是每個請求結束就關掉(也常稱為持久連線或 keep-alive)。依你想達成的目的不同,這個概念在實務上有好幾種形式。
1. HTTP Keep-Alive(持久連線)
- HTTP/1.1 的預設行為。
- 用戶端與伺服器在多個請求/回應之間重複使用同一條 TCP 連線。
- 省掉反覆開關連線的延遲與開銷。
Header 範例:
text
Connection: keep-alive
流程:
text
Client ──── request 1 ────▶ Server
Client ◀─── response 1 ─── Server
Client ──── request 2 ────▶ Server (same TCP connection)
Client ◀─── response 2 ─── Server
2. HTTP Pipelining
- 多個 HTTP 請求一次送出,不必等前一個回應。
- 但回應仍必須按順序回來。
- 因為**隊頭阻塞(head-of-line blocking)**問題,今天幾乎不用了。
流程:
text
Client ──── request 1 ────▶
Client ──── request 2 ────▶ Server
Client ──── request 3 ────▶
Client ◀─── response 1, 2, 3 (in order)
3. Long Polling 長輪詢
- 用戶端送出一個請求。
- 伺服器把這個請求壓著不回,等到有新資料才回。
- 資料一送出,連線就關閉,用戶端立刻再連一次。
- 常用來做接近即時的更新(比較舊的技術)。
流程:
text
Client ──── request ─────────────────────▶ Server
(server waits for new data)
Client ◀─── response (when data ready) ── Server
Client ──── reconnect immediately ───────▶ Server
**適用場景:**聊天室、通知系統(舊做法)。
4. 串流(分塊傳輸 Chunked Transfer)
- 伺服器保持連線開著,持續以 chunk 的形式把資料送出去。
- 適合 log、即時資料流、進度更新。
Header:
text
Transfer-Encoding: chunked
範例(Node.js):
javascript
res.writeHead(200, { 'Transfer-Encoding': 'chunked' });
setInterval(() => res.write('data chunk\n'), 1000);
5. Server-Sent Events(SSE)
- 單向通訊:只有伺服器 → 用戶端。
- 瀏覽器保持一條長壽命的 HTTP 連線。
- 做即時更新(推播通知、儀表板)比 WebSocket 簡單。
- 斷線會自動重連。
Header:
text
Content-Type: text/event-stream
伺服器端範例(Node.js):
javascript
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
setInterval(() => res.write(`data: ${JSON.stringify({ time: Date.now() })}\n\n`), 1000);
用戶端範例:
javascript
const source = new EventSource('/events');
source.onmessage = (e) => console.log(e.data);
6. WebSocket(從 HTTP 升級而來)
- 一開始是 HTTP,接著升級成持久的全雙工連線。
- 支援即時的雙向通訊。
- 最適合聊天、遊戲、即時協作。
升級用的 header:
text
Upgrade: websocket
Connection: Upgrade
伺服器端範例(Node.js 搭配 ws):
javascript
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', (ws) => {
ws.on('message', (msg) => ws.send(`echo: ${msg}`));
});
用戶端範例:
javascript
const ws = new WebSocket('ws://localhost:8080');
ws.onmessage = (e) => console.log(e.data);
ws.send('hello');
7. HTTP/2 多工
- 多條 stream 共用同一條長壽命連線。
- 解決了 HTTP 層的隊頭阻塞(HTTP/1.1 的 pipelining 沒解決)。
- 更有效率:header 會壓縮(HPACK),各條 stream 互相獨立。
和 HTTP/1.1 的主要差異:
| 特性 | HTTP/1.1 | HTTP/2 |
|---|---|---|
| 每個 host 的連線數 | 多條 | 一條(多工) |
| 隊頭阻塞 | 有 | 沒有(HTTP 層) |
| Header 壓縮 | 沒有 | 有(HPACK) |
| Server push | 沒有 | 有 |
總結
| 方法 | 方向 | 連線 | 適用場景 |
|---|---|---|---|
| Keep-Alive | 請求/回應 | 重用 TCP | 一般 HTTP 效率 |
| Pipelining | 請求/回應 | 重用 TCP | 批次請求(幾乎不用了) |
| Long Polling | 伺服器 → 用戶端 | 每次都重開 | 舊式的即時更新 |
| 分塊串流 | 伺服器 → 用戶端 | 長壽命 | log、即時資料流 |
| SSE | 伺服器 → 用戶端 | 長壽命 | 儀表板、通知 |
| WebSocket | 全雙工 | 長壽命 | 聊天、遊戲、協作 |
| HTTP/2 | 請求/回應 | 多工 | 現代 API、降低延遲 |
快速決策指南:
- **要重用連線:**Keep-Alive、HTTP/2
- **用戶端在等資料:**Long Polling
- **伺服器要持續送資料:**串流、SSE
- **要全雙工(雙向)通訊:**WebSocket
優點、缺點與適用場景比較
| 方法 | 優點 | 缺點 | 最適合 |
|---|---|---|---|
| HTTP Keep-Alive | • 省掉 TCP 三向交握的開銷 • HTTP/1.1 預設,支援度極廣 • 簡單 —— 不用多寫程式 |
• 依然只是請求/回應 • 伺服器要抱著閒置連線 • 同一條連線內仍有隊頭阻塞 |
• REST API • 靜態資源 • 任何一般 HTTP 流量 |
| HTTP Pipelining | • 不必等就能連送多個請求 • 減少來回延遲 |
• 回應必須按序抵達(隊頭阻塞) • proxy/伺服器支援很差 • 基本上已被淘汰 |
• 幾乎不用;已被 HTTP/2 取代 |
| Long Polling | • 任何 HTTP 環境都能用 • 對防火牆/proxy 友善 • 不需要特殊的瀏覽器 API |
• 伺服器資源吃很兇(連線被抱著) • 每次回應後重連都有延遲 • 不是真的即時 |
• 舊的聊天系統 • 舊環境裡的通知輪詢 • 沒有 WebSocket/SSE 可用時 |
| 分塊串流 | • 伺服器端實作簡單 • 純 HTTP 就能跑 • 適合很大或長度未知的回應 |
• 沒有內建重連 • 用戶端要自己處理不完整的 chunk • 結構性不如 SSE |
• 檔案下載 • 追 log • 回報進度 |
| Server-Sent Events(SSE) | • 內建自動重連 • 瀏覽器原生 EventSource API• 輕量 —— 純 HTTP,不需要升級 • 用標準負載平衡器就好擴展 |
• 只能伺服器 → 用戶端(單向) • 只能傳文字 • 某些 proxy 會緩衝而造成延遲 |
• 即時儀表板 • 新聞/賽事比分 • 推播通知 • 動態時報 |
| WebSocket | • 全雙工(雙向) • 交握完成後延遲很低 • 支援二進位與文字 • 瀏覽器支援廣泛 |
• 需要協定升級 • 較難擴展(要 sticky session 或 pub/sub) • 有些防火牆/proxy 會擋 WS • 錯誤處理較複雜 |
• 聊天應用 • 線上遊戲 • 即時協作(例如 Google Docs) • 金融交易報價 |
| HTTP/2 多工 | • 一條連線上跑多條 stream • 沒有隊頭阻塞(HTTP 層) • Header 壓縮(HPACK) • 可以 server push |
• TCP 層的隊頭阻塞仍在(由 HTTP/3 解決) • 多數瀏覽器要求 TLS • 比較難除錯 |
• 現代 REST/gRPC API • 資源很多的網頁 • 微服務之間的通訊 |
可擴展性的考量
| 方法 | 伺服器資源 | 水平擴展 |
|---|---|---|
| Keep-Alive | 低(短暫閒置) | 容易 |
| Long Polling | 高(每個用戶端抱著一條執行緒) | 中等 |
| 分塊串流 | 中(開著的回應流) | 中等 |
| SSE | 中(開著的回應流) | 設計成無狀態就很容易 |
| WebSocket | 中偏高(持久連線) | 需要 sticky session 或訊息中介(Redis pub/sub) |
| HTTP/2 | 低到中(一條連線、多條 stream) | 容易 |