原文地址javascript
最近,業務中有使用到
socket.io
,進行客戶端與服務端的實時通訊。socket.io
提供的API
易上手,對新手友好,這就極大提升了開發者的效率。不過,期間也有遇到不少socket.io
中的坑,例如,中文亂碼問題,服務端NPE問題 等。有些涉及到底層的問題,就勢必要理解socket.io
設計原理,進行排查。因此,總結了一下相關的概念,方便從此更快定位問題。html
socket.io 官網java
socket.io
是基於 Websocket 的Client-Server 實時通訊庫。node
socket.io
底層是基於engine.io這個庫。git
所以,在介紹socket.io
以前,先簡單的介紹一些關於engine.io
相關的知識,方便深刻理解。github
依賴關係詳見:web
engine.io
爲 socket.io
提供跨瀏覽器/跨設備的雙向通訊的底層庫。engine.io
使用了 Websocket
和 XHR
方式封裝了一套 socket
協議。 在低版本的瀏覽器中,不支持Websocket
,爲了兼容使用長輪詢(polling)替代。算法
相關源碼以下:json
Client瀏覽器
import eio from './engine.io-client'
// 建立一個socket長鏈接
let socket = new eio.Socket('ws://localhost');
複製代碼
根據流程圖,能夠看出:
websocket
、xhr
、jsonp
。其中,後兩種使用長輪詢的方式進行模擬。request
,當服務端有消息推送時會push一條response
給客戶端。客戶端收到response
後,會再次發送request
,重複上述過程,直到其中一端主動斷開鏈接爲止。下圖是創建成功的socket長鏈接:
參數說明
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
服務端 hostnameOrigin
客戶端 hostname:portSec-WebSocket-Extensions
客戶端向服務端發起請求擴展列表(list),供服務端選擇並在響應中返回Sec-WebSocket-Key
祕鑰的值是經過規範中定義的算法進行計算得出,所以是不安全的,可是能夠阻止一些誤操做的websocket請求。Sec-WebSocket-Protocol
指定有限使用的Websocket協議,能夠是一個協議列表(list)。服務端在response
中返回列表中支持的第一個值。Sec-WebSocket-Version
指定通訊時使用的Websocket協議版本。最新版本:13,歷史版本Upgrade
通知服務端,指定升級協議類型爲websocket
engine.io
的url創建通訊鏈接response
中返回一個open
的packet,JSON編碼數據格式以下: 1. sid: session id (String
) 2. upgrades: 傳輸類型(Array
) 3. pingTimeout: 服務端通訊超時配置,客戶端用於超時檢測(Number
) 4. pingInterval: 服務端通訊定時器配置,客戶端用於超時檢測(Number
)ping
packets時,服務端必須定時發送pong
packetsmessage
pakcetsPolling
傳輸能夠發送一個close
pakcet來關閉socket
,由於他們可能會一直opening
或closing
engine.io
URL的組成以下:
/engine.io/[?\<query string>]
polling
,websocket
> 2. j: 若是傳輸方式爲polling
,可是須要JSONP
的響應,則j
必須設置爲JSONP
響應的index
3. sid: 若是客戶端已經分配了一個session id
,則sid
必須包含在query string中 4. b64: 若是客戶端不支持XHR2
,則必須在query string中加上b64=1
標識,以通知服務端全部二進制數據應該進行base64編碼engine.io
有兩種編碼方式:
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
Payload
Payload
是綁定在一塊兒的一系列編碼分組。格式以下:
<length1>:<packet1>[<length2>:<packet2>[...]]
packet
的字符長度engine.io
支持三種傳輸方式:
websocket
polling
jsonp
長輪詢傳輸包括客戶端向服務器重複發出GET請求以獲取數據,以及具備從客戶端到服務器的有效負載的POST請求以發送數據。
XHR 服務端必須支持CORS
JSONP 服務器實現必須使用有效的JavaScript進行響應。 URL包含必須在響應中使用的query string參數j
。 j
是整數。
JSONP包的格式:
___eio[<
j
> ](" <encoded payload> ");
例如:
___eio[4]("packet data");