【譯】WebSocket協議第五章——數據幀(Data Framing)

概述

本文爲WebSocket協議的第五章,本文翻譯的主要內容爲WebSocket傳輸的數據相關內容。html

有興趣瞭解該文檔以前幾章內容的同窗能夠見:算法

數據幀(協議正文)

5.1 概覽

在WebSocket協議中,數據是經過一系列數據幀來進行傳輸的。爲了不因爲網絡中介(例如一些攔截代理)或者一些在第10.3節討論的安全緣由,客戶端必須在它發送到服務器的全部幀中添加掩碼(Mask)(具體細節見5.3節)。(注意:不管WebSocket協議是否使用了TLS,幀都須要添加掩碼)。服務端收到沒有添加掩碼的數據幀之後,必須當即關閉鏈接。在這種狀況下,服務端能夠發送一個在7.4.1節定義的狀態碼爲1002(協議錯誤)的關閉幀。服務端禁止在發送數據幀給客戶端時添加掩碼。客戶端若是收到了一個添加了掩碼的幀,必須當即關閉鏈接。在這種狀況下,它可使用第7.4.1節定義的1002(協議錯誤)狀態碼。(這些規則可能會在未來的規範中放開)。數組

基礎的數據幀協議使用操做碼、有效負載長度和在「有效負載數據」中定義的放置「擴展數據」與「引用數據」的指定位置來定義幀類型。特定的bit位和操做碼爲未來的協議擴展作了保留。緩存

一個數據幀能夠在開始握手完成以後和終端發送了一個關閉幀以前的任意一個時間經過客戶端或者服務端進行傳輸(第5.5.1節)。安全

5.2 基礎幀協議

在這節中的這種數據傳輸部分的有線格式是經過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; 長度爲1 bit
  • frame-rsv1; 長度爲1 bit
  • frame-rsv2; 長度爲1 bit
  • frame-rsv3; 長度爲1 bit
  • frame-opcode; 長度爲4 bit
  • frame-masked; 長度爲1 bit
  • frame-payload-length; 長度爲7或者7+16或者7+64 bit
  • [frame-masking-key]; 長度爲32 bit
  • frame-payload-data; 長度爲大於0的n*8 bit(其中n>0)

frame-fin =

  • %x0,除了如下爲1的狀況
  • %x1,最後一個消息幀
  • 長度爲1 bit

frame-rsv1 =

  • %x0 / %x1,長度爲1 bit,若是沒有協商則必須爲0

frame-rsv2 =

  • %x0 / %x1,長度爲1 bit,若是沒有協商則必須爲0

frame-rsv3 =

  • %x0 / %x1,長度爲1 bit,若是沒有協商則必須爲0

frame-opcode =

  • frame-opcode-non-control
  • frame-opcode-control
  • frame-opcode-cont

frame-opcode-non-control

  • %x1,文本幀
  • %x2,二進制幀
  • %x3-7,保留給未來的非控制幀
  • 長度爲4 bit

frame-opcode-control

  • %x8,鏈接關閉
  • %x9,ping幀
  • %xA,pong幀
  • %xB-F,保留給未來的控制幀
  • 長度爲4 bit

frame-masked

  • %x0,不添加掩碼,沒有frame-masking-key
  • %x1,添加掩碼,存在frame-masking-key
  • 長度爲1 bit

frame-payload-length

  • %x00-7D,長度爲7 bit
  • %x7E frame-payload-length-16,長度爲7+16 bit
  • %x7F frame-payload-length-63,長度爲7+64 bit

frame-payload-length-16

  • %x0000-FFFF,長度爲16 bit

frame-payload-length-63

  • %x0000000000000000-7FFFFFFFFFFFFFFF,長度爲64 bit

frame-masking-key

  • 4(%x00-FF),當frame-mask爲1時存在,長度爲32 bit

frame-payload-data

  • frame-masked-extension-data frame-masked-application-data,當frame-masked爲1時
  • frame-unmasked-extension-data frame-unmasked-application-data,當frame-masked爲0時

frame-masked-extension-data

  • *(%x00-FF),保留給未來的擴展,長度爲n*8,其中n>0

frame-masked-application-data

  • *(%x00-FF),長度爲n*8,其中n>0

frame-unmasked-extension-data

  • *(%x00-FF),保留給未來的擴展,長度爲n*8,其中n>0

frame-unmasked-application-data

  • *(%x00-FF),長度爲n*8,其中n>0

5.3 客戶端到服務端添加掩碼

添加掩碼的數據幀必須像5.2節定義的同樣,設置frame-masked字段爲1。

掩碼值像第5.2節說到的徹底包含在幀中的frame-masking-key上。它是用於對定義在同一節中定義的幀負載數據Payload data字段中的包含Extension dataApplication 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的長度。如跟在掩碼值後面的字節數組的數。

5.4 消息分片

消息分片的主要目的是容許發送一個未知長度且消息開始發送後不須要緩存的消息。若是消息不能被分片,那麼一端必須在緩存整個消息,所以這個消息的長度必須在第一個字節發送前就須要計算出來。若是有消息分片,服務端或者代理能夠選擇一個合理的緩存長度,當緩存區滿了之後,就想網絡發送一個片斷。

第二個消息分片使用的場景是不適合在一個邏輯通道內傳輸一個大的消息佔滿整個輸出頻道的多路複用場景。多路複用須要可以將消息進行自由的切割成更小的片斷來共享輸出頻道。(注意:多路複用的擴展不在這個文檔中討論)。

除非在擴展中另有規定,不然幀沒有語義的含義。若是客戶端和服務的沒有協商擴展字段,或者服務端和客戶端協商了一些擴展字段,而且代理可以徹底識別全部的協商擴展字段,在這些擴展字段存在的狀況下知道如何進行幀的合併和拆分,代理就可能會合並或者拆分幀。這個的一個含義是指在缺乏擴展字段的狀況下,發送者和接收者都不能依賴特定的幀邊界的存在。

消息分片相關的規則以下:

  • 一個未分片的消息包含一個設置了FIN字段(標記爲1)的單獨的幀和一個除0之外的操做碼。
  • 一個分片的消息包含一個未設置的FIN字段(標記爲0)的單獨的幀和一個除0之外的操做碼,而後跟着0個或者多個未設置FIN字段的幀和操做碼爲0的幀,而後以一個設置了FIN字段以及操做碼爲0的幀結束。一個分片的消息內容按幀順序組合後的payload字段,是等價於一個單獨的更大的消息payload字段中包含的值;然而,若是擴展字段存在,由於擴展字段定義了Extension data的解析方式,所以前面的結論可能不成立。例如:Extension data可能只出如今第一個片斷的開頭,並適用於接下來的片斷,或者可能每個片斷都有Extension data,可是隻適用於特定的片斷。在Extension data不存在時,下面的示例演示了消息分片是如何運做的。 示例:一個文本須要分紅三個片斷進行發送,第一個片斷包含的操做碼爲0x1而且未設置FIN字段,第二個片斷的操做碼爲0x0而且未設置FIN字段,第三個片斷的操做碼爲0x0而且設置了FIN字段。
  • 控制幀(見5.5節)可能被插入到分片消息的中間。控制幀不能被分片。
  • 消息片斷必須在發送端按照順序發送給接收端。
  • 除非在擴展中定義了這種嵌套的邏輯,不然一條消息分的片不能與另外一條消息分的片嵌套傳輸。
  • 終端必須有能力來處理在分片的消息中的控制幀。
  • 發送端可能會建立任意大小的非控制消息片斷。
  • 客戶端和服務端必須同時支持分片和不分片消息。
  • 控制幀不能被分片,而且代理不容許改變控制幀的片斷。
  • 若是有保留字段被使用而且代理不能理解這些字段的值時,那麼代理不能改變消息的片斷。
  • 在擴展字段已經被協商過,可是代理不知道協商擴展字段的具體語義時,代理不能改變任意消息的片斷。一樣的,擴展不能看到WebSocket握手(而且得不到通知內容)致使WebSocket的鏈接禁止改變鏈接過程當中任意的消息片斷。
  • 做爲這些規則的結論,全部的消息片斷都是同類型的,而且設置了第一個片斷的操做碼(opccode)字段。控制幀不能被分片,全部的消息分片類型必須是文本或者二進制,或者是保留的任意一個操做碼。

注:若是控制幀沒有被打斷,心跳(ping)的等待時間可能會變很長,例如在一個很大的消息以後。所以,在分片的消息傳輸中插入控制幀是有必要的。

實踐說明:若是擴展字段不存在,接收者不須要使用緩存來存儲下整個消息片斷來進行處理。例如:若是使用一個流式API,再收到部分幀的時候就能夠將數據交給上層應用。然而,這個假設對之後全部的WebSocket擴展可能不必定成立。

5.5 控制幀

控制幀是經過操做碼最高位的值爲1來進行區分的。當前已經定義的控制幀操做碼包括0x8(關閉),0x9(心跳Ping)和0xA(心跳Pong)。操做碼0xB-0xF沒有被定義,當前被保留下來作爲之後的控制幀。

控制幀是用於WebSocket的通訊狀態的。控制幀能夠被插入到消息片斷中進行傳輸。

全部的控制幀必須有一個126字節或者更小的負載長度,而且不能被分片。

5.5.1 關閉(Close)

控制幀的操做碼值是0x8。

關閉幀可能包含內容(body)(幀的「應用數據」部分)來代表鏈接關閉的緣由,例如終端的斷開,或者是終端收到了一個太大的幀,或者是終端收到了一個不符合預期的格式的內容。若是這個內容存在,內容的前兩個字節必須是一個無符號整型(按照網絡字節序)來表明在7.4節中定義的狀態碼。跟在這兩個整型字節以後的能夠是UTF-8編碼的的數據值(緣由),數據值的定義不在此文檔中。數據值不必定是要人能夠讀懂的,可是必須對於調試有幫助,或者能傳遞有關於當前打開的這條鏈接有關聯的信息。數據值不保證人必定能夠讀懂,因此不能把這些展現給終端用戶。

從客戶端發送給服務端的控制幀必須添加掩碼,具體見5.3節。

應用禁止在發送了關閉的控制幀後再發送任何的數據幀。

若是終端收到了一個關閉的控制幀而且沒有在之前發送一個關閉幀,那麼終端必須發送一個關閉幀做爲迴應。(當發送一個關閉幀做爲迴應時,終端一般會輸出它收到的狀態碼)響應的關閉幀應該儘快發送。終端可能會推遲發送關閉幀直到當前的消息都已經發送完成(例如:若是大多數分片的消息已經發送了,終端可能會在發送關閉幀以前將剩餘的消息片斷髮送出去)。然而,已經發送關閉幀的終端不能保證會繼續處理收到的消息。

在已經發送和收到了關閉幀後,終端認爲WebSocket鏈接以及關閉了,而且必須關閉底層的TCP鏈接。服務端必須立刻關閉底層的TCP鏈接,客戶端應該等待服務端關閉鏈接,可是也能夠在收到關閉幀之後任意時間關閉鏈接。例如:若是在合理的時間段內沒有收到TCP關閉指令。

若是客戶端和服務端咋同一個時間發送了關閉幀,兩個終端都會發送和接收到一條關閉的消息,而且應該認爲WebSocket鏈接已經關閉,同時關閉底層的TCP鏈接。

5.5.2 心跳Ping

心跳Ping幀包含的操做碼是0x9。

關閉幀可能包含「應用數據」。

若是收到了一個心跳Ping幀,那麼終端必須發送一個心跳Pong 幀做爲迴應,除非已經收到了一個關閉幀。終端應該儘快恢復Pong幀。Pong幀將會在5.5.3節討論。

終端可能會在創建鏈接後與鏈接關閉前中間的任意時間發送Ping幀。

注意:Ping幀多是用於保活或者用來驗證遠端是否仍然有應答。

5.5.3 心跳Pong

心跳Ping幀包含的操做碼是0xA。

5.5.2節詳細說明了Ping幀和Pong幀的要求。

做爲迴應發送的Pong幀必須完整攜帶Ping幀中傳遞過來的「應用數據」字段。

若是終端收到一個Ping幀可是沒有發送Pong幀來回應以前的pong幀,那麼終端可能選擇用Pong幀來回復最近處理的那個Ping幀。

Pong幀能夠被主動發送。這會做爲一個單項的心跳。預期外的Pong包的響應沒有規定。

5.6 數據幀

數據幀(例如非控制幀)的定義是操做碼的最高位值爲0。當前定義的數據幀操做嗎包含0x1(文本)、0x2(二進制)。操做碼0x3-0x7是被保留做爲非控制幀的操做碼。

數據幀會攜帶應用層/擴展層數據。操做碼決定了攜帶的數據解析方式:

文本

「負載字段」是用UTF-8編碼的文本數據。注意特殊的文本幀可能包含部分UTF-8序列;然而,整個消息必須是有效的UTF-8編碼數據。從新組合消息後無效的UTF-8編碼數據處理見8.1節。

二進制

「負載字段」是任意的二進制數據,二進制數據的解析僅僅依靠應用層。

5.7 示例

  • 一個單幀未添加掩碼的文本消息 0x81 0x05 0x48 0x65 0x6c 0x6c 0x6f (內容爲"Hello")
  • 一個單幀添加掩碼的文本消息 0x81 0x85 0x37 0xfa 0x21 0x3d 0x7f 0x9f 0x4d 0x51 0x58 (內容爲Hello")
  • 一個分片的未添加掩碼的文本消息 0x01 0x03 0x48 0x65 0x6c (內容爲"Hel") 0x80 0x02 0x6c 0x6f (內容爲」lo")
  • 未添加掩碼的Ping請求和添加掩碼的Ping響應(譯者注:即Pong) 0x89 0x05 0x48 0x65 0x6c 0x6c 0x6f (包含內容爲」Hello", 可是文本內容是任意的) 0x8a 0x85 0x37 0xfa 0x21 0x3d 0x7f 0x9f 0x4d 0x51 0x58 (包含內容爲」Hello", 匹配ping的內容)
  • 256字節的二進制數據放入一個未添加掩碼數據幀 0x82 0x7E 0x0100 [256 bytes of binary data\]
  • 64KB二進制數據在一個非掩碼幀中 0x82 0x7F 0x0000000000010000 [65536 bytes of binary data\]

5.8 擴展性

這個協議的設計初衷是容許擴展的,能夠在基礎協議上增長能力。終端的鏈接必須在握手的過程當中協商使用的全部擴展。在規範中提供了從0x3-0x7和0xB-0xF的操做碼,在數據幀Header中的「擴展數據」字段、frame-rsv一、frame-rsv二、frame-rsv3字段均可以用於擴展。擴展的協商討論將在之後的9.1節中詳細討論。下面是一些符合預期的擴展用法。下面的列表不完整,也不是規範中內容。

  • 「擴展數據」能夠放置在「負載數據「中的應用數據」以前的位置。
  • 保留的字段能夠在每一幀須要時被使用。
  • 保留的操做碼的值能夠被定義。
  • 若是須要更多的操做碼,那麼保留的操做碼字段能夠被定義。
  • 保留的字段或者「擴展」操做碼能夠在「負載數據」之中的分配額外的位置來定義,這樣能夠定義更大的操做碼或者更多的每一幀的字段。
相關文章
相關標籤/搜索