導語 對Websocket的基礎原理研究,并在nodejs的WebSocket庫中進行選型對比,選出最适合我們的庫。本文分為兩章,第一張對WebSocket基礎原理進行研究,第二章将從Nodejs庫中選出最适合的WebSocket庫。

WebSocket連接配接本質上是TCP連接配接,在網頁打開後通過http協定握手之後建立長連接配接。真正實作了Web的實時通信,使B/S模式具備了C/S模式的實時通信能力
分為三個階段:
第一階段:由用戶端發起的握手階段,握手後建立連接配接
第二階段:資料交換,用戶端與服務端可以互相主動發送消息
第三階段:關閉連接配接,可以由任意一端發起關閉的指令
HTTP request method 必須是GET,協定應不小于1.1
Upgrade,并且其值為 websocket;
Connection,并且其值為Upgrade;
Sec-WebSocket-Key,其值采用base64編碼的随機16位元組長的字元序列;
Origin,伺服器可以從Origin決定是否接受該WebSocket連接配接;
Sec-webSocket-Version,目前值必須是13;握手響應
首行傳回的是HTTP/1.1協定版本和狀态碼101,表示變換協定(Switching Protocol)
Upgrade,其值為 websocket;
Connection,其值為Upgrade;
Sec-WebSocket-Accept,加密處理後的握手Key消息體組成
WebSocket的消息并非沒有額外資訊,除了業務資料以外,消息體也包含一些額外資訊。隻不過相對http的頭會小很多,一般隻有6個bytes
FIN:1 bit
訓示這個是消息的最後片段。第一個片段可能也是最後的片段。
RSV1, RSV2, RSV3: 每個1 bit
必須是0,除非一個擴充協商為非零值定義含義。如果收到一個非零值且沒有協商的擴充定義這個非零值的含義,接收端點必須失敗WebSokcket連接配接。
Opcode: 4 bits
定義了“負載資料”的解釋。如果收到一個未知的操作碼,接收端點必須失敗WebSocket連接配接。定義了以下值。
%x0 代表一個繼續幀
%x1 代表一個文本幀
%x2 代表一個二進制幀
%x3-7 保留用于未來的非控制幀
%x8 代表連接配接關閉
%x9 代表ping
%xA 代表pong
%xB-F 保留用于未來的控制幀
Mask: 1 bit
定義是否“負載資料”是掩碼的。如果設定為1,一個掩碼鍵出現在masking-key,且這個是用于根據5.3節解掩碼(unmask)“負載資料”。從用戶端發送到伺服器的所有幀有這個位設定為1。
Payload length: 7 bits, 7+16 bits, 或者 7+64 bits
“負載資料”的長度,以位元組為機關:如果0-125,這是負載長度。如果126,之後的兩位元組解釋為一個16位的無符号整數是負載長度。如果127,之後的8位元組解釋為一個64位的無符号整數(最高有效位必須是0)是負載長度。多位元組長度數量以網絡位元組順序來表示。注意,在所有情況下,最小數量的位元組必須用于編碼長度,例如,一個124位元組長的字元串的長度不能被編碼為序列126,0,124。負載長度是“擴充資料”長度+“應用資料”長度。“擴充資料”長度可能是零,在這種情況下,負載長度是“應用資料”長度。
Masking-key: 0 or 4 bytes
用戶端發送到伺服器的所有幀通過一個包含在幀中的32位值來掩碼。如果mask位設定為1,則該字段存在,如果mask位設定為0,則該字段缺失。詳細資訊請參見5.3節 用戶端到伺服器掩碼。
Payload data: (x+y) bytes
“負載資料”定義為“擴充資料”連接配接“應用資料”。
Extension data: x bytes
“擴充資料”是0位元組除非已經協商了一個擴充。任何擴充必須指定“擴充資料”的長度,或長度是如何計算的,以及擴充如何使用必須在打開階段握手期間協商。 如果存在,“擴充資料”包含在總負載長度中。
Application data: y bytes
任意的“應用資料”,占用“擴充資料”之後幀的剩餘部分。“應用資料”的長度等于負載長度減去“擴充資料”長度。
FIN + RSV1 + RSV2 + RSV3 + Opcode + Mask + Payload length + Masking-key = 業務資料以外的消息大小
1bit + 1bit + 1bit + 1bit + 4bit + 1bit + 7bit + 4bytes = 6bytes
以發送JSON字元串 {“req”:”123”} 為例,字元串本身13 bytes
通過http發送的話,http消息總大小 523+13
通過WebSocket發送的話,消息總大小是 6+13
由于工作原因,主要用Nodejs進行開發,是以隻對比Nodejs實作的WebSocket庫
GitHub上面,用nodejs實作的WebSocket庫非常多,我挑選了幾個靠前的庫進行對比
websockets/ws
theturtle32/WebSocket-Node
faye/faye-websocket-node
socketio/socket.io本地Windows環境 Ajax vs WebSocket
在本地Windows環境,對比Ajax與WebSocket發送消息的耗時。可以看到WebSocket的耗時遠遠低于Ajax
在本地Windows環境,處理不同消息大小的耗時對比。
測試結果: websocket-node < faye < ws < socket.io
因為本地Windows環境與生産環境并不一樣,是以上面的資料僅作Windows環境參考。因為下面在生産環境進行對比後,資料會有較大差異
以下生産環境測試,都是在2G記憶體、10個ecu環境下進行的測試對比
這個測試與上一個Windows測試是一樣的,但結果完全不同。ws表現最好
測試結果:ws< socket.io < websocket-node < faye < ajax
使用同樣大小的消息,對服務發起大量的請求。測試服務的記憶體消耗。socket.io/ws/websocket-node 表現都不錯,比較穩定。faye表現最差,占用記憶體高。
測試結果:socket.io < ws < websocket-node < faye
使用同樣大小的消息,對服務發起大量的請求。測試服務的CPU占用情況。socket.io表現最差,CPU占比很高。
測試結果:websocket-node = faye < ws < socket.io
在2G記憶體的伺服器上,測試各個庫的最大連接配接數。最好的結果也是差異巨大。最好的ws是最差的socket.io的近三倍
測試結果:ws > websocket-node > faye > socket.io
websocket-node 在連接配接數超過140000的時候,連接配接速度比較慢。伺服器沒響應,但之前的連接配接不會斷開
而faye和ws在到極限的時候,會出現異常。所有連接配接會斷開
socket.io 連接配接在20000左右 的時候,就非常慢了
測試最大連接配接數的時候,同時監控了記憶體和CPU的波動。
在記憶體方面,ws的增長最為平緩,而socket.io早早的攀升到了極限最後挂掉了
測試結果:ws < websocket-node < faye < socket.io
在CPU方面,ws同樣保持穩定,占用比也非常低。
按第一得分4,第二得3分,第三得2分,第四得1分計算各個庫的得分情況
庫
得分
ws
21
websocket-node
17
faye
11
socket.io
ws表現最好簡單易用,連接配接數最大,記憶體和CPU控制的穩定。缺點是在到達最大連接配接數極限之後,會斷開所有連接配接