在上一篇文章中,我們瞭解到
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
技術,客戶端通過xhr
或jsonp
傳送訊息給服務端,並通過事件回撥來接收服務端訊息。
這種技術雖然也能保證雙向通訊,但是有一個不可避免的問題,就是效能問題。客戶端不斷向服務端傳送請求,如果客戶端併發數過大,無疑導致服務端壓力劇增。因此,websocket
就是解決這一痛點而誕生的。
這裡再延伸一些名詞:
- 長輪詢
客戶端向服務端傳送
xhr
請求,服務端接收並hold
該請求,直到有新訊息push
到客戶端,才會主動斷開該連線。然後,客戶端處理該response
後再向服務端發起新的請求。以此類推。
HTTP1.1
預設使用長連線,使用長連線的HTTP
協議,會在響應頭中加入下面這行資訊:Connection:keep-alive
- 短輪詢:
客戶端不管是否收到服務端的response
資料,都會定時想服務端傳送請求,查詢是否有資料更新。
- 長連線
指在一個
TCP
連線上可以傳送多個資料包,在TCP
連線保持期間,如果沒有資料包傳送,則雙方就需要傳送心跳包
來維持此連線。
連線過程: 建立連線——資料傳輸——...——(傳送心跳包,維持連線)——...——資料傳輸——關閉連線
- 短連線
指通訊雙方有資料互動時,建立一個
TCP
連線,資料傳送完成之後,則斷開此連線。
連線過程: 建立連線——資料傳輸——斷開連線——...——建立連線——資料傳輸——斷開連線
Tips
這裡有一個誤解,長連線和短連線的概念本質上指的是傳輸層的
TCP
連線,因為HTTP1.1
協議以後,連線預設都是長連線。沒有短連線說法(HTTP1.0
預設使用短連線),網上大多數指的長短連線實質上說的就是TCP
連線。http
使用長連線的好處: 當我們請求一個網頁資源的時候,會帶有很多js
、css
等資原始檔,如果使用的時短連線的話,就會開啟很多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: Upgrade
、Upgrade: websocket
,服務端識別該header之後,進行協議升級,使用Websocket
協議進行資料通訊。
引數說明
Request URL
請求服務端地址Request Method
請求方式 (支援get/post/option)Status Code
101 Switching Protocols
規範解釋: 當收到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.io
與engine.io
的一大區別在於,socket.io
並不直接提供連線功能,而是在engine.io
層提供。
socket.io
提供了一個房間(Namespace
)概念。當客戶端建立一個新的長連線時,就會分配一個新的Namespace
進行區分。
根據流程圖,可以看出:
- 建立長連線的方式有三種:
websocket
、xhr
、jsonp
。其中,後兩種使用長輪詢的方式進行模擬。 - 所謂的長輪詢是指,客戶端傳送一次
request
,當服務端有訊息推送時會push一條response
給客戶端。客戶端收到response
後,會再次傳送request
,重複上述過程,直到其中一端主動斷開連線為止。
// 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:Upgrade
、Upgrade:websocket
)等資訊,告訴服務端準備建立連線,此時,後端返回的response
資料。
資料格式如下:
0{"sid":"ab4507c4-d947-4deb-92e4-8a9e34a9f0b2","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":60000}
複製程式碼
- 0: 代表
open
標識 - sid: session id
- upgrades: 升級協議型別
- pingInterval:
ping
的間隔時長 - pingTimeout: 判斷連線超時時長
當客戶端收到響應之後,scoket.io
會根據當前客戶端環境是否支援Websocket
。如果支援,則建立一個websocket
連線,否則使用polling
(xhr
、jsonp
)長輪詢進行雙向資料通訊。
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
連線時強制輪詢週期。- 客戶端通過新的傳輸連線
- 客戶端傳送
2send
- 服務端接收併傳送
3probe
- 客戶端結束併傳送
5
- 服務端重新整理並關閉舊的傳輸連線並切換到新傳輸連線
7. socket.io 全家桶
區別