跳至主要內容
Skip to content

WebSocket 揭秘:從 HTTP Upgrade 到持久連線

在 WebRTC 的世界裡,我們經常提到「信令伺服器 (Signaling Server)」來交換資訊。而信令伺服器最常見的底層技術就是 WebSocket

如果說 HTTP 是「你問我答」的單向溝通,那麼 WebSocket 就是「雙向通報」的電話線。本章我們將拆解這條電話線是如何被接通的。


一、 為什麼需要 WebSocket?

在傳統的 HTTP/1.1 協定中:

  • 請求是單向的:只有客戶端(Client)能主動發起連線。
  • 標頭(Header)沈重:每次通訊都要攜送大量的 Cookie、UA 等資訊。
  • 即時性低:雖然有 Long Polling(長輪詢),但效率極低。

WebSocket 的出現,是為了提供一種 全雙工(Full-Duplex)低延遲低開銷 的通訊方式。


二、 握手流程:HTTP Upgrade

WebSocket 並不是一個全新的協定發明,它是基於 HTTP 協定進行「升級」的。

1. 客戶端請求 (Client Request)

當你在 JavaScript 執行 new WebSocket('ws://...') 時,瀏覽器會先發送一個標準的 HTTP GET 請求:

http
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
  • Upgrade: websocket:告訴伺服器「我想把這個連線升級成 WebSocket」。
  • Connection: Upgrade:表示這是個升級請求。
  • Sec-WebSocket-Key:這是一個 base64 編碼的隨機字串,用於驗證伺服器是否支援 WebSocket。

2. 伺服器回應 (Server Response)

如果伺服器理解並支援 WebSocket,它會回傳 HTTP 101 Switching Protocols

http
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
  • 101 Switching Protocols:正式宣告連線已成功升級。
  • Sec-WebSocket-Accept:伺服器將 Key 經過特定的算法運算後回傳,雙方從此建立信任。

三、 手寫原生握手:Node.js 實驗室

為了徹底理解協定,我們跳過任何庫(Library),直接用 Node.js 的 net 模組來攔截這個「升級請求」。

這段程式碼展示了伺服器如何接收標頭,並算出 Sec-WebSocket-Accept 回傳:

javascript
const net = require("net");
const crypto = require("crypto");

const server = net.createServer((socket) => {
  socket.once("data", (buffer) => {
    const request = buffer.toString();

    // 1. 檢查是否有 Upgrade: websocket 標頭
    if (request.includes("Upgrade: websocket")) {
      const key = request.match(/Sec-WebSocket-Key: (.+)/)[1].trim();

      // 2. 握手演算法:Key + 特定 GUID -> SHA1 -> Base64
      const MAGIC_STRING = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
      const acceptKey = crypto
        .createHash("sha1")
        .update(key + MAGIC_STRING)
        .digest("base64");

      // 3. 回傳 101 Switching Protocols
      const response = [
        "HTTP/1.1 101 Switching Protocols",
        "Upgrade: websocket",
        "Connection: Upgrade",
        `Sec-WebSocket-Accept: ${acceptKey}`,
        "",
        "",
      ].join("\r\n");

      socket.write(response);
      console.log("✅ WebSocket 握手成功!");

      // 此時 TCP 連線已接管,雙方可以開始傳輸數據幀...
    }
  });
});

server.listen(8080, () => console.log("🚀 原生握手伺服器運行在 8080"));

四、 運作機制圖解


五、 核心觀念:持久連線 (Persistent Connection)

一旦 101 狀態碼確立,客戶端與伺服器之間就不再使用 HTTP 的 Request/Response 模式,而是進入一個長連線狀態:

  1. TCP 連線不中斷:只要沒有人主動關閉,這條管道會一直開著(不需反覆建立三向交握)。
  2. 極小開銷:後續傳輸的數據幀標頭通常只有 2-14 bytes,遠低於 HTTP。
  3. 低延遲推播:伺服器擁有主動權,不再需要 Client 反覆詢問(Polling)。

總結

WebSocket 的精髓在於「借用 HTTP 開門,交由 TCP 接管」。它完美解決了即時通訊的效率問題,這也是為什麼它能成為 WebRTC 信令與高性能同步系統的唯一首選。

下一章,我們將解開傳輸數據的真正祕密:數據幀 (Frame) 的掩碼與分段。


️ 進階挑戰

  1. 實作挑戰:嘗試修改文中的 Node.js 程式碼,讓它能解析並印出客戶端發送的所有 HTTP Headers。
  2. 深度思考:既然 WebSocket 這麼強,為什麼我們不把所有的通訊(如部落格靜態頁面)都換成它?(提示:快取能力、SEO 與伺服器資源消耗)。

延伸閱讀與資源