Websocket數(shù)據(jù)幀的封裝和傳輸其實(shí)和處理握手請(qǐng)求的流程差不太多,都需要通過(guò)bytebuffer寫(xiě)入Socket的輸出流或者從輸入流讀取。
數(shù)據(jù)幀格式
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 ... |
+---------------------------------------------------------------+
具體每一bit的意思
FIN 1bit 表示信息的最后一幀
RSV 1-3 1bit each 以后備用的 默認(rèn)都為 0
Opcode 4bit 幀類(lèi)型,稍后細(xì)說(shuō)
Mask 1bit 掩碼海渊,是否加密數(shù)據(jù),默認(rèn)必須置為1
Payload len 7bit 數(shù)據(jù)的長(zhǎng)度
Masking-key 1 or 4 bit 掩碼
Payload data (x + y) bytes 數(shù)據(jù)
Extension data x bytes 擴(kuò)展數(shù)據(jù)
Application data y bytes 程序數(shù)據(jù)
這個(gè)看著比較蛋疼的一點(diǎn)是頂部用的十進(jìn)制而不是八進(jìn)制,仔細(xì)看一下每行有4個(gè)字節(jié)
Fin
為1表示是最后一個(gè)一個(gè)數(shù)據(jù)幀贮乳,有時(shí)候數(shù)據(jù)需要分成多個(gè)數(shù)據(jù)包來(lái)發(fā)送,這就需要用到分片胀屿,也就是使用多個(gè)數(shù)據(jù)幀來(lái)傳輸一個(gè)數(shù)據(jù)塘揣。
Opcode
表示幀的類(lèi)型,例如這個(gè)傳輸?shù)膸俏谋绢?lèi)型還是二進(jìn)制類(lèi)型宿崭,二進(jìn)制類(lèi)型傳輸?shù)臄?shù)據(jù)可以是圖片或者語(yǔ)音之類(lèi)的亲铡。
OPCODE:4位
解釋PayloadData,如果接收到未知的opcode葡兑,接收端必須關(guān)閉連接奖蔓。
0x0表示附加數(shù)據(jù)幀
0x1表示文本數(shù)據(jù)幀
0x2表示二進(jìn)制數(shù)據(jù)幀
0x3-7暫時(shí)無(wú)定義,為以后的非控制幀保留
0x8表示連接關(guān)閉
0x9表示ping
0xA表示pong
0xB-F暫時(shí)無(wú)定義讹堤,為以后的控制幀保留
Mask
表示是否經(jīng)過(guò)掩碼處理
Payload len
占據(jù)七位用來(lái)描述消息長(zhǎng)度吆鹤,由于7位最多只能描述127所以這個(gè)值會(huì)代表三種情況,一種是消息內(nèi)容少于126存儲(chǔ)消息長(zhǎng)度,如果消息長(zhǎng)度少于UINT16的情況此值為126,當(dāng)消息長(zhǎng)度大于UINT16的情況下此值為127;這兩種情況的消息長(zhǎng)度存儲(chǔ)到緊隨后面的byte[],分別是UINT16(2位byte)和UINT64(4位byte)。
客戶(hù)端到服務(wù)器端掩碼處理
客戶(hù)端發(fā)送到服務(wù)器端的數(shù)據(jù)必須進(jìn)行掩碼處理洲守,掩碼的密鑰是一個(gè)32位的隨機(jī)值疑务。所有數(shù)據(jù)都需要與掩碼做一次異或運(yùn)算沾凄。
j = i mod 4(i 是傳輸數(shù)據(jù)中的十進(jìn)制的索引下標(biāo))
轉(zhuǎn)換后的數(shù)據(jù) d = original ^ mask[j]
消息分片
分片的目的是允許發(fā)送未知長(zhǎng)度的消息。如果消息不能被碎片化知允,那么一端就必須將消息整個(gè)地載入內(nèi)存緩沖撒蟀,計(jì)算長(zhǎng)度,構(gòu)建frame并發(fā)送温鸽。有了碎片化的機(jī)制保屯,服務(wù)端或者中間件就可以選取適用的內(nèi)存緩沖長(zhǎng)度,然后當(dāng)緩沖滿(mǎn)了之后就發(fā)送一個(gè)消息碎片涤垫。
分片規(guī)則:
- 一個(gè)未分片的消息只有一幀(FIN為1姑尺,opcode非0)
- 一個(gè)分片的消息由起始幀(FIN為0,opcode非0)蝠猬,若干(0個(gè)或多個(gè))幀(FIN為0切蟋,opcode為0),結(jié)束幀(FIN為1吱雏,opcode為0)敦姻。
- 控制幀可以出現(xiàn)在分片消息中間,但控制幀本身不允許分片歧杏×耄控制幀是通過(guò)它的 opcode 的最高有效位是 1 去確定的。當(dāng)前已經(jīng)定義了的控制幀包括 0x8 (close)犬绒,0x9 (Ping)旺入,0xA (Pong)。
- 消息幀必須以其被發(fā)送時(shí)的順序傳遞到接收端凯力。
- 組成消息的所有幀都是相同的數(shù)據(jù)類(lèi)型茵瘾,在第一個(gè)幀中的 opcode 中指明。因?yàn)榭刂茙荒鼙凰槠篮祝M成消息的碎片類(lèi)型必須是文本拗秘、二進(jìn)制、或者其他的保留類(lèi)型祈惶。
未分片的消息
FIN=0,Opcode>0
分片的消息分片可以分為三個(gè)類(lèi)型:
開(kāi)始幀:FIN=0,Opcode>0;一個(gè)
傳輸幀:FIN=0,Opcode=0;零個(gè)或多個(gè)
終止幀:FIN=1,Opcode=0;一個(gè)
例子解析
比如我們接受到了一段消息的數(shù)據(jù)部分是810548656c6c6f
81 05: 10000001 00000101
48 65: 01001000 01100101
6c 6c: 01001100 01001100
6f : 01001111
0:1雕旨,這是最后一幀
1~3:全為0
4~7:0001,文本數(shù)據(jù)幀
8:0捧请,PlayloadData未經(jīng)過(guò)掩碼
9~15:0000101 = 5 < 125凡涩,因此數(shù)據(jù)長(zhǎng)度位5
剩下的:48 65 6c 6c 6f即為Hello
比如我們發(fā)送的消息123456789數(shù)據(jù)部分是818911eb9db220d9ae8624ddaa8a28
81 89 : 10000001 10001001
11 eb : 00010001 11101011
9d b2 : 10011101 10110010
20 d9 : 00100000 11011001
ae 86 : 10101110 10000110
24 dd : 00100100 11011101
aa 8a : 10101010 10001010
28 : 00101000
0:1,這是最后一幀
1~3:全為0
4~7:0001疹蛉,文本數(shù)據(jù)幀
8:1活箕,PlayloadData經(jīng)過(guò)掩碼,(所有的由客戶(hù)端發(fā)往服務(wù)端的幀此數(shù)位都被設(shè)置成 1可款。)
9~15:0001001 = 9 < 125育韩,因此數(shù)據(jù)長(zhǎng)度位9
11 eb 9d b2:掩碼
20 d9 ae 86 24 dd aa 8a 28:數(shù)據(jù)
下面演示消息123456789怎么通過(guò)掩碼加密成20 d9 ae 86 24 dd aa 8a 28
1 2 3 發(fā)送的消息
31 32 33 消息對(duì)應(yīng)的ASCII
00110001 00110010 00110011
11 eb 9d 掩碼
00010001 11101011 10011101
交配
00110001 00110010 00110011
00010001 11101011 10011101
00100000 11011001 10101110
20 d9 ae
關(guān)閉幀
前面提到了關(guān)閉幀的操作碼opcode是0x8
關(guān)閉幀也可以包含消息體克蚂,通過(guò)數(shù)據(jù)幀的“應(yīng)用數(shù)據(jù)部分表示關(guān)閉原因”,消息體的前兩個(gè)字節(jié)必須是無(wú)符號(hào)的整型數(shù)(采用網(wǎng)絡(luò)字節(jié)序)座慰,以此整型數(shù)去表示狀態(tài)碼陨舱。在兩個(gè)字節(jié)的無(wú)符號(hào)整型數(shù)之后翠拣,可以跟上以 UTF-8 編碼的數(shù)據(jù)表示 /reason/版仔,/reason/ 數(shù)據(jù)的具體解釋方式此文檔并沒(méi)有定義。并且 /reason/ 的內(nèi)容不一定是人類(lèi)可讀的數(shù)據(jù)误墓,只要是有利于發(fā)起連接的腳本進(jìn)行調(diào)試就可以蛮粮。因?yàn)?/reason/ 并不一定就是人類(lèi)可讀的,所以客戶(hù)端必須不將此內(nèi)容展示給最終用戶(hù)谜慌。
應(yīng)用程序在發(fā)送了關(guān)閉幀之后就不可以再發(fā)送其他數(shù)據(jù)幀了然想。
如果接收到關(guān)閉幀的一端之前沒(méi)有發(fā)送過(guò)關(guān)閉幀的話(huà),那么它必須發(fā)送一個(gè)關(guān)閉幀作為響應(yīng)欣范。(當(dāng)發(fā)送一個(gè)關(guān)閉幀作為響應(yīng)的時(shí)候变泄,發(fā)送端通常在作為響應(yīng)的關(guān)閉幀中采用和其接收到的關(guān)閉幀相同的狀態(tài)碼)。并且響應(yīng)必須盡快的發(fā)送恼琼。一端可以延遲關(guān)閉幀的發(fā)送妨蛹,比如一個(gè)重要的消息已經(jīng)發(fā)送了一半,那么可以在消息的剩余部分發(fā)送完之后再發(fā)送關(guān)閉幀晴竞。但是作為首先發(fā)送了關(guān)閉幀蛙卤,并在等待另一端進(jìn)行關(guān)閉響應(yīng)的那一端來(lái)說(shuō),并不一定保證其會(huì)繼續(xù)處理數(shù)據(jù)內(nèi)容噩死。
在發(fā)送和接收到了關(guān)閉幀之后颤难,一端就可以認(rèn)為 WebSocket 連接已經(jīng)關(guān)閉,并且必須關(guān)閉底層相關(guān)的 TCP 連接已维。如果是服務(wù)端首先發(fā)送了關(guān)閉幀行嗤,那么在接收到客戶(hù)端返回的關(guān)閉幀之后,服務(wù)端必須立即關(guān)閉底層相關(guān)的 TCP 連接垛耳;但是如果是客戶(hù)端首先發(fā)送了關(guān)閉幀栅屏,并接收到了服務(wù)端返回的關(guān)閉幀之后,可以選擇其認(rèn)為合適的時(shí)間關(guān)閉連接艾扮,比如既琴,在一段時(shí)間內(nèi)沒(méi)有接收到服務(wù)端的 TCP 關(guān)閉握手。
如果客戶(hù)端和服務(wù)端同時(shí)發(fā)送了關(guān)閉消息泡嘴,那么它們兩端都將會(huì)接收到來(lái)自對(duì)方的關(guān)閉消息甫恩,那么它們就可以認(rèn)為 WebSocket 連接已經(jīng)關(guān)閉,并且關(guān)閉底層相關(guān)的 TCP 連接酌予。
已經(jīng)定義的狀態(tài)碼
1000 表明這是一個(gè)正常的關(guān)閉
1001 表明一端是即將關(guān)閉的磺箕,比如服務(wù)端將關(guān)閉或者瀏覽器跳轉(zhuǎn)到了其他頁(yè)面
1002 表明一端正在因?yàn)閰f(xié)議錯(cuò)誤而關(guān)閉連接
1003 表明一端因?yàn)榻邮盏搅藷o(wú)法受理的數(shù)據(jù)而關(guān)閉連接(比如只能處理文本的一端接收到了一個(gè)二進(jìn)制的消息)
1004 保留的奖慌。
1005 是一個(gè)保留值,并且必須不可以作為關(guān)閉幀的狀態(tài)碼
1006 是一個(gè)保留值松靡,并且必須不可以作為關(guān)閉幀的狀態(tài)碼
1007 表明一端接收到的消息內(nèi)容與之標(biāo)記的類(lèi)型不符而需要關(guān)閉連接(比如文本消息中出現(xiàn)了非 UTF-8 的內(nèi)容)
1008 表明了一端接收到的消息內(nèi)容違反了其接收消息的策略而需要關(guān)閉連接
1009 表明一端接收了非常大的數(shù)據(jù)而其無(wú)法處理時(shí)需要關(guān)閉連接
1010 表明了客戶(hù)端希望服務(wù)端協(xié)商一個(gè)或多個(gè)擴(kuò)展简僧,但是服務(wù)端在返回的握手信息中包含協(xié)商信息
1011 表明了一端遇到了異常情況使得其無(wú)法完成請(qǐng)求而需要關(guān)閉連接
1015 是一個(gè)保留值,并且必須不可以作為關(guān)閉幀的狀態(tài)碼