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 模式,而是進入一個長連線狀態:
- TCP 連線不中斷:只要沒有人主動關閉,這條管道會一直開著(不需反覆建立三向交握)。
- 極小開銷:後續傳輸的數據幀標頭通常只有 2-14 bytes,遠低於 HTTP。
- 低延遲推播:伺服器擁有主動權,不再需要 Client 反覆詢問(Polling)。
總結
WebSocket 的精髓在於「借用 HTTP 開門,交由 TCP 接管」。它完美解決了即時通訊的效率問題,這也是為什麼它能成為 WebRTC 信令與高性能同步系統的唯一首選。
下一章,我們將解開傳輸數據的真正祕密:數據幀 (Frame) 的掩碼與分段。
️ 進階挑戰
- 實作挑戰:嘗試修改文中的 Node.js 程式碼,讓它能解析並印出客戶端發送的所有 HTTP Headers。
- 深度思考:既然 WebSocket 這麼強,為什麼我們不把所有的通訊(如部落格靜態頁面)都換成它?(提示:快取能力、SEO 與伺服器資源消耗)。
延伸閱讀與資源
- RFC 6455:The WebSocket Protocol
- MDN Web Docs:WebSocket API