engine.io 原理詳解

栗子喲發表於2019-01-26

原文地址

最近,業務中有使用到 socket.io,進行客戶端與服務端的實時通訊。socket.io提供的API易上手,對新手友好,這就極大提高了開發者的效率。不過,期間也有遇到很多socket.io中的坑,例如,中文亂碼問題服務端NPE問題 等。有些涉及到底層的問題,就勢必要理解socket.io設計原理,進行排查。所以,總結了一下相關的概念,方便今後更快定位問題。

socket.io 官網

1.概述

socket.io 是基於 Websocket 的Client-Server 實時通訊庫。

socket.io 底層是基於engine.io這個庫。

因此,在介紹socket.io之前,先簡單的介紹一些關於engine.io相關的知識,方便深入理解。

依賴關係詳見:

2. Engine.io 基礎

engine.iosocket.io 提供跨瀏覽器/跨裝置的雙向通訊的底層庫。engine.io 使用了 WebsocketXHR 方式封裝了一套 socket 協議。 在低版本的瀏覽器中,不支援Websocket,為了相容使用長輪詢(polling)替代。

相關原始碼如下:

3. Engine.io 工作流程

Client

import eio from './engine.io-client'

// 建立一個socket長連線
let socket = new eio.Socket('ws://localhost');
複製程式碼

在這裡插入圖片描述

根據流程圖,可以看出:

  • 建立長連線的方式有三種: websocketxhrjsonp。其中,後兩種使用長輪詢的方式進行模擬。
  • 所謂的長輪詢是指,客戶端傳送一次request,當服務端有訊息推送時會push一條response給客戶端。客戶端收到response後,會再次傳送request,重複上述過程,直到其中一端主動斷開連線為止。

4. webSocket 請求頭資訊

下圖是建立成功的socket長連線:

在這裡插入圖片描述

引數說明

  • Request URL 請求服務端地址
  • Request Method 請求方式 (支援get/post/option)
  • Status Code 101 Switching Protocols

RFC 7231 規範定義

規範解釋: 當收到101請求狀態碼時,表明服務端理解並同意客戶端請求,更改Upgrade header欄位。服務端也必須在response中,生成對應的Upgrade值。

  • Connection 設定upgrade header,通知服務端,該request型別需要進行升級為websocketupgrade_mechanism 規範
  • Host 服務端 hostname
  • Origin 客戶端 hostname:port
  • Sec-WebSocket-Extensions 客戶端向服務端發起請求擴充套件列表(list),供服務端選擇並在響應中返回
  • Sec-WebSocket-Key 祕鑰的值是通過規範中定義的演算法進行計算得出,因此是不安全的,但是可以阻止一些誤操作的websocket請求。
  • Sec-WebSocket-Protocol 指定有限使用的Websocket協議,可以是一個協議列表(list)。服務端在response中返回列表中支援的第一個值。
  • Sec-WebSocket-Version 指定通訊時使用的Websocket協議版本。最新版本:13,歷史版本
  • Upgrade 通知服務端,指定升級協議型別為websocket

5. engine.io 協議解析

  • 客戶端通過engine.io的url建立通訊連線
  • 服務端在response中返回一個open的packet,JSON編碼資料格式如下: 1. sid: session id (String) 2. upgrades: 傳輸型別(Array) 3. pingTimeout: 服務端通訊超時配置,客戶端用於超時檢測(Number) 4. pingInterval: 服務端通訊定時器配置,客戶端用於超時檢測(Number)
  • 收到客戶端傳送的ping packets時,服務端必須定時傳送pong packets
  • 客戶端與服務端可以隨意交換message pakcets
  • Polling傳輸可以傳送一個close pakcet來關閉socket,因為他們可能會一直openingclosing

6. URLs

engine.io URL的組成如下:

/engine.io/[?\<query string>]

  • engine.io: 只允許由庫自身進行修改
  • query string: 可選欄位,並提供了四個保留欄位: 1. transport: 宣告傳輸方式,可選值:<pollingwebsocket> 2. j: 如果傳輸方式為polling,但是需要JSONP的響應,則j必須設定為JSONP響應的index 3. sid: 如果客戶端已經分配了一個session id,則sid必須包含在query string中 4. b64: 如果客戶端不支援XHR2,則必須在query string中加上b64=1標識,以通知服務端所有二進位制資料應該進行base64編碼

7. 編碼方式

engine.io有兩種編碼方式:

  • packet
  • payload

Packet

編碼包可以是UTF8二進位制資料,編碼格式如下:

<包型別id>[<data>]

例如:

2probe

包型別id(packet type id)是一個整型,具體含義如下:

  • 0 open 當開啟一個新傳輸時,服務端檢測併傳送
  • 1 close 請求關閉傳輸,但不是主動斷開連線
  • 2 ping 客戶端發出,服務端應該返回包含相同資料的pong packet進行應答
  • 3 pong 服務端發出,用以響應客戶端的ping packet
  • 4 message 真實資料,客戶端和服務端應該呼叫回撥中的data
// 服務端傳送 
send('4HelloWorld')
// 客戶端接收資料並呼叫回撥 
socket.on('message', function (data) { console.log(data); });
// 客戶端傳送 
send('4HelloWorld')
// 服務端接收資料並呼叫回撥 
socket.on('message', function (data) { console.log(data); })
複製程式碼
  • 5 upgradeengine.io切換傳輸之前,它會測試伺服器和客戶端是否可以通過此傳輸進行通訊。如果此測試成功,客戶端將傳送升級資料包,請求伺服器重新整理舊傳輸上的快取並切換到新傳輸。
  • 6 noop noop packet。主要用於在收到傳入的websocket連線時強制輪詢週期。
    1. 客戶端通過新的傳輸連線
    2. 客戶端傳送 2send
    3. 服務端接收併傳送 3probe
    4. 客戶端結束併傳送 5
    5. 服務端重新整理並關閉舊的傳輸連線並切換到新傳輸連線

Payload

Payload是繫結在一起的一系列編碼分組。格式如下:

<length1>:<packet1>[<length2>:<packet2>[...]]

  • length: 表示packet的字元長度
  • packet: 真實資料包

Transports

engine.io支援三種傳輸方式:

  • websocket
  • polling
    • jsonp
    • xhr

Polling

長輪詢傳輸包括客戶端向伺服器重複發出GET請求以獲取資料,以及具有從客戶端到伺服器的有效負載的POST請求以傳送資料。

XHR 服務端必須支援CORS

JSONP 伺服器實現必須使用有效的JavaScript進行響應。 URL包含必須在響應中使用的query string引數jj是整數。

JSONP包的格式:

___eio[<j> ](" <encoded payload> ");

例如:

___eio[4]("packet data");

原文地址

相關文章