1、數據幀格式概覽
下面給出了WebSocket數據幀的統一格式。熟悉TCP/IP協議的同學對這樣的圖應該不陌生。
- 從左到右,單位是比特。比如FIN、RSV1各占據1比特,opcode占據4比特。
- 內容包括了標識、操作代碼、掩碼、數據、數據長度等。(下一小節會展開)
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 ... |
+---------------------------------------------------------------+
2、數據幀格式詳解
針對前面的格式概覽圖,這里逐個字段進行講解,如有不清楚之處,可參考協議規范,或留言交流。
FIN:1個比特。
如果是1,表示這是消息(message)的最后一個分片(fragment),如果是0,表示不是是消息(message)的最后一個分片(fragment)。
RSV1, RSV2, RSV3:各占1個比特。
一般情況下全為0。當客戶端、服務端協商采用WebSocket擴展時,這三個標志位可以非0,且值的含義由擴展進行定義。如果出現非零的值,且並沒有采用WebSocket擴展,連接出錯。
Opcode: 4個比特。
操作代碼,Opcode的值決定了應該如何解析后續的數據載荷(data payload)。如果操作代碼是不認識的,那么接收端應該斷開連接(fail the connection)。可選的操作代碼如下:
%x0:表示一個延續幀。當Opcode為0時,表示本次數據傳輸采用了數據分片,當前收到的數據幀為其中一個數據分片。
%x1:表示這是一個文本幀(frame)
%x2:表示這是一個二進制幀(frame)
%x3-7:保留的操作代碼,用於后續定義的非控制幀。
%x8:表示連接斷開。
%x9:表示這是一個ping操作。
%xA:表示這是一個pong操作。
%xB-F:保留的操作代碼,用於后續定義的控制幀。
Mask: 1個比特。
表示是否要對數據載荷進行掩碼操作。從客戶端向服務端發送數據時,需要對數據進行掩碼操作;從服務端向客戶端發送數據時,不需要對數據進行掩碼操作。
如果服務端接收到的數據沒有進行過掩碼操作,服務端需要斷開連接。
如果Mask是1,那么在Masking-key中會定義一個掩碼鍵(masking key),並用這個掩碼鍵來對數據載荷進行反掩碼。所有客戶端發送到服務端的數據幀,Mask都是1。
掩碼的算法、用途在下一小節講解。
Payload length:
數據載荷的長度,單位是字節。為7位,或7+16位,或1+64位。
假設數Payload length === x,如果
x為0~126:數據的長度為x字節。
x為126:后續2個字節代表一個16位的無符號整數,該無符號整數的值為數據的長度。
x為127:后續8個字節代表一個64位的無符號整數(最高位為0),該無符號整數的值為數據的長度。
此外,如果payload length占用了多個字節的話,payload length的二進制表達采用網絡序(big endian,重要的位在前)。
Masking-key:0或4字節(32位)
所有從客戶端傳送到服務端的數據幀,數據載荷都進行了掩碼操作,Mask為1,且攜帶了4字節的Masking-key。如果Mask為0,則沒有Masking-key。
備注:載荷數據的長度,不包括mask key的長度。
Payload data:(x+y) 字節
載荷數據:包括了擴展數據、應用數據。其中,擴展數據x字節,應用數據y字節。
擴展數據:如果沒有協商使用擴展的話,擴展數據數據為0字節。所有的擴展都必須聲明擴展數據的長度,或者可以如何計算出擴展數據的長度。此外,擴展如何使用必須在握手階段就協商好。如果擴展數據存在,那么載荷數據長度必須將擴展數據的長度包含在內。
應用數據:任意的應用數據,在擴展數據之后(如果存在擴展數據),占據了數據幀剩余的位置。載荷數據長度 減去 擴展數據長度,就得到應用數據的長度。
3、掩碼算法
掩碼鍵(Masking-key)是由客戶端挑選出來的32位的隨機數。掩碼操作不會影響數據載荷的長度。掩碼、反掩碼操作都采用如下算法:
首先,假設:
original-octet-i:為原始數據的第i字節。
transformed-octet-i:為轉換后的數據的第i字節。
j:為i mod 4的結果。
masking-key-octet-j:為mask key第j字節。
算法描述為:
original-octet-i與 masking-key-octet-j異或后,得到 transformed-octet-i。
j = i MOD 4
transformed-octet-i = original-octet-i XOR masking-key-octet-j
4、掩碼樣例
場景:
客戶端:發送語音文件到服務端,先發送txt消息文件名稱"tts";再讀物文件,發送bin消息二進制流數據(多幀發送)。
服務端:先接收txt消息,需要接收的文件名稱並創建打開;再接收bin消息語音流消息,並寫入文件;當接收到最后一幀二進制后,關閉打開的文件。
txt消息生成的 掩碼KEY:
the key is 14,51,172,208.
客服端處理消息:“tts” =》122,71,-33
- 客戶端生成原始4位掩碼
- 將需要發送字符與掩碼異或運算,獲取計算后的值,用於網絡傳輸。
116(t)與掩碼14異或處理,得到異或值:122
116(t)與掩碼51異或處理,得到異或值:71
115(s)與掩碼172異或處理,得到異或值:223 - 生成待發送消息:121,71,223
服務端處理消息:122,71,223 =》 “tts”
- 獲取客戶端發送的4位掩碼和數據消息
- 將收到的字符消息與掩碼異或運算,獲取源字符值
122與掩碼(14)異或處理,得到原字符值:116(t)
71與掩碼(51)異或處理,得到原字符值:116(t)
233與掩碼(172)異或處理,得到原字符值:115(s) - 解碼獲取文件名:116,116,115;及字符:tts
