運作原理
Socket.IO 伺服器 (Node.js) 和 Socket.IO 用戶端 (瀏覽器、Node.js 或 其他程式語言) 之間的雙向通道會在可行的情況下透過 WebSocket 連線 建立,並會使用 HTTP 長輪詢作為備援。
Socket.IO 程式碼庫分為兩個不同的層級
- 低階管道:我們稱之為 Engine.IO,Socket.IO 內部的引擎
- 高階 API:Socket.IO 本身
Engine.IO
Engine.IO 負責建立伺服器和用戶端之間的低階連線。它會處理
Engine.IO 協定的詳細版本可以在 這裡 找到。
參考實作的原始碼 (以 TypeScript 編寫) 可以在此找到
- 伺服器:https://github.com/socketio/engine.io
- 用戶端:https://github.com/socketio/engine.io-client
- 解析器:https://github.com/socketio/engine.io-parser
傳輸
目前已實作兩種傳輸
HTTP 長輪詢
HTTP 長輪詢傳輸(也簡稱為「輪詢」)包含連續的 HTTP 要求
- 長執行
GET
要求,用於從伺服器接收資料 - 短執行
POST
要求,用於將資料傳送至伺服器
由於傳輸的性質,連續的發射可能會串接並在同一個 HTTP 要求中傳送。
WebSocket
WebSocket 傳輸包含一個 WebSocket 連線,它提供伺服器和用戶端之間的雙向低延遲通訊管道。
由於傳輸的性質,每個發射都會在自己的 WebSocket 框架中傳送(有些發射甚至可能產生兩個不同的 WebSocket 框架,更多資訊 在此)。
交握
在 Engine.IO 連線開始時,伺服器會傳送一些資訊
{
"sid": "FSDjX-WRwSA4zTZMALqx",
"upgrades": ["websocket"],
"pingInterval": 25000,
"pingTimeout": 20000
}
sid
是工作階段的 ID,它必須包含在所有後續 HTTP 要求的sid
查詢參數中upgrades
陣列包含伺服器支援的所有「較佳」傳輸清單pingInterval
和pingTimeout
值用於心跳機制
升級機制
預設情況下,用戶端會透過 HTTP 長輪詢傳輸建立連線。
但是,為什麼呢?
雖然 WebSocket 明顯是建立雙向通訊的最佳方式,但經驗顯示,由於公司代理伺服器、個人防火牆、防毒軟體等因素,並非總是都能建立 WebSocket 連線
從使用者的角度來看,不成功的 WebSocket 連線可能會導致長達 10 秒的等待時間,才能讓即時應用程式開始交換資料。這會明顯損害使用者體驗。
總而言之,Engine.IO 首先注重可靠性與使用者體驗,其次才是邊際潛在 UX 改進與提升伺服器效能。
要升級,客戶端將會
- 確保其傳出緩衝區為空
- 將目前的傳輸設為唯讀模式
- 嘗試與其他傳輸建立連線
- 如果成功,則關閉第一個傳輸
您可以在瀏覽器的網路監視器中查看
- 握手(包含後續要求中使用的會話 ID,在此為
zBjrh...AAAK
) - 傳送資料(HTTP 長輪詢)
- 接收資料(HTTP 長輪詢)
- 升級(WebSocket)
- 接收資料(HTTP 長輪詢,一旦在 4. 中成功建立 WebSocket 連線,便會關閉)
中斷偵測
Engine.IO 連線在下列情況下會被視為已關閉
- 一個 HTTP 要求(GET 或 POST)失敗(例如,當伺服器關閉時)
- WebSocket 連線關閉(例如,當使用者關閉瀏覽器中的分頁時)
- 在伺服器端或客戶端呼叫
socket.disconnect()
還有一個心跳機制,用於檢查伺服器與客戶端之間的連線是否仍然正常運作
在特定間隔(握手時傳送的 pingInterval
值),伺服器會傳送 PING 封包,而客戶端有幾秒鐘(pingTimeout
值)可以傳送 PONG 封包回應。如果伺服器沒有收到 PONG 封包回應,它會認為連線已關閉。反之,如果客戶端在 pingInterval + pingTimeout
內沒有收到 PING 封包,它會認為連線已關閉。
Socket.IO
Socket.IO 提供一些 Engine.IO 連線沒有的額外功能
可在 這裡 找到 Socket.IO 通訊協定的詳細版本。
參考實作的原始碼 (以 TypeScript 編寫) 可以在此找到