本文爲WebSocket協議的第五章,本文翻譯的主要內容爲WebSocket傳輸的數據相關內容。html
有興趣瞭解該文檔以前幾章內容的同窗能夠見:算法
在WebSocket協議中,數據是經過一系列數據幀來進行傳輸的。爲了不因爲網絡中介(例如一些攔截代理)或者一些在第10.3節討論的安全緣由,客戶端必須在它發送到服務器的全部幀中添加掩碼(Mask)(具體細節見5.3節)。(注意:不管WebSocket協議是否使用了TLS,幀都須要添加掩碼)。服務端收到沒有添加掩碼的數據幀之後,必須當即關閉鏈接。在這種狀況下,服務端能夠發送一個在7.4.1節定義的狀態碼爲1002(協議錯誤)的關閉幀。服務端禁止在發送數據幀給客戶端時添加掩碼。客戶端若是收到了一個添加了掩碼的幀,必須當即關閉鏈接。在這種狀況下,它可使用第7.4.1節定義的1002(協議錯誤)狀態碼。(這些規則可能會在未來的規範中放開)。數組
基礎的數據幀協議使用操做碼、有效負載長度和在「有效負載數據」中定義的放置「擴展數據」與「引用數據」的指定位置來定義幀類型。特定的bit位和操做碼爲未來的協議擴展作了保留。緩存
一個數據幀能夠在開始握手完成以後和終端發送了一個關閉幀以前的任意一個時間經過客戶端或者服務端進行傳輸(第5.5.1節)。安全
在這節中的這種數據傳輸部分的有線格式是經過ABNFRFC5234來進行詳細說明的。(注意:不像這篇文檔中的其餘章節內容,在這節中的ABNF是對bit組進行操做。每個bit組的長度是在評論中展現的。在線上編碼時,最高位的bit是在ABNF最左邊的)。對於數據幀的高級的預覽能夠見下圖。若是下圖指定的內容和這一節中後面的ABNF指定的內容有衝突的話,如下圖爲準。服務器
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 ... |
+---------------------------------------------------------------+
複製代碼
FIN: 1 bit網絡
表示這是消息的最後一個片斷。第一個片斷也有多是最後一個片斷。閉包
RSV1,RSV2,RSV3: 每一個1 bitapp
必須設置爲0,除非擴展了非0值含義的擴展。若是收到了一個非0值可是沒有擴展任何非0值的含義,接收終端必須斷開WebSocket鏈接。post
Opcode: 4 bit
定義「有效負載數據」的解釋。若是收到一個未知的操做碼,接收終端必須斷開WebSocket鏈接。下面的值是被定義過的。
%x0 表示一個持續幀
%x1 表示一個文本幀
%x2 表示一個二進制幀
%x3-7 預留給之後的非控制幀
%x8 表示一個鏈接關閉包
%x9 表示一個ping包
%xA 表示一個pong包
%xB-F 預留給之後的控制幀
Mask: 1 bit
mask標誌位,定義「有效負載數據」是否添加掩碼。若是設置爲1,那麼掩碼的鍵值存在於Masking-Key中,根據5.3節描述,這個通常用於解碼「有效負載數據」。全部的從客戶端發送到服務端的幀都須要設置這個bit位爲1。
Payload length: 7 bits, 7+16 bits, or 7+64 bits
以字節爲單位的「有效負載數據」長度,若是值爲0-125,那麼就表示負載數據的長度。若是是126,那麼接下來的2個bytes解釋爲16bit的無符號整形做爲負載數據的長度。若是是127,那麼接下來的8個bytes解釋爲一個64bit的無符號整形(最高位的bit必須爲0)做爲負載數據的長度。多字節長度量以網絡字節順序表示(譯註:應該是指大端序和小端序)。在全部的示例中,長度值必須使用最小字節數來進行編碼,例如:長度爲124字節的字符串不可用使用序列126,0,124進行編碼。有效負載長度是指「擴展數據」+「應用數據」的長度。「擴展數據」的長度可能爲0,那麼有效負載長度就是「應用數據」的長度。
Masking-Key: 0 or 4 bytes
全部從客戶端發往服務端的數據幀都已經與一個包含在這一幀中的32 bit的掩碼進行過了運算。若是mask標誌位(1 bit)爲1,那麼這個字段存在,若是標誌位爲0,那麼這個字段不存在。在5.3節中會介紹更多關於客戶端到服務端增長掩碼的信息。
Payload data: (x+y) bytes
「有效負載數據」是指「擴展數據」和「應用數據」。
Extension data: x bytes
除非協商過擴展,不然「擴展數據」長度爲0 bytes。在握手協議中,任何擴展都必須指定「擴展數據」的長度,這個長度如何進行計算,以及這個擴展如何使用。若是存在擴展,那麼這個「擴展數據」包含在總的有效負載長度中。
Application data: y bytes
任意的「應用數據」,佔用「擴展數據」後面的剩餘全部字段。「應用數據」的長度等於有效負載長度減去「擴展應用」長度。
基礎數據幀協議經過ABNF進行了正式的定義。須要重點知道的是,這些數據都是二進制的,而不是ASCII字符。例如,長度爲1 bit的字段的值爲%x0 / %x1表明的是一個值爲0/1的單獨的bit,而不是一整個字節(8 bit)來表明ASCII編碼的字符「0」和「1」。一個長度爲4 bit的範圍是%x0-F的字段值表明的是4個bit,而不是字節(8 bit)對應的ASCII碼的值。不要指定字符編碼:「規則解析爲一組最終的值,有時候是字符。在ABNF中,字符僅僅是一個非負的數字。在特定的上下文中,會根據特定的值的映射(編碼)編碼集(例如ASCII)」。在這裏,指定的編碼類型是將每一個字段編碼爲特定的bits數組的二進制編碼的最終數據。
ws-frame =
frame-fin =
frame-rsv1 =
frame-rsv2 =
frame-rsv3 =
frame-opcode =
frame-opcode-non-control
frame-opcode-control
frame-masked
frame-payload-length
frame-payload-length-16
frame-payload-length-63
frame-masking-key
frame-payload-data
frame-masked-extension-data
frame-masked-application-data
frame-unmasked-extension-data
frame-unmasked-application-data
添加掩碼的數據幀必須像5.2節定義的同樣,設置frame-masked字段爲1。
掩碼值像第5.2節說到的徹底包含在幀中的frame-masking-key上。它是用於對定義在同一節中定義的幀負載數據Payload data
字段中的包含Extension data
和Application data
的數據進行添加掩碼。
掩碼字段是一個由客戶端隨機選擇的32bit的值。當準備掩碼幀時,客戶端必須從容許的32bit值中須知你咋一個新的掩碼值。掩碼值必須是不可被預測的;所以,掩碼必須來自強大的熵源(entropy),而且給定的掩碼不能讓服務器或者代理可以很容易的預測到後續幀。掩碼的不可預測性對於預防惡意應用做者在網上暴露相關的字節數據相當重要。RFC 4086討論了安全敏感的應用須要一個什麼樣的合適的強大的熵源。
掩碼不影響Payload data
的長度。進行掩碼的數據轉換爲非掩碼數據,或者反過來,根據下面的算法便可。這個一樣的算法適用於任意操做方向的轉換,例如:對數據進行掩碼操做和對數據進行反掩碼操做所涉及的步驟是相同的。
表示轉換後數據的八位字節的i(transformed-octet-i
)是表示的原始數據的i(original-octet-i
)與索引i模4獲得的掩碼值(masking-key-octet-j
)通過異或操做(XOR)獲得的:
j = i MOD 4 transfromed-octed-i = original-octet-i XOR masking-key-octet-j
在規範中定義的位於frame-payload-length字段的有效負載的長度,不包括掩碼值的長度。它只是Payload data
的長度。如跟在掩碼值後面的字節數組的數。
消息分片的主要目的是容許發送一個未知長度且消息開始發送後不須要緩存的消息。若是消息不能被分片,那麼一端必須在緩存整個消息,所以這個消息的長度必須在第一個字節發送前就須要計算出來。若是有消息分片,服務端或者代理能夠選擇一個合理的緩存長度,當緩存區滿了之後,就想網絡發送一個片斷。
第二個消息分片使用的場景是不適合在一個邏輯通道內傳輸一個大的消息佔滿整個輸出頻道的多路複用場景。多路複用須要可以將消息進行自由的切割成更小的片斷來共享輸出頻道。(注意:多路複用的擴展不在這個文檔中討論)。
除非在擴展中另有規定,不然幀沒有語義的含義。若是客戶端和服務的沒有協商擴展字段,或者服務端和客戶端協商了一些擴展字段,而且代理可以徹底識別全部的協商擴展字段,在這些擴展字段存在的狀況下知道如何進行幀的合併和拆分,代理就可能會合並或者拆分幀。這個的一個含義是指在缺乏擴展字段的狀況下,發送者和接收者都不能依賴特定的幀邊界的存在。
消息分片相關的規則以下:
Extension data
的解析方式,所以前面的結論可能不成立。例如:Extension data
可能只出如今第一個片斷的開頭,並適用於接下來的片斷,或者可能每個片斷都有Extension data
,可是隻適用於特定的片斷。在Extension data
不存在時,下面的示例演示了消息分片是如何運做的。 示例:一個文本須要分紅三個片斷進行發送,第一個片斷包含的操做碼爲0x1而且未設置FIN字段,第二個片斷的操做碼爲0x0而且未設置FIN字段,第三個片斷的操做碼爲0x0而且設置了FIN字段。注:若是控制幀沒有被打斷,心跳(ping)的等待時間可能會變很長,例如在一個很大的消息以後。所以,在分片的消息傳輸中插入控制幀是有必要的。
實踐說明:若是擴展字段不存在,接收者不須要使用緩存來存儲下整個消息片斷來進行處理。例如:若是使用一個流式API,再收到部分幀的時候就能夠將數據交給上層應用。然而,這個假設對之後全部的WebSocket擴展可能不必定成立。
控制幀是經過操做碼最高位的值爲1來進行區分的。當前已經定義的控制幀操做碼包括0x8(關閉),0x9(心跳Ping)和0xA(心跳Pong)。操做碼0xB-0xF沒有被定義,當前被保留下來作爲之後的控制幀。
控制幀是用於WebSocket的通訊狀態的。控制幀能夠被插入到消息片斷中進行傳輸。
全部的控制幀必須有一個126字節或者更小的負載長度,而且不能被分片。
控制幀的操做碼值是0x8。
關閉幀可能包含內容(body)(幀的「應用數據」部分)來代表鏈接關閉的緣由,例如終端的斷開,或者是終端收到了一個太大的幀,或者是終端收到了一個不符合預期的格式的內容。若是這個內容存在,內容的前兩個字節必須是一個無符號整型(按照網絡字節序)來表明在7.4節中定義的狀態碼。跟在這兩個整型字節以後的能夠是UTF-8編碼的的數據值(緣由),數據值的定義不在此文檔中。數據值不必定是要人能夠讀懂的,可是必須對於調試有幫助,或者能傳遞有關於當前打開的這條鏈接有關聯的信息。數據值不保證人必定能夠讀懂,因此不能把這些展現給終端用戶。
從客戶端發送給服務端的控制幀必須添加掩碼,具體見5.3節。
應用禁止在發送了關閉的控制幀後再發送任何的數據幀。
若是終端收到了一個關閉的控制幀而且沒有在之前發送一個關閉幀,那麼終端必須發送一個關閉幀做爲迴應。(當發送一個關閉幀做爲迴應時,終端一般會輸出它收到的狀態碼)響應的關閉幀應該儘快發送。終端可能會推遲發送關閉幀直到當前的消息都已經發送完成(例如:若是大多數分片的消息已經發送了,終端可能會在發送關閉幀以前將剩餘的消息片斷髮送出去)。然而,已經發送關閉幀的終端不能保證會繼續處理收到的消息。
在已經發送和收到了關閉幀後,終端認爲WebSocket鏈接以及關閉了,而且必須關閉底層的TCP鏈接。服務端必須立刻關閉底層的TCP鏈接,客戶端應該等待服務端關閉鏈接,可是也能夠在收到關閉幀之後任意時間關閉鏈接。例如:若是在合理的時間段內沒有收到TCP關閉指令。
若是客戶端和服務端咋同一個時間發送了關閉幀,兩個終端都會發送和接收到一條關閉的消息,而且應該認爲WebSocket鏈接已經關閉,同時關閉底層的TCP鏈接。
心跳Ping幀包含的操做碼是0x9。
關閉幀可能包含「應用數據」。
若是收到了一個心跳Ping幀,那麼終端必須發送一個心跳Pong 幀做爲迴應,除非已經收到了一個關閉幀。終端應該儘快恢復Pong幀。Pong幀將會在5.5.3節討論。
終端可能會在創建鏈接後與鏈接關閉前中間的任意時間發送Ping幀。
注意:Ping幀多是用於保活或者用來驗證遠端是否仍然有應答。
心跳Ping幀包含的操做碼是0xA。
5.5.2節詳細說明了Ping幀和Pong幀的要求。
做爲迴應發送的Pong幀必須完整攜帶Ping幀中傳遞過來的「應用數據」字段。
若是終端收到一個Ping幀可是沒有發送Pong幀來回應以前的pong幀,那麼終端可能選擇用Pong幀來回復最近處理的那個Ping幀。
Pong幀能夠被主動發送。這會做爲一個單項的心跳。預期外的Pong包的響應沒有規定。
數據幀(例如非控制幀)的定義是操做碼的最高位值爲0。當前定義的數據幀操做嗎包含0x1(文本)、0x2(二進制)。操做碼0x3-0x7是被保留做爲非控制幀的操做碼。
數據幀會攜帶應用層/擴展層數據。操做碼決定了攜帶的數據解析方式:
文本
「負載字段」是用UTF-8編碼的文本數據。注意特殊的文本幀可能包含部分UTF-8序列;然而,整個消息必須是有效的UTF-8編碼數據。從新組合消息後無效的UTF-8編碼數據處理見8.1節。
二進制
「負載字段」是任意的二進制數據,二進制數據的解析僅僅依靠應用層。
這個協議的設計初衷是容許擴展的,能夠在基礎協議上增長能力。終端的鏈接必須在握手的過程當中協商使用的全部擴展。在規範中提供了從0x3-0x7和0xB-0xF的操做碼,在數據幀Header中的「擴展數據」字段、frame-rsv一、frame-rsv二、frame-rsv3字段均可以用於擴展。擴展的協商討論將在之後的9.1節中詳細討論。下面是一些符合預期的擴展用法。下面的列表不完整,也不是規範中內容。