簡介
我們知道WebSocket是建立在TCP協議基礎上的一種網路協議,用來進行客戶端和伺服器端的實時通訊。非常的好用。最簡單的使用WebSocket的辦法就是直接使用瀏覽器的API和伺服器端進行通訊。
本文將會深入分析WebSocket的訊息互動格式,讓大家得以明白,websocket到底是怎麼工作的。
WebSocket的握手流程
我們知道WebSocket為了相容HTTP協議,是在HTTP協議的基礎之上進行升級得到的。在客戶端和伺服器端建立HTTP連線之後,客戶端會向伺服器端傳送一個升級到webSocket的協議,如下所示:
GET /chat HTTP/1.1
Host: example.com:8000
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
注意,這裡的HTTP版本必須是1.1以上。HTTP的請求方法必須是GET
通過設定Upgrade和Connection這兩個header,表示我們準備升級到webSocket了。
除了這裡列的屬性之外,其他的HTTP自帶的header屬性都是可以接受的。
這裡還有兩個比較特別的header,他們是Sec-WebSocket-Version和Sec-WebSocket-Key。
先看一下Sec-WebSocket-Version, 它表示的是客戶端請求的WebSocket的版本號。如果伺服器端並不明白客戶端傳送的請求,則會返回一個400 ("Bad Request"),在這個返回中,伺服器端會返回失敗的資訊。
如果是不懂客戶端傳送的Sec-WebSocket-Version,伺服器端同樣會將Sec-WebSocket-Version返回,以告知客戶端。
這裡要特別關注的一個header欄位就是Sec-WebSocket-Key。我們接下來看一下這個欄位到底有什麼用。
當伺服器端收到客戶端的請求之後,會返回給客戶端一個響應,告訴客戶端協議已經從HTTP升級到WebSocket了。
返回的響應可能是這樣的:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
這裡的Sec-WebSocket-Accept是根據客戶端請求中的Sec-WebSocket-Key來生成的。具體而言是將客戶端傳送的Sec-WebSocket-Key 和 字串"258EAFA5-E914-47DA-95CA-C5AB0DC85B11" 進行連線。然後使用SHA1演算法求得其hash值。
最後將hash值進行base64編碼即可。
當伺服器端返回Sec-WebSocket-Accept之後,客戶端可以對其進行校驗,已完成整個握手過程。
webSocket的訊息格式
之所以要使用webSocket是因為client和server可以隨時隨地傳送訊息。這是websocket的神奇所在。那麼傳送的訊息是什麼格式的呢?我們來詳細看一下。
client和server端進行溝通的訊息是以一個個的frame的形式來傳輸的。frame的格式如下:
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 ... |
+---------------------------------------------------------------+
MASK表示的是訊息是否是被編碼過的,對於從client過來的訊息來說,MASK必須是1。如果client傳送給server端的訊息,MASK不為1,則server需要斷開和client的連線。但是server端傳送給client端的訊息,MASK欄位就不需要設定了。
RSV1-3是擴充套件的欄位,可以忽略。
opcode表示怎麼去解釋payload欄位。payload就是實際要傳遞的訊息。0x0表示繼續,0x1表示文字,0x2表示二進位制,其他的表示控制欄位。
FIN表示是否是訊息的最後一個frame。如果是0,表示該訊息還有更多的frame。如果是1表示,該frame是訊息的最後一部分了,可以對訊息進行處理了。
為什麼需要Payload len欄位呢?因為我們需要知道什麼時候停止接收訊息。所以需要一個表示payload的欄位來對訊息進行具體的處理。
怎麼解析Payload呢?這個就比較複雜。
- 首先讀取9-15 bits,將其解析為無符號整數。如果其小於125,那麼這個就是payload的長度,結束。如果是126,那麼就去到第二步。如果是127,那麼就去到第三步。
- 讀取下一個16 bits,然後將其解析為無符號整數,結束。
- 讀取下一個64 bits。將其解析為符號整數。結束。
如果設定了Mask,那麼讀取下4個位元組,也就是32bits。這個是masking key。當資料讀取完畢之後,我們就獲取到了編碼過後的payload:ENCODED,和MASK key。要解碼的話,其邏輯如下:
var DECODED = "";
for (var i = 0; i < ENCODED.length; i++) {
DECODED[i] = ENCODED[i] ^ MASK[i % 4];
FIN可以和opcode一起配合使用,用來傳送長訊息。
FIN=1表示,是最後一個訊息。 0x1表示是text訊息,0x2是0,表示是二淨值訊息,0x0表示訊息還沒有結束,所以0x0通常和FIN=0 一起使用。
Extensions和Subprotocols
在客戶端和伺服器端進行握手的過程中,在標準的websocket協議基礎之上,客戶端還可以傳送Extensions或者Subprotocols。這兩個有什麼區別呢?
首先這兩個都是通過HTTP頭來設定的。但是兩者還是有很大的不同。Extensions可以對WebSocket進行控制,並且修改payload,而subprotocols只是定義了payload的結構,並不會對其進行修改。
Extensions是可選的,而Subprotocols是必須的。
你可以將Extensions看做是資料壓縮,它是在webSocket的基礎之上,對資料進行壓縮或者優化操作,可以讓傳送的訊息更短。
而Subprotocols 表示的是訊息的格式,比如使用soap或者wamp。
子協議是在WebSocket協議基礎上發展出來的協議,主要用於具體的場景的處理,它是是在WebSocket協議之上,建立的更加嚴格的規範。
比如,客戶端請求伺服器時候,會將對應的協議放在Sec-WebSocket-Protocol頭中:
GET /socket HTTP/1.1
...
Sec-WebSocket-Protocol: soap, wamp
伺服器端會根據支援的型別,做對應的返回,如:
Sec-WebSocket-Protocol: soap
總結
本文講解了webSocket訊息互動的具體格式,可以看到很多強大功能的協議,都是由最最基本的結構組成的。
本文已收錄於 http://www.flydean.com/07-websocket-message/
最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!