04-1. WebSocket 基礎概念

⏱️ 閱讀時間: 10 分鐘 🎯 難度: ⭐⭐ (簡單)


🎯 本篇重點

理解 WebSocket 的基本概念、為什麼需要 WebSocket、WebSocket 的連線建立流程,以及適用場景。


🤔 什麼是 WebSocket?

WebSocket = 全雙工、雙向通訊協定

一句話解釋: WebSocket 就像是打電話,雙方可以同時說話、即時對話;而 HTTP 像是寄信,一問一答、有延遲。


📞 用通訊方式來比喻

HTTP = 寄信(一問一答)

你想知道朋友的近況:

步驟 1:你寫信給朋友(HTTP 請求)
「你好嗎?」
→ 寄出信件
→ 等待...(3 天)

步驟 2:朋友收到信,寫回信(HTTP 回應)
「我很好!」
→ 寄回信件
→ 等待...(3 天)

步驟 3:你想繼續聊天
「今天天氣如何?」
→ 又要寄信
→ 又要等待...

特性:
- 單向(一次只能一方說話)
- 有延遲(要等對方回信)
- 重複建立連線(每次都要寄新信)
- 開銷大(每封信都有完整的地址、信封)

WebSocket = 打電話(即時對話)

你想跟朋友聊天:

步驟 1:撥打電話(建立 WebSocket 連線)
你:喂?
朋友:喂?
→ 連線建立 ✅

步驟 2:自由對話(雙向通訊)
你:你好嗎?
朋友:我很好!今天天氣不錯
你:真的嗎?那要不要出去玩?
朋友:好啊!
(雙方隨時都能說話,不用等對方說完)

步驟 3:掛電話(關閉連線)
你:那就這樣,再見!
朋友:再見!
→ 連線關閉

特性:
- 全雙工(雙方同時說話)
- 即時(沒有延遲)
- 持久連線(不用重複建立)
- 低開銷(連線建立後,資料傳輸很輕量)

🏗️ WebSocket 在網路模型中的位置

OSI 7 層模型

┌──────────────────────────────┬─────────────────┐
│ 7. Application Layer (應用層) │  WebSocket, HTTP│ ← WebSocket 在這裡
├──────────────────────────────┼─────────────────┤
│ 6. Presentation Layer (表示層)│  加密、壓縮      │
├──────────────────────────────┼─────────────────┤
│ 5. Session Layer (會話層)     │  建立、維護會話  │
├──────────────────────────────┼─────────────────┤
│ 4. Transport Layer (傳輸層)   │  TCP            │
├──────────────────────────────┼─────────────────┤
│ 3. Network Layer (網路層)     │  IP             │
├──────────────────────────────┼─────────────────┤
│ 2. Data Link Layer (資料鏈結層)│  Ethernet       │
├──────────────────────────────┼─────────────────┤
│ 1. Physical Layer (實體層)    │  網路線、光纖    │
└──────────────────────────────┴─────────────────┘

WebSocket 位於第 7 層(應用層)

  • WebSocket 是應用層協定
  • 提供全雙工、即時雙向通訊
  • 從 HTTP 升級而來(Upgrade handshake)

TCP/IP 4 層模型

┌─────────────────────────────┬─────────────────┐
│ 4. Application Layer (應用層) │  WebSocket, HTTP│ ← WebSocket 在這裡
├─────────────────────────────┼─────────────────┤
│ 3. Transport Layer (傳輸層)  │  TCP            │
├─────────────────────────────┼─────────────────┤
│ 2. Internet Layer (網際網路層)│  IP             │
├─────────────────────────────┼─────────────────┤
│ 1. Network Access (網路存取層)│  Ethernet       │
└─────────────────────────────┴─────────────────┘

WebSocket 位於第 4 層(應用層)

  • 在 TCP/IP 模型中,WebSocket 是應用層協定
  • 使用 TCP 作為傳輸層協定
  • TCP 提供可靠的雙向連線

對比表:

協定OSI 層級TCP/IP 層級底層協定Port加密版本
HTTPLayer 7Layer 4TCP80HTTPS (443)
WebSocketLayer 7Layer 4TCP80WSS (443)

重點:

  • WebSocket 是應用層協定(兩種模型都是)
  • 使用 TCP 作為傳輸層
  • 非加密:ws://(Port 80),加密:wss://(Port 443)
  • 從 HTTP 握手升級為 WebSocket

WebSocket 連線建立流程:

1. 客戶端發送 HTTP 升級請求(Upgrade: websocket)
   ↓
2. 伺服器同意升級(HTTP 101 Switching Protocols)
   ↓
3. 切換為 WebSocket 協定
   ↓
4. 開始全雙工通訊(雙向傳輸資料)

🌐 為什麼需要 WebSocket?

HTTP 的限制

問題 1:單向通訊
HTTP:客戶端請求 → 伺服器回應
→ 伺服器無法主動推送資料

場景:即時聊天
用 HTTP:
客戶端:每秒輪詢(Polling)「有新訊息嗎?」
伺服器:「沒有」「沒有」「沒有」「有,這是新訊息」
→ 浪費資源(99% 的請求沒有新資料)

問題 2:高延遲
每次請求都要:
1. 建立 TCP 連線(三次握手)
2. 發送完整的 HTTP 標頭(通常數 KB)
3. 等待回應
4. 關閉連線(或 Keep-Alive)
→ 不適合需要即時更新的應用

問題 3:開銷大
每個 HTTP 請求:
GET /messages HTTP/1.1
Host: example.com
User-Agent: ...
Accept: ...
Cookie: ...
... (數百 bytes 到數 KB)

→ 傳輸「有新訊息嗎?」需要 1KB+
→ 如果每秒輪詢 10 次 = 10KB/s 開銷

WebSocket 的解決方案

優勢 1:全雙工通訊
- 客戶端 ⇄ 伺服器(雙向)
- 伺服器可以主動推送
- 不需要輪詢

場景:即時聊天
WebSocket:
連線建立 → 保持連線
有新訊息 → 伺服器主動推送
→ 沒有浪費的請求 ✅

優勢 2:低延遲
- 連線建立後持續保持
- 不需要重複握手
- 資料傳輸即時

優勢 3:低開銷
訊息格式:
[0x81][0x05]Hello
→ 只需 2-10 bytes 的標頭
→ 相比 HTTP 的數百 bytes,開銷低 99%

實際對比:
HTTP 輪詢:
- 每秒 10 次請求
- 每次 1KB 標頭
- 總開銷:10KB/s

WebSocket:
- 持久連線
- 每次 2-10 bytes 標頭
- 總開銷:0.01KB/s
→ 減少 99.9% 開銷

🔌 WebSocket 連線建立流程

握手過程(WebSocket Handshake)

WebSocket 使用 HTTP 升級機制

【第 1 步:客戶端發送升級請求】
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket                          ← 請求升級到 WebSocket
Connection: Upgrade                         ← 連線升級
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== ← 隨機金鑰(Base64)
Sec-WebSocket-Version: 13                   ← WebSocket 版本
Origin: http://example.com

【第 2 步:伺服器回應升級確認】
HTTP/1.1 101 Switching Protocols            ← 切換協定
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= ← 驗證金鑰

【第 3 步:連線建立完成】
→ 從 HTTP 升級為 WebSocket
→ 使用相同的 TCP 連線
→ 開始 WebSocket 通訊

【資料傳輸】
客戶端 ⇄ 伺服器
- 訊息 1
- 訊息 2
- 訊息 3
- ...

【關閉連線】
任一方發送關閉訊息
雙方確認後關閉

Sec-WebSocket-Key 驗證

為什麼需要驗證?
防止非 WebSocket 客戶端誤連

流程:
1. 客戶端產生隨機 key
   Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==

2. 伺服器計算 accept key
   key = "dGhlIHNhbXBsZSBub25jZQ=="
   magic_string = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
   accept = Base64(SHA1(key + magic_string))
   = "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="

3. 伺服器回傳 accept key
   Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

4. 客戶端驗證
   客戶端用相同方式計算
   → 比對是否一致
   → 一致則連線成功 ✅

安全性:
- 防止 HTTP 快取誤當作 WebSocket 回應
- 確認伺服器支援 WebSocket

📦 WebSocket 訊息格式

幀(Frame)結構

WebSocket 使用「幀」(Frame)傳輸資料

基本結構:
 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len |    Extended payload length    |
|I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
|N|V|V|V|       |S|             |   (if payload len==126/127)   |
| |1|2|3|       |K|             |                               |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|     Extended payload length continued, if payload len == 127  |
+ - - - - - - - - - - - - - - - +-------------------------------+
|                               |Masking-key, if MASK set to 1  |
+-------------------------------+-------------------------------+
| Masking-key (continued)       |          Payload Data         |
+-------------------------------- - - - - - - - - - - - - - - - +
:                     Payload Data continued ...                :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|                     Payload Data continued ...                |
+---------------------------------------------------------------+

欄位說明:

FIN (1 bit):
- 1 = 最後一個分片
- 0 = 還有後續分片

Opcode (4 bits):
- 0x0:繼續幀
- 0x1:文字幀(UTF-8)
- 0x2:二進位幀
- 0x8:關閉連線
- 0x9:Ping
- 0xA:Pong

MASK (1 bit):
- 1 = 資料已遮罩(客戶端到伺服器必須遮罩)
- 0 = 資料未遮罩(伺服器到客戶端)

Payload length (7 bits, 7+16 bits, or 7+64 bits):
- 0-125:實際長度
- 126:後面 16 bits 是長度
- 127:後面 64 bits 是長度

Masking-key (32 bits):
- 用於遮罩資料

Payload Data:
- 實際資料

訊息範例

文字訊息:"Hello"

客戶端 → 伺服器:
[0x81][0x85][mask_key...][masked_data...]
 ^     ^     ^            ^
 |     |     |            └─ 遮罩後的資料
 |     |     └─ 遮罩金鑰(4 bytes)
 |     └─ 長度 5 + MASK bit
 └─ FIN=1, Opcode=0x1(文字)

伺服器 → 客戶端:
[0x81][0x05]Hello
 ^     ^    ^
 |     |    └─ 資料
 |     └─ 長度 5
 └─ FIN=1, Opcode=0x1(文字)

二進位訊息:
[0x82][0x05][binary_data...]
 ^
 └─ Opcode=0x2(二進位)

Ping/Pong(心跳):
Ping:  [0x89][0x00]
Pong:  [0x8A][0x00]

關閉連線:
[0x88][0x02][0x03][0xE8]
 ^     ^    ^
 |     |    └─ 關閉碼(1000 = 正常關閉)
 |     └─ 長度 2
 └─ Opcode=0x8(關閉)

💻 實戰範例

JavaScript 客戶端

// 建立 WebSocket 連線
const ws = new WebSocket('ws://localhost:8080/chat');

// 連線開啟
ws.onopen = (event) => {
    console.log('WebSocket 連線已建立');

    // 發送訊息
    ws.send('Hello Server!');
    ws.send(JSON.stringify({ type: 'greeting', message: 'Hi' }));
};

// 接收訊息
ws.onmessage = (event) => {
    console.log('收到訊息:', event.data);

    // 如果是 JSON
    try {
        const data = JSON.parse(event.data);
        console.log('解析後:', data);
    } catch (e) {
        console.log('純文字訊息:', event.data);
    }
};

// 錯誤處理
ws.onerror = (error) => {
    console.error('WebSocket 錯誤:', error);
};

// 連線關閉
ws.onclose = (event) => {
    console.log('WebSocket 連線已關閉');
    console.log('關閉碼:', event.code);
    console.log('關閉原因:', event.reason);
};

// 主動關閉連線
// ws.close(1000, 'Normal closure');

// 檢查連線狀態
console.log('連線狀態:', ws.readyState);
// 0: CONNECTING(連線中)
// 1: OPEN(已開啟)
// 2: CLOSING(關閉中)
// 3: CLOSED(已關閉)

Python 伺服器(使用 websockets)

import asyncio
import websockets
import json

# 儲存所有連線的客戶端
connected_clients = set()

async def chat_handler(websocket, path):
    """處理 WebSocket 連線"""
    # 新客戶端連線
    connected_clients.add(websocket)
    print(f"新客戶端連線,目前 {len(connected_clients)} 個客戶端")

    try:
        async for message in websocket:
            print(f"收到訊息:{message}")

            # 解析 JSON
            try:
                data = json.loads(message)
                print(f"解析後:{data}")
            except json.JSONDecodeError:
                print(f"純文字訊息:{message}")

            # 廣播給所有客戶端
            tasks = []
            for client in connected_clients:
                if client != websocket:  # 不傳給自己
                    tasks.append(client.send(message))

            if tasks:
                await asyncio.gather(*tasks)

            # 回應給發送者
            response = {
                "type": "echo",
                "message": f"伺服器收到:{message}"
            }
            await websocket.send(json.dumps(response))

    except websockets.exceptions.ConnectionClosed:
        print("客戶端斷線")
    finally:
        # 移除客戶端
        connected_clients.remove(websocket)
        print(f"客戶端離開,剩餘 {len(connected_clients)} 個客戶端")

# 啟動伺服器
async def main():
    async with websockets.serve(chat_handler, "localhost", 8080):
        print("WebSocket 伺服器啟動在 ws://localhost:8080")
        await asyncio.Future()  # 持續運行

if __name__ == "__main__":
    asyncio.run(main())

Node.js 伺服器(使用 ws)

const WebSocket = require('ws');

// 建立 WebSocket 伺服器
const wss = new WebSocket.Server({ port: 8080 });

console.log('WebSocket 伺服器啟動在 ws://localhost:8080');

// 連線事件
wss.on('connection', (ws) => {
    console.log('新客戶端連線');

    // 接收訊息
    ws.on('message', (message) => {
        console.log('收到訊息:', message.toString());

        // 廣播給所有客戶端
        wss.clients.forEach((client) => {
            if (client !== ws && client.readyState === WebSocket.OPEN) {
                client.send(message);
            }
        });

        // 回應給發送者
        ws.send(JSON.stringify({
            type: 'echo',
            message: `伺服器收到:${message}`
        }));
    });

    // 錯誤處理
    ws.on('error', (error) => {
        console.error('WebSocket 錯誤:', error);
    });

    // 關閉連線
    ws.on('close', () => {
        console.log('客戶端斷線');
    });

    // 發送歡迎訊息
    ws.send(JSON.stringify({
        type: 'welcome',
        message: '歡迎連線到 WebSocket 伺服器!'
    }));
});

// 心跳檢測(每 30 秒)
setInterval(() => {
    wss.clients.forEach((ws) => {
        if (ws.readyState === WebSocket.OPEN) {
            ws.ping();
        }
    });
}, 30000);

🎯 WebSocket 適用場景

1. 即時聊天應用

場景:Line、Messenger、Slack

需求:
- 訊息即時送達
- 伺服器主動推送
- 雙向通訊

傳統 HTTP 輪詢:
每秒請求一次「有新訊息嗎?」
→ 浪費資源
→ 延遲 1 秒

WebSocket:
有新訊息 → 伺服器立即推送
→ 沒有浪費
→ 延遲 < 100ms ✅

2. 線上遊戲

場景:多人線上遊戲

需求:
- 玩家位置即時更新
- 低延遲(< 50ms)
- 高頻率更新(每秒 60 次)

HTTP 輪詢:
- 延遲高
- 開銷大(每次數 KB 標頭)

WebSocket:
- 延遲低(< 50ms)
- 開銷小(每次 2-10 bytes 標頭)
- 適合高頻率更新 ✅

3. 即時協作工具

場景:Google Docs、Notion、Figma

需求:
- 多人同時編輯
- 即時同步變更
- 衝突處理

WebSocket:
用戶 A 輸入 → 伺服器 → 用戶 B 立即看到
→ 即時協作 ✅

4. 金融交易平台

場景:股票交易、加密貨幣交易所

需求:
- 價格即時更新
- 低延遲(毫秒級)
- 高頻率(每秒數千筆)

WebSocket:
價格變動 → 伺服器推送給所有客戶端
→ 即時更新 ✅
→ 不需要輪詢

5. 即時通知系統

場景:社群媒體通知、系統監控

需求:
- 事件發生時立即通知
- 不能有延遲

WebSocket:
新通知 → 伺服器推送 → 用戶立即看到
→ 即時通知 ✅

6. 物聯網(IoT)

場景:智慧家居、感測器

需求:
- 即時監控
- 雙向控制

WebSocket:
感測器資料 → 伺服器 → 即時顯示
控制指令 → 伺服器 → 設備執行
→ 即時控制 ✅

❌ WebSocket 不適用場景

1. 簡單的 API 請求
   - 取得用戶資料
   - 上傳檔案
   → 用 HTTP REST API 即可

2. 靜態資源
   - HTML、CSS、JavaScript
   - 圖片、影片
   → 用 HTTP + CDN

3. 不需要即時更新
   - 部落格文章
   - 新聞網站
   → 用 HTTP

4. SEO 需求
   - WebSocket 不被搜尋引擎索引
   → 用 HTTP

5. 大量資料傳輸
   - 下載大檔案
   → 用 HTTP(支援斷點續傳)

原則:
只在需要「即時雙向通訊」時使用 WebSocket
其他情況用 HTTP 即可

🎓 面試常見問題

Q1:WebSocket 和 HTTP 有什麼差異?

A:通訊方式和連線模式不同

HTTP:
- 單向(客戶端請求 → 伺服器回應)
- 短連線(請求完畢就關閉)
- 半雙工(一次只能一方傳輸)
- 開銷大(每次完整標頭)
- 無狀態

WebSocket:
- 雙向(客戶端 ⇄ 伺服器)
- 長連線(持續保持)
- 全雙工(雙方同時傳輸)
- 開銷小(輕量標頭)
- 有狀態

比喻:
HTTP = 寄信(一問一答)
WebSocket = 打電話(即時對話)

使用場景:
HTTP:
✅ API 請求
✅ 網頁載入
✅ 檔案下載

WebSocket:
✅ 即時聊天
✅ 線上遊戲
✅ 即時協作

結論:
需要即時雙向通訊 → WebSocket
其他情況 → HTTP

Q2:WebSocket 如何建立連線?

A:透過 HTTP 升級機制

流程:

1. 客戶端發送 HTTP 升級請求
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket          ← 關鍵
Connection: Upgrade         ← 關鍵
Sec-WebSocket-Key: xxx      ← 隨機金鑰
Sec-WebSocket-Version: 13

2. 伺服器回應升級確認
HTTP/1.1 101 Switching Protocols  ← 切換協定
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: yyy         ← 驗證金鑰

3. 連線建立完成
→ 從 HTTP 升級為 WebSocket
→ 使用相同的 TCP 連線
→ 開始 WebSocket 通訊

關鍵:
- 使用 HTTP 的 101 狀態碼
- Upgrade 和 Connection 標頭
- Sec-WebSocket-Key 驗證
- 不需要重新建立 TCP 連線

優點:
✅ 相容現有基礎設施(HTTP)
✅ 可以穿過防火牆(使用 80/443 port)
✅ 支援 TLS 加密(wss://)

Q3:WebSocket 如何保持連線?

A:使用心跳(Ping/Pong)機制

問題:
長時間沒有資料傳輸
→ 中間的代理伺服器可能認為連線已斷
→ 關閉連線

解決:心跳檢測

伺服器定期發送 Ping:
每 30 秒發送一次 Ping 幀
[0x89][0x00]

客戶端回應 Pong:
收到 Ping 後立即回應 Pong 幀
[0x8A][0x00]

伺服器判斷:
如果 X 秒內沒收到 Pong
→ 認為連線已斷
→ 關閉連線

實作(JavaScript):
// 瀏覽器自動處理 Ping/Pong
// 無需手動實作

實作(Node.js 伺服器):
setInterval(() => {
    wss.clients.forEach((ws) => {
        if (ws.isAlive === false) {
            return ws.terminate(); // 關閉連線
        }

        ws.isAlive = false;
        ws.ping();  // 發送 Ping
    });
}, 30000);

wss.on('connection', (ws) => {
    ws.isAlive = true;

    ws.on('pong', () => {
        ws.isAlive = true;  // 收到 Pong
    });
});

優點:
✅ 檢測連線狀態
✅ 保持連線活躍
✅ 及時發現斷線

Q4:WebSocket 安全嗎?如何加密?

A:使用 WSS(WebSocket Secure)

WebSocket 安全問題:

1. 明文傳輸(ws://)
ws://example.com
→ 資料未加密
→ 可被竊聽 ❌

2. 跨站 WebSocket 劫持
惡意網站建立 WebSocket 連線
→ 竊取用戶資料

解決方案:

1. 使用 WSS(加密)
wss://example.com
→ 使用 TLS 加密
→ 等同於 HTTPS
→ 防止竊聽 ✅

建立 WSS 連線:
// 客戶端
const ws = new WebSocket('wss://example.com/chat');

// 伺服器(Node.js)
const https = require('https');
const WebSocket = require('ws');
const fs = require('fs');

const server = https.createServer({
    cert: fs.readFileSync('/path/to/cert.pem'),
    key: fs.readFileSync('/path/to/key.pem')
});

const wss = new WebSocket.Server({ server });

server.listen(443);

2. 驗證 Origin
防止跨站 WebSocket 劫持

伺服器檢查 Origin:
wss.on('connection', (ws, req) => {
    const origin = req.headers.origin;

    // 檢查 Origin
    if (origin !== 'https://example.com') {
        ws.close(1008, 'Invalid origin');
        return;
    }

    // 允許連線...
});

3. 使用 Token 驗證
連線時傳送認證 Token

客戶端:
const ws = new WebSocket('wss://example.com/chat?token=xxx');

伺服器:
wss.on('connection', (ws, req) => {
    const url = new URL(req.url, 'wss://example.com');
    const token = url.searchParams.get('token');

    if (!verifyToken(token)) {
        ws.close(1008, 'Invalid token');
        return;
    }

    // 允許連線...
});

4. 速率限制
防止 DDoS 攻擊

限制:
- 每個 IP 的連線數
- 每秒訊息數量

最佳實踐:
✅ 使用 WSS(加密)
✅ 驗證 Origin
✅ Token 認證
✅ 速率限制
✅ 輸入驗證

Q5:WebSocket 如何處理斷線重連?

A:客戶端實作自動重連機制

問題:
網路不穩定
→ WebSocket 連線可能斷開
→ 需要自動重連

實作(JavaScript):
class ReconnectingWebSocket {
    constructor(url) {
        this.url = url;
        this.reconnectInterval = 1000;  // 重連間隔(毫秒)
        this.maxReconnectInterval = 30000;  // 最大間隔
        this.reconnectDecay = 1.5;  // 遞增倍數
        this.timeoutInterval = 2000;  // 連線逾時
        this.shouldReconnect = true;

        this.connect();
    }

    connect() {
        this.ws = new WebSocket(this.url);

        // 連線成功
        this.ws.onopen = (event) => {
            console.log('WebSocket 連線成功');
            this.reconnectInterval = 1000;  // 重置重連間隔
            this.onopen && this.onopen(event);
        };

        // 接收訊息
        this.ws.onmessage = (event) => {
            this.onmessage && this.onmessage(event);
        };

        // 連線關閉
        this.ws.onclose = (event) => {
            console.log('WebSocket 連線關閉');
            this.onclose && this.onclose(event);

            if (this.shouldReconnect) {
                this.reconnect();
            }
        };

        // 錯誤
        this.ws.onerror = (error) => {
            console.error('WebSocket 錯誤');
            this.onerror && this.onerror(error);
            this.ws.close();
        };
    }

    reconnect() {
        console.log(`${this.reconnectInterval}ms 後重新連線...`);

        setTimeout(() => {
            console.log('嘗試重新連線...');
            this.connect();

            // 遞增重連間隔(指數退避)
            this.reconnectInterval = Math.min(
                this.reconnectInterval * this.reconnectDecay,
                this.maxReconnectInterval
            );
        }, this.reconnectInterval);
    }

    send(data) {
        if (this.ws.readyState === WebSocket.OPEN) {
            this.ws.send(data);
        } else {
            console.error('WebSocket 未連線');
        }
    }

    close() {
        this.shouldReconnect = false;
        this.ws.close();
    }
}

// 使用
const ws = new ReconnectingWebSocket('wss://example.com/chat');

ws.onopen = (event) => {
    console.log('連線開啟');
};

ws.onmessage = (event) => {
    console.log('收到訊息:', event.data);
};

ws.send('Hello!');

重連策略:

1. 固定間隔
   每次都等 1 秒再重連
   → 簡單但可能造成伺服器壓力

2. 指數退避(推薦)
   第 1 次:1 秒後重連
   第 2 次:1.5 秒後重連
   第 3 次:2.25 秒後重連
   ...
   最多:30 秒後重連
   → 減少伺服器壓力

3. 限制重連次數
   最多重連 10 次
   → 避免無限重連

4. 重連成功後重置
   重連成功 → 重置間隔為 1 秒
   → 下次斷線快速重連

✅ 重點回顧

WebSocket 定義:

  • 全雙工、雙向通訊協定
  • 客戶端 ⇄ 伺服器(同時通訊)

主要優勢:

  • ✅ 雙向通訊(伺服器可主動推送)
  • ✅ 低延遲(持久連線)
  • ✅ 低開銷(輕量標頭)
  • ✅ 即時性(適合即時應用)

連線建立:

  • 透過 HTTP 升級機制
  • 使用 101 Switching Protocols
  • Sec-WebSocket-Key 驗證

適用場景:

  • 即時聊天
  • 線上遊戲
  • 即時協作工具
  • 金融交易平台
  • 即時通知

不適用場景:

  • 簡單 API 請求
  • 靜態資源
  • SEO 需求

安全性:

  • 使用 WSS(WebSocket Secure)
  • 驗證 Origin
  • Token 認證
  • 速率限制

面試重點:

  • ✅ WebSocket vs HTTP 差異
  • ✅ 連線建立流程(HTTP 升級)
  • ✅ 心跳機制(Ping/Pong)
  • ✅ 安全性(WSS、Origin 驗證)
  • ✅ 斷線重連(指數退避)

記憶比喻:

  • HTTP = 寄信(一問一答)
  • WebSocket = 打電話(即時對話)

上一篇: 03-4. HTTPS 握手過程 下一篇: 04-2. WebSocket vs HTTP


最後更新:2025-01-06

0%