04-2. WebSocket vs HTTP 深度比較
理解何時該用 WebSocket,何時該用 HTTP
目錄
🔀 WebSocket vs HTTP 深度比較
🎯 核心差異
💡 比喻:
HTTP = 郵寄信件
- 你寄一封信(請求)
- 郵局回信(回應)
- 一問一答,不能主動通知
WebSocket = 電話通話
- 撥通後保持連線
- 雙方隨時可以說話
- 即時雙向溝通連線模式對比
HTTP(請求-回應):
客戶端 伺服器
│ │
├─ GET /data ──────────────>│
│ │
│<─ 200 OK ──────────────────┤
│ (data) │
│ │
├─ GET /data ──────────────>│ (需要新資料,再次請求)
│ │
│<─ 200 OK ──────────────────┤
│ (new data) │WebSocket(雙向通訊):
客戶端 伺服器
│ │
├─ WebSocket Handshake ────>│ (一次連線)
│<─ 101 Switching Protocols ┤
│ │
│<══════ 保持連線 ═══════════>│
│ │
├─ Message ────────────────>│ (隨時發送)
│<─ Message ──────────────────┤ (伺服器主動推送)
├─ Message ────────────────>│
│<─ Message ──────────────────┤📊 詳細比較表
| 特性 | HTTP | WebSocket |
|---|---|---|
| 連線 | 短連線(一次性) | 長連線(持續) |
| 方向 | 單向(客戶端發起) | 雙向(任一方發起) |
| 開銷 | 每次請求都有 Header(~200+ bytes) | 握手後僅需 2-6 bytes |
| 延遲 | 高(需建立連線) | 低(連線已建立) |
| 即時性 | 需輪詢 | 即時推送 |
| 瀏覽器支援 | ✅ 所有瀏覽器 | ✅ 現代瀏覽器 |
| 快取 | ✅ 支援 | ❌ 不支援 |
| CDN | ✅ 支援 | ❌ 不支援 |
| 防火牆 | ✅ 友善 | ⚠️ 可能被阻擋 |
🔄 HTTP 實現即時通訊的方法
1️⃣ 短輪詢(Short Polling)
💡 比喻:每隔幾秒問一次「有新訊息嗎?」// 客戶端:每 3 秒請求一次
function shortPolling() {
setInterval(async () => {
const response = await fetch('/api/messages');
const messages = await response.json();
if (messages.length > 0) {
displayMessages(messages);
}
}, 3000); // 每 3 秒
}
shortPolling();缺點:
- ❌ 大量無用請求(沒有新資料也要請求)
- ❌ 延遲高(最多 3 秒才知道有新訊息)
- ❌ 伺服器負擔大
適用場景:
- 更新頻率低(每分鐘)
- 對即時性要求不高
2️⃣ 長輪詢(Long Polling)
💡 比喻:打電話問「有新訊息就告訴我」,沒有的話就一直等// 客戶端
async function longPolling() {
while (true) {
try {
const response = await fetch('/api/messages/poll');
const messages = await response.json();
displayMessages(messages);
// 立即發起下一次輪詢
} catch (error) {
await new Promise(resolve => setTimeout(resolve, 3000));
}
}
}
longPolling();# 伺服器(Flask)
from flask import Flask, jsonify
import queue
import time
app = Flask(__name__)
message_queues = {}
@app.route('/api/messages/poll')
def poll_messages():
user_id = request.args.get('user_id')
# 等待新訊息(最多 30 秒)
timeout = 30
start_time = time.time()
while time.time() - start_time < timeout:
if user_id in message_queues and not message_queues[user_id].empty():
messages = []
while not message_queues[user_id].empty():
messages.append(message_queues[user_id].get())
return jsonify(messages)
time.sleep(0.5) # 避免忙等
# 超時,回應空陣列
return jsonify([])優點:
- ✅ 比短輪詢即時
- ✅ 減少無用請求
缺點:
- ❌ 伺服器需維持大量連線
- ❌ 斷線需重新連線
3️⃣ Server-Sent Events (SSE)
💡 比喻:訂閱電台廣播,伺服器持續推送// 客戶端
const eventSource = new EventSource('/api/stream');
eventSource.onmessage = (event) => {
const message = JSON.parse(event.data);
displayMessage(message);
};
eventSource.onerror = (error) => {
console.error('SSE error:', error);
eventSource.close();
};# 伺服器(Flask)
from flask import Flask, Response
import time
import json
app = Flask(__name__)
@app.route('/api/stream')
def stream():
def generate():
while True:
# 產生事件
data = {
'message': 'Hello',
'timestamp': time.time()
}
yield f"data: {json.dumps(data)}\n\n"
time.sleep(1)
return Response(generate(), mimetype='text/event-stream')優點:
- ✅ 伺服器主動推送
- ✅ 自動重連
- ✅ 簡單易用
缺點:
- ❌ 單向(只能伺服器→客戶端)
- ❌ 僅支援文字
- ❌ 連線數限制(HTTP/1.1 每域名 6 個)
🆚 HTTP vs WebSocket 性能比較
頻寬消耗
HTTP(每次請求):
GET /api/data HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0
Accept: application/json
Cookie: session=abc123
...
(Header 約 200-500 bytes)
資料:{"temp": 25}(13 bytes)
總共:213-513 bytesWebSocket(握手後):
Frame Header: 2-6 bytes
資料:{"temp": 25}(13 bytes)
總共:15-19 bytes
節省:92-96% 的頻寬!延遲比較
# 模擬測試
import time
import requests
from websocket import create_connection
# HTTP 輪詢
start = time.time()
for i in range(100):
response = requests.get('http://example.com/api/data')
http_time = time.time() - start
print(f"HTTP 100 次請求:{http_time:.2f} 秒")
# WebSocket
ws = create_connection('ws://example.com/ws')
start = time.time()
for i in range(100):
ws.send('{"action": "get_data"}')
result = ws.recv()
ws_time = time.time() - start
print(f"WebSocket 100 次訊息:{ws_time:.2f} 秒")
# 結果範例:
# HTTP 100 次請求:5.23 秒
# WebSocket 100 次訊息:0.15 秒
# WebSocket 快 ~35 倍!📋 選擇指南
使用 HTTP 的場景
✅ 適合 HTTP:
1. RESTful API(CRUD 操作):
// 取得使用者資料
GET /api/users/123
// 建立文章
POST /api/articles
{
"title": "New Article",
"content": "..."
}
// 更新設定
PUT /api/settings
{
"theme": "dark"
}2. 需要快取的資源:
// 圖片、CSS、JS 等靜態資源
GET /images/logo.png
Cache-Control: max-age=31536000
// API 資料快取
GET /api/products?category=electronics
Cache-Control: max-age=3003. 無需即時性的資料:
// 文章列表(不需即時更新)
fetch('/api/articles')
// 使用者個人檔案
fetch('/api/profile')
// 歷史記錄
fetch('/api/orders/history')4. SEO 需求:
<!-- 搜尋引擎可以爬取 HTTP 頁面 -->
<meta property="og:title" content="Article Title" />使用 WebSocket 的場景
✅ 適合 WebSocket:
1. 即時聊天:
// 即時訊息推送
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
displayMessage(message);
};
// 發送訊息
ws.send(JSON.stringify({
type: 'message',
content: 'Hello!'
}));2. 即時通知:
// 訂單狀態更新
ws.onmessage = (event) => {
const notification = JSON.parse(event.data);
if (notification.type === 'order_shipped') {
showNotification('您的訂單已出貨!');
}
};3. 多人協作:
// Google Docs 風格的協作編輯
ws.onmessage = (event) => {
const update = JSON.parse(event.data);
if (update.type === 'text_change') {
applyTextChange(update.position, update.text);
}
if (update.type === 'cursor_move') {
showOtherUserCursor(update.userId, update.position);
}
};4. 即時數據(股票、遊戲):
// 股票價格即時更新
ws.onmessage = (event) => {
const priceUpdate = JSON.parse(event.data);
updateStockPrice(priceUpdate.symbol, priceUpdate.price);
};
// 遊戲狀態同步
ws.onmessage = (event) => {
const gameState = JSON.parse(event.data);
updateGameState(gameState);
};5. IoT 監控:
// 感測器數據即時顯示
ws.onmessage = (event) => {
const sensorData = JSON.parse(event.data);
updateDashboard(sensorData);
};🔄 混合使用策略
很多應用會同時使用 HTTP 和 WebSocket:
class HybridApp {
constructor() {
this.ws = null;
this.setupWebSocket();
}
// WebSocket:即時通訊
setupWebSocket() {
this.ws = new WebSocket('ws://example.com/ws');
this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'notification') {
this.handleNotification(data);
} else if (data.type === 'chat') {
this.handleChatMessage(data);
}
};
this.ws.onerror = () => {
// WebSocket 失敗,降級到 Long Polling
this.fallbackToPolling();
};
}
// HTTP:API 請求
async loadUserProfile() {
const response = await fetch('/api/user/profile');
const profile = await response.json();
this.displayProfile(profile);
}
async uploadFile(file) {
const formData = new FormData();
formData.append('file', file);
await fetch('/api/upload', {
method: 'POST',
body: formData
});
}
// HTTP:靜態資源
async loadArticles() {
const response = await fetch('/api/articles');
const articles = await response.json();
this.renderArticles(articles);
}
// WebSocket:即時發送訊息
sendChatMessage(message) {
this.ws.send(JSON.stringify({
type: 'chat',
content: message
}));
}
// 降級策略
fallbackToPolling() {
setInterval(async () => {
const response = await fetch('/api/messages/poll');
const messages = await response.json();
messages.forEach(msg => this.handleChatMessage(msg));
}, 3000);
}
}
// 使用
const app = new HybridApp();
// HTTP:載入初始資料
app.loadUserProfile();
app.loadArticles();
// WebSocket:即時通訊
app.sendChatMessage('Hello!');🎓 常見面試題
Q1:為什麼 WebSocket 比 HTTP 更適合即時通訊?
答案:
核心原因:
- 雙向通訊:
HTTP:
客戶端 → 伺服器(請求)
客戶端 ← 伺服器(回應)
伺服器「無法」主動推送
WebSocket:
客戶端 ↔ 伺服器
任一方都可以「主動」發送訊息- 低延遲:
HTTP 輪詢:
- 每次都要建立 TCP 連線(三次握手)
- 發送完整 HTTP Header(200+ bytes)
- 延遲:~100-500ms
WebSocket:
- 連線已建立(無需握手)
- Header 僅 2-6 bytes
- 延遲:~1-10ms- 低頻寬:
發送 100 次 "hello"(5 bytes)
HTTP:
每次請求:Header ~200 bytes + Data 5 bytes = 205 bytes
100 次:20,500 bytes
WebSocket:
握手:~200 bytes(一次)
每次訊息:Header 2 bytes + Data 5 bytes = 7 bytes
100 次:200 + 700 = 900 bytes
節省:95.6% 的頻寬!- 伺服器負擔:
HTTP 輪詢(1000 個用戶,每 3 秒一次):
每秒請求數:1000 / 3 ≈ 333 req/s
WebSocket(1000 個用戶):
1000 個長連線(持續保持)
只有在有訊息時才處理記憶口訣:「雙低低低」
- 雙(雙向)
- 低延遲
- 低頻寬
- 低負擔
Q2:WebSocket 有什麼缺點?
答案:
主要缺點:
1. 無法快取:
HTTP:
瀏覽器自動快取:GET /api/products
CDN 快取:圖片、CSS、JS
WebSocket:
訊息無法快取
每次都是「新鮮」資料2. 負載平衡困難:
HTTP(無狀態):
請求 1 → Server A
請求 2 → Server B
請求 3 → Server A
✅ 簡單(隨機分配)
WebSocket(有狀態):
連線 1 → Server A(必須保持在 Server A)
如果 Server A 掛掉?
需要:Sticky Session 或狀態同步3. 防火牆/代理問題:
企業防火牆可能阻擋 WebSocket
某些 Proxy 不支援 Upgrade 請求
解決:降級到 Long Polling4. 連線管理複雜:
// 需要處理:
ws.onerror = () => {}; // 錯誤處理
ws.onclose = () => {}; // 斷線重連
setInterval(() => { // 心跳檢測
ws.send('ping');
}, 30000);5. 除錯困難:
HTTP:
- Chrome DevTools 清楚顯示
- Postman 可以測試
- curl 可以模擬
WebSocket:
- 訊息是動態的
- 需要專門工具(wscat)
- 較難重現問題6. SEO 不友善:
搜尋引擎爬蟲無法執行 JavaScript WebSocket
動態內容無法被索引Q3:什麼時候應該使用 Long Polling 而不是 WebSocket?
答案:
使用 Long Polling 的場景:
1. 舊瀏覽器支援:
IE 9 及以下不支援 WebSocket
使用 Long Polling 作為後備方案2. 防火牆限制:
某些企業防火牆阻擋 WebSocket
Long Polling 使用標準 HTTP,較不易被阻擋3. 更新頻率低:
如果訊息每分鐘才有一次:
Long Polling 的開銷可以接受
如果每秒數百次更新:
WebSocket 更合適4. 無需雙向通訊:
只需要伺服器→客戶端推送
不需要客戶端→伺服器即時發送
範例:新聞推送、股票報價
→ 可以用 Long Polling 或 SSE5. 短期連線:
只需要監聽幾分鐘
建立 WebSocket 的成本不值得
範例:付款頁面等待交易完成降級策略:
class RealTimeCommunication {
constructor(url) {
this.url = url;
this.preferWebSocket = true;
this.connect();
}
connect() {
if (this.preferWebSocket && 'WebSocket' in window) {
this.useWebSocket();
} else {
this.useLongPolling();
}
}
useWebSocket() {
this.ws = new WebSocket(this.url);
this.ws.onerror = () => {
console.log('WebSocket 失敗,降級到 Long Polling');
this.useLongPolling();
};
this.ws.onmessage = (event) => {
this.handleMessage(JSON.parse(event.data));
};
}
async useLongPolling() {
while (true) {
try {
const response = await fetch(this.url + '/poll', {
method: 'GET',
signal: AbortSignal.timeout(30000) // 30 秒超時
});
const data = await response.json();
if (data.messages) {
data.messages.forEach(msg => this.handleMessage(msg));
}
} catch (error) {
await new Promise(resolve => setTimeout(resolve, 3000));
}
}
}
handleMessage(message) {
console.log('收到訊息:', message);
}
}Q4:如何選擇 WebSocket 或 SSE?
答案:
SSE (Server-Sent Events) = 單向推送
WebSocket = 雙向通訊比較:
| 特性 | SSE | WebSocket |
|---|---|---|
| 方向 | 單向(伺服器→客戶端) | 雙向 |
| 資料格式 | 文字(UTF-8) | 文字+二進位 |
| 協定 | HTTP | WebSocket (ws://) |
| 自動重連 | ✅ 內建 | ❌ 需手動實作 |
| 瀏覽器支援 | ✅ 現代瀏覽器 | ✅ 現代瀏覽器 |
| 複雜度 | 簡單 | 複雜 |
使用 SSE:
// 客戶端
const eventSource = new EventSource('/api/notifications');
eventSource.onmessage = (event) => {
const notification = JSON.parse(event.data);
showNotification(notification);
};
// 自動重連(內建)
eventSource.onerror = () => {
console.log('連線中斷,自動重連...');
};# 伺服器(Flask)
@app.route('/api/notifications')
def notifications():
def generate():
while True:
# 等待新通知
notification = get_next_notification()
yield f"data: {json.dumps(notification)}\n\n"
return Response(generate(), mimetype='text/event-stream')選擇建議:
使用 SSE:
✅ 只需伺服器推送(通知、新聞、股票)
✅ 需要自動重連
✅ 簡單易用
使用 WebSocket:
✅ 需要雙向通訊(聊天、遊戲)
✅ 需要二進位資料(圖片、音訊)
✅ 低延遲要求高Q5:HTTP/2 和 HTTP/3 對即時通訊有幫助嗎?
答案:
HTTP/2 改進:
1. 多路復用(Multiplexing):
HTTP/1.1:每個請求需要新連線(最多 6 個)
HTTP/2:單一連線可以同時處理多個請求
→ Long Polling 效率提升
2. Server Push:
伺服器可以主動推送資源
但不適合即時訊息(需要預知資源)// HTTP/2 Server Push(不適合即時訊息)
// 只適合靜態資源預載
response.push('/style.css');
response.push('/script.js');HTTP/3(QUIC)改進:
1. 基於 UDP:
TCP 的 Head-of-Line Blocking 問題解決
封包遺失不會阻塞整個連線
2. 更快的連線建立:
0-RTT(Zero Round Trip Time)
首次連線後,之後可以立即發送資料
3. 更好的行動網路支援:
網路切換(WiFi ↔ 4G)不會斷線結論:
HTTP/2 和 HTTP/3 改進了 HTTP 的效能
但對於「真正即時」的應用,WebSocket 仍是首選
HTTP/2 SSE > HTTP/1.1 Long Polling
但
WebSocket > HTTP/2 SSE(對即時性要求極高的場景)選擇總結:
即時性要求:
極高(<10ms):WebSocket
高(<100ms):WebSocket 或 HTTP/2 SSE
中(<1s):HTTP/2 SSE 或 Long Polling
低(>1s):HTTP 輪詢📝 總結
核心要點:
- HTTP:請求-回應,適合一般 API、靜態資源
- WebSocket:雙向長連線,適合即時通訊
選擇原則:
需要即時性、雙向通訊?→ WebSocket
需要快取、SEO、簡單?→ HTTP
只需單向推送?→ SSE
瀏覽器相容性?→ Long Polling最佳實踐:
- 混合使用(HTTP 載入資料 + WebSocket 即時通訊)
- 提供降級方案(WebSocket → Long Polling)
- 根據場景選擇最適合的技術
記憶口訣:「雙即快,HTTP 簡穩廣」
- WebSocket:雙(雙向)、即(即時)、快(快速)
- HTTP:簡(簡單)、穩(穩定)、廣(廣泛支援)