在上一篇文章中,咱們瞭解到
socket.io
是 基於engine.io
進行封裝的庫。 因此對engine.io
不清楚的童鞋能夠點擊進行了解: engine.io 詳解javascript
socket.io
是基於 Websocket 的Client-Server 實時通訊庫。 socket.io
底層使用engine.io
封裝了一層協議。css
二者的依賴關係可參考: package.jsonhtml
Websocket 定義
參考規範 rfc6455java
規範解釋 Websocket
是一種提供客戶端(提供不可靠祕鑰)與服務端(校驗經過該祕鑰)進行雙向通訊的協議。git
在沒有websocket
協議以前,要提供客戶端與服務端實時雙向推送消息,就會使用polling
技術,客戶端經過xhr
或jsonp
發送消息給服務端,並經過事件回調來接收服務端消息。github
這種技術雖然也能保證雙向通訊,可是有一個不可避免的問題,就是性能問題。客戶端不斷向服務端發送請求,若是客戶端併發數過大,無疑致使服務端壓力劇增。所以,websocket
就是解決這一痛點而誕生的。web
這裏再延伸一些名詞:redis
xhr
請求,服務端接收並hold
該請求,直到有新消息push
到客戶端,纔會主動斷開該鏈接。而後,客戶端處理該response
後再向服務端發起新的請求。以此類推。
HTTP1.1
默認使用長鏈接,使用長鏈接的HTTP
協議,會在響應頭中加入下面這行信息:Connection:keep-alive
算法
客戶端不論是否收到服務端的response
數據,都會定時想服務端發送請求,查詢是否有數據更新。json
TCP
鏈接上能夠發送多個數據包,在TCP
鏈接保持期間,若是沒有數據包發送,則雙方就須要發送心跳包
來維持此鏈接。鏈接過程: 創建鏈接——數據傳輸——...——(發送心跳包,維持鏈接)——...——數據傳輸——關閉鏈接
TCP
鏈接,數據發送完成以後,則斷開此鏈接。鏈接過程: 創建鏈接——數據傳輸——斷開鏈接——...——創建鏈接——數據傳輸——斷開鏈接
Tips
這裏有一個誤解,長鏈接和短鏈接的概念本質上指的是傳輸層的
TCP
鏈接,由於HTTP1.1
協議之後,鏈接默認都是長鏈接。沒有短鏈接說法(HTTP1.0
默認使用短鏈接),網上大多數指的長短鏈接實質上說的就是TCP
鏈接。http
使用長鏈接的好處: 當咱們請求一個網頁資源的時候,會帶有不少js
、css
等資源文件,若是使用的時短鏈接的話,就會打開不少tcp
鏈接,若是客戶端請求數過大,致使tcp
鏈接數量過多,對服務端形成壓力也就可想而知了。
Tips
單工、半雙工和全雙工 這三者都是創建在
TCP
協議(傳輸層上)的概念,不要與應用層進行混淆。
Websocket
協議也是基於TCP
協議的,是一種雙全工通訊技術、複用HTTP
握手通道。
Websocket
默認使用請求協議爲:ws://
,默認端口:80
。對TLS
加密請求協議爲:wss://
,端口:443
。
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
數據格式定義參考:規範 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爲了確保客戶端與服務端的長鏈接正常,有時即便客戶端鏈接中斷,可是服務端未觸發onclose
事件,這就有可能致使無效鏈接佔用。因此須要一種機制,確保兩端的鏈接處於正常狀態,心跳檢測就是這種機制。客戶端每隔一段時間,會向服務端發送心跳(數據包),服務端也會返回response
進行反饋鏈接正常。
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
)功能。
當使用
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}
複製代碼
open
標識ping
的間隔時長當客戶端收到響應以後,scoket.io
會根據當前客戶端環境是否支持Websocket
。若是支持,則創建一個websocket
鏈接,不然使用polling
(xhr
、jsonp
)長輪詢進行雙向數據通訊。
socket.io
協議中定義的數據格式稱之爲Pakcet
,每個Packet
都含有nsp
的對象值。
Packet
編碼包能夠是UTF8或二進制數據,編碼格式以下:
<包類型id>[<data>]
例如:
2probe
包類型id(packet type id)是一個整型,具體含義以下:
pong
packet進行應答ping
packetdata
// 服務端發送
send('4HelloWorld')
// 客戶端接收數據並調用回調
socket.on('message', function (data) { console.log(data); });
// 客戶端發送
send('4HelloWorld')
// 服務端接收數據並調用回調
socket.on('message', function (data) { console.log(data); })
複製代碼
engine.io
切換傳輸以前,它會測試服務器和客戶端是否能夠經過此傳輸進行通訊。若是此測試成功,客戶端將發送升級數據包,請求服務器刷新舊傳輸上的緩存並切換到新傳輸。noop
packet。主要用於在收到傳入的websocket
鏈接時強制輪詢週期。
2send
3probe
5
區別