socket.io 原理詳解

socket.io 原理詳解

上一篇文章中,我們瞭解到 socket.io 是 基於engine.io 進行封裝的庫。
所以對engine.io不清楚的童鞋可以點擊進行了解: engine.io 詳解

1.概述


socket.io 是基於 Websocket 的Client-Server 實時通信庫。
socket.io 底層使用engine.io 封裝了一層協議。

兩者的依賴關係可參考: package.json

2. WebSocket 簡介


Websocket 定義 參考規範 rfc6455

規範解釋
Websocket 是一種提供客戶端(提供不可靠祕鑰)與服務端(校驗通過該祕鑰)進行雙向通信的協議。

在沒有websocket協議之前,要提供客戶端與服務端實時雙向推送消息,就會使用polling技術,客戶端通過xhrjsonp 發送消息給服務端,並通過事件回調來接收服務端消息。

這種技術雖然也能保證雙向通信,但是有一個不可避免的問題,就是性能問題。客戶端不斷向服務端發送請求,如果客戶端併發數過大,無疑導致服務端壓力劇增。因此,websocket就是解決這一痛點而誕生的。

這裏再延伸一些名詞:

  • 長輪詢
    客戶端向服務端發送xhr請求,服務端接收並hold該請求,直到有新消息push到客戶端,纔會主動斷開該連接。然後,客戶端處理該response後再向服務端發起新的請求。以此類推。

HTTP1.1默認使用長連接,使用長連接的HTTP協議,會在響應頭中加入下面這行信息: Connection:keep-alive

  • 短輪詢:

客戶端不管是否收到服務端的response數據,都會定時想服務端發送請求,查詢是否有數據更新。

  • 長連接
    指在一個TCP連接上可以發送多個數據包,在TCP連接保持期間,如果沒有數據包發送,則雙方就需要發送心跳包來維持此連接。

連接過程: 建立連接——數據傳輸——…——(發送心跳包,維持連接)——…——數據傳輸——關閉連接

  • 短連接
    指通信雙方有數據交互時,建立一個TCP連接,數據發送完成之後,則斷開此連接。

連接過程: 建立連接——數據傳輸——斷開連接——…——建立連接——數據傳輸——斷開連接

Tips

這裏有一個誤解,長連接和短連接的概念本質上指的是傳輸層的TCP連接,因爲HTTP1.1協議以後,連接默認都是長連接。沒有短連接說法(HTTP1.0默認使用短連接),網上大多數指的長短連接實質上說的就是TCP連接。
http使用長連接的好處: 當我們請求一個網頁資源的時候,會帶有很多jscss等資源文件,如果使用的時短連接的話,就會打開很多tcp連接,如果客戶端請求數過大,導致tcp連接數量過多,對服務端造成壓力也就可想而知了。

  • 單工
    數據傳輸的方向唯一,只能由發送方向接收方的單一固定方向傳輸數據。
  • 半雙工
    即通信雙方既是接收方也是發送方,不過,在某一時刻只能允許向一個方向傳輸數據。
  • 全雙工:
    即通信雙方既是接收方也是發送方,兩端設備可以同時發送和接收數據。

Tips

單工半雙工全雙工 這三者都是建立在 TCP協議(傳輸層上)的概念,不要與應用層進行混淆。

3. 什麼是Websocket


Websocket 協議也是基於TCP協議的,是一種雙全工通信技術、複用HTTP握手通道。

Websocket默認使用請求協議爲:ws://,默認端口:80。對TLS加密請求協議爲:wss://,端口:443

3.1 特點

  • 支持瀏覽器/Nodejs環境
  • 支持雙向通信
  • API簡單易用
  • 支持二進制傳輸
  • 減少傳輸數據量

3.2 建立連接過程

Websocket複用了HTTP的握手通道。指的是,客戶端發送HTTP請求,並在請求頭中帶上Connection: UpgradeUpgrade: websocket,服務端識別該header之後,進行協議升級,使用Websocket協議進行數據通信。

在這裏插入圖片描述

參數說明

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

RFC 7231 規範定義

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

  • Connection 設置upgrade header,通知服務端,該request類型需要進行升級爲websocket
    upgrade_mechanism 規範

  • Host 服務端 hostname

  • Origin 客戶端 hostname:port

  • Sec-WebSocket-Extensions 客戶端向服務端發起請求擴展列表(list),供服務端選擇並在響應中返回

  • Sec-WebSocket-Key 祕鑰的值是通過規範中定義的算法進行計算得出,因此是不安全的,但是可以阻止一些誤操作的websocket請求。

  • Sec-WebSocket-Accept
    計算公式:
    1. 獲取客戶端請求header的值: Sec-WebSocket-Key
    2. 使用魔數magic = ‘258EAFA5-E914-47DA-95CA-C5AB0DC85B11’
    3. 通過SHA1進行加密計算, sha1(Sec-WebSocket-Key + magic)
    4. 將值轉換爲base64

  • Sec-WebSocket-Protocol 指定有限使用的Websocket協議,可以是一個協議列表(list)。服務端在response中返回列表中支持的第一個值。

  • Sec-WebSocket-Version 指定通信時使用的Websocket協議版本。最新版本:13,歷史版本

  • Upgrade 通知服務端,指定升級協議類型爲websocket

3.3 數據幀格式

數據格式定義參考:規範 RFC6455

  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,表示這是message的最終片段(fragment),如果爲0,表示這是一個message的第一個片段。
  • RSV1, RSV2, RSV3: 各佔1 bit
    一般默認值是0,除非協商擴展,爲非零值進行定義,否則收到非零值,並且沒有進行協商擴展定義,則websocket連接失敗。
  • Opcode: 4 bits
    根據操作碼(Opcode),解析有效載荷數據(Payload data).如果接受到未定義操作碼,則應該斷開websocket連接。
  • Mask: 1 bit
    定義是否需要的載荷數據(``Payload data),進行掩碼操作。如果設置值爲1,那麼在Masking-key中會定義一個掩碼key,並用這個key對載荷數據進行反掩碼(unmask)操作。所有從客戶端發送到服務端的數據幀(frame),mask都被設置爲1.
  • Payload length: 7 bits, 7+16 bits, or 7+64 bits
    載荷數據的長度。
  • Masking-key: 0 or 4 bytes
    所有從客戶端傳送到服務端的數據幀,數據載荷都進行了掩碼操作,Mask爲1,且攜帶了4字節的Masking-key。如果Mask爲0,則沒有Masking-key。
  • Payload data: (x+y) bytes

3.4 心跳檢測

爲了確保客戶端與服務端的長連接正常,有時即使客戶端連接中斷,但是服務端未觸發onclose事件,這就有可能導致無效連接佔用。所以需要一種機制,確保兩端的連接處於正常狀態,心跳檢測就是這種機制。客戶端每隔一段時間,會向服務端發送心跳(數據包),服務端也會返回response進行反饋連接正常。

4. socket.io 簡介


socket.ioengine.io的一大區別在於,socket.io並不直接提供連接功能,而是在engine.io層提供。

socket.io提供了一個房間(Namespace)概念。當客戶端創建一個新的長連接時,就會分配一個新的Namespace進行區分。

// lookup 源碼
var parsed = url(uri)
var source = parsed.source
var id = parsed.id
var path = parsed.path
// 查找相同房間
var sameNamespace = cache[id] && path in cache[id].nsps
// 如果房間號已存在,創建新連接
var newConnection = sameNamespace
// ...

socket.io也提供支持多路複用(built-in multiplexing)方式,這表明每一個數據包(Packet)都始終屬於給定的namespace,並有path進行標識(例如: /xxxx)

socket.io可以在 open 之前,emit 消息,並且該消息會在 open 之後發出。而engine.io必須等到open 之後,才能 send消息。

socket.io也支持斷網重連(reconnection)功能。

5. socket.io 工作流程

當使用socket.io創建一個長連接時,到底發生了什麼呢?下面我們就來進入本文的正體:

const socket = io('http://localhost', {
  path: '/myownpath'
});

首先,socket.io通過一個http請求,並且該請求頭中帶有升級協議(Connection:UpgradeUpgrade:websocket)等信息,告訴服務端準備建立連接,此時,後端返回的response數據。
數據格式如下:

0{"sid":"ab4507c4-d947-4deb-92e4-8a9e34a9f0b2","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":60000}
  • 0: 代表open標識
  • sid: session id
  • upgrades: 升級協議類型
  • pingIntervalping的間隔時長
  • pingTimeout: 判斷連接超時時長

當客戶端收到響應之後,scoket.io會根據當前客戶端環境是否支持Websocket。如果支持,則建立一個websocket連接,否則使用polling(xhrjsonp)長輪詢進行雙向數據通信

6. socket.io 協議解析

socket.io協議中定義的數據格式稱之爲Pakcet ,每一個Packet都含有nsp的對象值。

在這裏插入圖片描述

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 upgrade
    engine.io切換傳輸之前,它會測試服務器和客戶端是否可以通過此傳輸進行通信。如果此測試成功,客戶端將發送升級數據包,請求服務器刷新舊傳輸上的緩存並切換到新傳輸。
  • 6 noop
    noop packet。主要用於在收到傳入的websocket連接時強制輪詢週期。
    1. 客戶端通過新的傳輸連接
    2. 客戶端發送 2send
    3. 服務端接收併發送 3probe
    4. 客戶端結束併發送 5
    5. 服務端刷新並關閉舊的傳輸連接並切換到新傳輸連接

7. socket.io 全家桶


在這裏插入圖片描述

區別

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章