# 數據幀
## 5.1概述
在WebSocket協議中,數據使用幀序列來傳輸。為避免混淆網絡中間件(例如攔截代理)和出于安全原因,第10.3節進一步討論,客戶端必須掩碼(mask)它發送到服務器的所有幀(更多詳細信息請參見5.3節)。(注意不管WebSocket協議是否運行在TLS至上,掩碼都要做。) 當收到一個沒有掩碼的幀時,服務器必須關閉連接。在這種情況下,服務器可能發送一個定義在7.4.1節的狀態碼1002(協議錯誤)的Close幀。服務器必須不掩碼發送到客戶端的所有幀。如果客戶端檢測到掩碼的幀,它必須關閉連接。在這種情況下,它可能使用定義在7.4.1節的狀態碼1002(協議錯誤)。(這些規則可能在未來規范中放寬。)
基本幀協議定義了帶有操作碼(opcode)的幀類型、負載長度、和用于“擴展數據”與“應用數據”及它們一起定義的“負載數據”的指定位置。某些字節和操作嗎保留用于未來協議的擴展。
一個數據幀可以被客戶端或者服務器在打開階段握手完成之后和端點發送Close幀之前的任何時候傳輸(5.5.1節)。
## [](https://github.com/zhangkaitao/websocket-protocol/wiki/5.%E6%95%B0%E6%8D%AE%E5%B8%A7#52%E5%9F%BA%E6%9C%AC%E5%B8%A7%E5%8D%8F%E8%AE%AE)5.2基本幀協議
用于數據傳輸部分的報文格式是通過本節中詳細描述的ABNF來描述。(注意,不像本文檔的其他章節,本節中的ABNF是在位(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 bit
必須是0,除非一個擴展協商為非零值定義含義。如果收到一個非零值且沒有協商的擴展定義這個非零值的含義,接收端點必須*失敗WebSokcket連接*。
Opcode: 4 bits
定義了“負載數據”的解釋。如果收到一個未知的操作碼,接收端點必須*失敗WebSocket連接*。定義了以下值。
* %x0 代表一個繼續幀
* %x1 代表一個文本幀
* %x2 代表一個二進制幀
* %x3-7 保留用于未來的非控制幀
* %x8 代表連接關閉
* %x9 代表ping
* %xA 代表pong
* %xB-F 保留用于未來的控制幀
Mask: 1 bit
定義是否“負載數據”是掩碼的。如果設置為1,一個掩碼鍵出現在masking-key,且這個是用于根據5.3節解掩碼(unmask)“負載數據”。從客戶端發送到服務器的所有幀有這個位設置為1。
Payload length: 7 bits, 7+16 bits, 或者 7+64 bits
“負載數據”的長度,以字節為單位:如果0-125,這是負載長度。如果126,之后的兩字節解釋為一個16位的無符號整數是負載長度。如果127,之后的8字節解釋為一個64位的無符號整數(最高有效位必須是0)是負載長度。多字節長度數量以網絡字節順序來表示。注意,在所有情況下,最小數量的字節必須用于編碼長度,例如,一個124字節長的字符串的長度不能被編碼為序列126,0,124。負載長度是“擴展數據”長度+“應用數據”長度。“擴展數據”長度可能是零,在這種情況下,負載長度是“應用數據”長度。
Masking-key: 0 or 4 bytes
客戶端發送到服務器的所有幀通過一個包含在幀中的32位值來掩碼。如果mask位設置為1,則該字段存在,如果mask位設置為0,則該字段缺失。詳細信息請參見5.3節 客戶端到服務器掩碼。
Payload data: (x+y) bytes
“負載數據”定義為“擴展數據”連接“應用數據”。
Extension data: x bytes
“擴展數據”是0字節除非已經協商了一個擴展。任何擴展必須指定“擴展數據”的長度,或長度是如何計算的,以及擴展如何使用必須在打開階段握手期間協商。 如果存在,“擴展數據”包含在總負載長度中。
Application data: y bytes
任意的“應用數據”,占用“擴展數據”之后幀的剩余部分。“應用數據”的長度等于負載長度減去“擴展數據”長度。
基本幀協議是由以下ABNF[[RFC5234](http://tools.ietf.org/html/rfc5234)]正式定義。重要的是要注意這個數據是二進制表示的,而不是ASCII字符。因此,一個1位長度的字段取值為%x0 / %x1 是表示為單個位,其值為0或1,不是以ASCII編碼代表字符“0”或“1”的完整的字節(8位位組)。4位長度的字段值介于%0-F之間,是通過4位表示的,不是通過ASCII字符或這些值的完整字節(8位位組)。[[RFC5234](http://tools.ietf.org/html/rfc5234)]沒有指定字符編碼:“規則解析為最終值的字符串,有時候被稱為字符。在ABNF中,一個字符僅僅是一個非負整數。在某些上下文中,一個值到一個字符集的特定映射(編碼)將被指定。” 在這里,指定的編碼是二進制編碼,每一個最終值是編碼到指定數量的比特中,每個字段是不同的。
~~~
ws-frame = frame-fin ; 1位長度
frame-rsv1 ; 1位長度
frame-rsv2 ; 1位長度
frame-rsv3 ; 1位長度
frame-opcode ; 4位長度
frame-masked ; 1位長度
frame-payload-length ; 或者 7、 7+16、
; 或者7+64 位長度
[ frame-masking-key ] ; 32位長度
frame-payload-data ; n*8位長度; n>=0
frame-fin = %x0 ; 這條消息后續還有更多的幀
/ %x1 ; 這條消息的最終幀
; 1位長度
frame-rsv1 = %x0 / %x1
; 1位長度,必須是0,除非協商其他
frame-rsv2 = %x0 / %x1
; 1位長度,必須是0,除非協商其他
frame-rsv3 = %x0 / %x1
; 1位長度,必須是0,除非協商其他
frame-opcode = frame-opcode-non-control /
frame-opcode-control /
frame-opcode-cont
frame-opcode-cont = %x0 ; 幀繼續
frame-opcode-non-control= %x1 ; 文本幀
/ %x2 ; 二進制幀
/ %x3-7
; 4位長度,保留用于未來的非控制幀
frame-opcode-control = %x8 ; 連接關閉
/ %x9 ; ping
/ %xA ; pong
/ %xB-F ; 保留用于未來的控制幀
; 4位長度
frame-masked = %x0
; 幀沒有掩碼,沒有frame-masking-key
/ %x1
; 幀被掩碼,存在frame-masking-key
; 1位長度
frame-payload-length = ( %x00-7D )
/ ( %x7E frame-payload-length-16 )
/ ( %x7F frame-payload-length-63 )
; 分別7, 7+16, or 7+64位長度
frame-payload-length-16 = %x0000-FFFF ; 16位長度
frame-payload-length-63 = %x0000000000000000-7FFFFFFFFFFFFFFF
; 64位長度
frame-masking-key = 4( %x00-FF )
; 僅當frame-masked 是 1時存在
; 32位長度
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
~~~
## [](https://github.com/zhangkaitao/websocket-protocol/wiki/5.%E6%95%B0%E6%8D%AE%E5%B8%A7#53%E5%AE%A2%E6%88%B7%E7%AB%AF%E5%88%B0%E6%9C%8D%E5%8A%A1%E5%99%A8%E6%8E%A9%E7%A0%81)5.3.客戶端到服務器掩碼
一個掩碼的幀必須有5.2節定義的字段frame-masked設置為1。 掩碼鍵完全包含在幀中,5.2節定義的frame-masking-key。它用于掩碼定義在相同章節的frame-payload-data 中的“負載數據”,其包含“擴展數據”和“應用數據”。
掩碼鍵是由客戶端隨機選擇的32位值。當準備一個掩碼的幀時,客戶端必須從允許的32位值集合中選擇一個新的掩碼鍵。掩碼鍵需要是不可預測的;因此,掩碼鍵必須來自一個強大的熵源,且用于給定幀的掩碼鍵必須不容易被服務器/代理預測用于后續幀的掩碼鍵。掩碼鍵的不可預測性對防止惡意應用的作者選擇出現在報文上的字節是必要的。[RFC 4086](http://tools.ietf.org/html/rfc4086)[[RFC4086](http://tools.ietf.org/html/rfc4086)]討論了什么需要一個用于安全敏感應用的合適的熵源。
掩碼不影響“負載數據”的長度。變換掩碼數據到解掩碼數據,或反之亦然,以下算法被應用。相同的算法應用,不管轉化的方向,例如,相同的步驟即應用到掩碼數據也應用到解掩碼數據。
變換數據的八位位組i ("transformed-octet-i")是原始數據的八位位組i("original-octet-i")異或(XOR)i取模4位置的掩碼鍵的八位位組("masking-key-octet-j"):
~~~
j = i MOD 4
transformed-octet-i = original-octet-i XOR masking-key-octet-j
~~~
負載長度,在幀中以frame-payload-length表示,不包括掩碼鍵的長度。它是“負載數據”的長度,例如,跟在掩碼鍵后邊的字節數。
## [](https://github.com/zhangkaitao/websocket-protocol/wiki/5.%E6%95%B0%E6%8D%AE%E5%B8%A7#54%E5%88%86%E7%89%87fragmentation)5.4.分片(Fragmentation)
分片的主要目的是允許當消息開始但不必緩沖該消息時發送一個未知大小的消息。如果消息不能被分片,那么端點將不得不緩沖整個消息以便在首字節發生之前統計出它的長度。對于分片,服務器或中間件可以選擇一個合適大小的緩沖,當緩沖滿時,寫一個片段到網絡。 第二個分片的用例是用于多路復用,一個邏輯通道上的一個大消息獨占輸出通道是不可取的,因此多路復用需要可以分割消息為更小的分段來更好的共享輸出通道。(注意,多路復用擴展在本文檔中沒有描述)
除非另有擴展指定,幀沒有語義含義。一個中間件可能合并且/或分割幀,如果客戶端和服務器沒有協商擴展;或如果已協商了一些擴展,但中間件理解所有協商的擴展且知道如何去合并且/或分割在這些擴展中存在的幀。這方面的一個含義是,在沒有擴展情況下,發送者和接收者必須不依賴于特定幀邊界的存在。
以下規則應用到分片:
* 一個沒有分片的消息由單個帶有FIN位設置(5.2節)和一個非0操作碼的幀組成。
* 一個分片的消息由單個帶有FIN位清零(5.2節)和一個非0操作碼的幀組成,跟隨零個或多個帶有FIN位清零和操作碼設置為0的幀,且終止于一個帶有FIN位設置且0操作碼的幀。一個分片的消息概念上是等價于單個大的消息,其負載是等價于按順序串聯片段的負載;然而,在存在擴展的情況下,這個可能不適用擴展定義的“擴展數據”存在的解釋。例如,“擴展數據”可能僅在首個片段開始處存在且應用到隨后的片段,或 “擴展數據”可以存在于僅用于到特定片段的每個片段。在沒有“擴展數據”的情況下,以下例子展示了分片如何工作。 例子:對于一個作為三個片段發送的文本消息,第一個片段將有一個0x1操作碼和一個FIN位清零,第二個片段將有一個0x0操作碼和一個FIN位清零,且第三個片段將有0x0操作碼和一個FIN位設置。
* 控制幀(參見5.5節)可能被注入到一個分片消息的中間。控制幀本身必須不被分割。
* 消息分片必須按發送者發送順序交付給收件人。
* 片段中的一個消息必須不能與片段中的另一個消息交替,除非已協商了一個能解釋交替的擴展。
* 一個端點必須能處理一個分片消息中間的控制幀。
* 一個發送者可以位非控制消息創建任何大小的片段。
* 客戶端和服務器必須支持接收分片和非分片的消息。
* 由于控制幀不能被分片,一個中間件必須不嘗試改變控制幀的分片。
* 如果使用了任何保留的位值且這些值的意思對中間件是未知的,一個中間件必須不改變一個消息的分片。
* 在一個連接上下文中,已經協商了擴展且中間件不知道協商的擴展的語義,一個中間件必須不改變任何消息的分片。同樣,沒有看見WebSocket握手(且沒被通知有關它的內容)、導致一個WebSocket連接的一個中間件,必須不改變這個鏈接的任何消息的分片。
* 由于這些規則,一個消息的所有分片是相同類型,以第一個片段的操作碼設置。因為控制幀不能被分片,用于一個消息中的所有分片的類型必須或者是文本、或者二進制、或者一個保留的操作碼。
注意:如果控制幀不能被插入,一個ping延遲,例如,如果跟著一個大消息將是非常長的。因此,要求在分片消息的中間處理控制幀。
實現注意:在沒有任何擴展時,一個接收者不必按順序緩沖整個幀來處理它。例如,如果使用了一個流式API,一個幀的一部分能被交付到應用。但是,請注意這個假設可能不適用所有未來的WebSocket擴展。
## [](https://github.com/zhangkaitao/websocket-protocol/wiki/5.%E6%95%B0%E6%8D%AE%E5%B8%A7#55%E6%8E%A7%E5%88%B6%E5%B8%A7)5.5.控制幀
控制幀由操作碼確定,其中操作碼最重要的位是1。當前定義的用于控制幀的操作碼包括0x8 (Close)、0x9(Ping)、和0xA(Pong)。 操作碼0xB-0xF保留用于未來尚未定義的控制幀。
控制幀用于傳達有關WebSocket的狀態。控制幀可以插入到分片消息的中間。
所有控制幀必須有一個125字節的負載長度或更少, 必須不被分段。
### [](https://github.com/zhangkaitao/websocket-protocol/wiki/5.%E6%95%B0%E6%8D%AE%E5%B8%A7#551close)5.5.1.Close
關閉(Close)幀包含0x8操作碼。
關閉幀可以包含內容體(“幀的“應用數據”部分)指示一個關閉的原因,例如端點關閉了、端點收到的幀太大、或端點收到的幀不符合端點期望的格式。如果有內容體,內容體的頭兩個字節必須是2字節的無符號整數(按網絡字節順序)代表一個在7.4節的/code/值定義的狀態碼。跟著2字節的整數,內容體可以包含UTF-8編碼的/reason/值,本規范沒有定義它的解釋。數據不必是人類可讀的但可能對調試或傳遞打開連接的腳本相關的信息是有用的。由于數據不保證人類可讀,客戶端必須不把它顯示給最終用戶。
客戶端發送到服務器的關閉幀必須根據5.3節被掩碼。
在應用發送關閉幀之后,必須不發送任何更多的數據幀。
如果一個端點接收到一個關閉幀且先前沒有發送一個關閉幀,端點必須在響應中發送一個關閉幀。(當在響應中發生關閉幀時,端點通常回送它接收到的狀態碼) 它應該根據實際情況盡快這樣做。端點可以延遲發送關閉幀知道它當前消息發送了(例如,如果一個分片消息的大多數已經發送了,端點可以發送剩余的片段在發送一個關閉幀之前)。但是,不保證一個已經發送關閉幀的端點將繼續處理數據。 發送并接收一個關閉消息后,一個端點認為WebSocket連接關閉了且必須關閉底層的TCP連接。服務器必須立即關閉底層TCP連接,客戶端應該等待服務器關閉連接但可能在發送和接收一個關閉消息之后的任何時候關閉連接,例如,如果它沒有在一個合理的時間周期內接收到服務器的TCP關閉。
如果客戶端和服務器同時都發送了一個關閉消息,兩個端點都將發送和接收一個關閉消息且應該認為WebSocket連接關閉了并關閉底層TCP連接。
### [](https://github.com/zhangkaitao/websocket-protocol/wiki/5.%E6%95%B0%E6%8D%AE%E5%B8%A7#552-ping)5.5.2\. Ping
Ping幀包含0x9操作碼。
Ping幀可以包含“應用數據”。
當收到一個Ping幀時,一個端點必須在響應中發送一個Pong幀,除非它早已接收到一個關閉幀。它應該盡可能快地以Pong幀響應。Pong幀在5.5.3節討論。
一個端點可以在連接建立之后并在連接關閉之前的任何時候發送一個Ping幀。 注意:一個Ping即可以充當一個keepalive,也可以作為驗證遠程端點仍可響應的手段。
### [](https://github.com/zhangkaitao/websocket-protocol/wiki/5.%E6%95%B0%E6%8D%AE%E5%B8%A7#553-pong)5.5.3\. Pong
Pong幀包含一個0xA操作碼。
5.5.2節詳細說明了應用Ping和Pong幀的要求。
一個Pong幀在響應中發送到一個Ping幀必須有在將回復的Ping幀的消息內容體中發現的相同的“應用數據”。
如果端點接收到一個Ping幀且尚未在響應中發送Pong幀到之前的Ping幀,端點可以選擇僅為最近處理的Ping幀發送一個Pong幀。
一個Pong幀可以未經請求的發送。這個充當單向的心跳(heartbeat)。到未經請求的Pong幀的一個響應是不期望的。
## [](https://github.com/zhangkaitao/websocket-protocol/wiki/5.%E6%95%B0%E6%8D%AE%E5%B8%A7#56%E6%95%B0%E6%8D%AE%E5%B8%A7)5.6.數據幀
數據幀(例如,非控制幀)由操作碼最高位是0的操作碼標識。當前為數據幀定義的操作碼包括0x1(文本)、0x2(二進制)。操作碼0x3-0x7保留用于未來尚未定義的非控制幀。
數據幀攜帶應用層和/或擴展層數據。操作碼決定了數據的解釋:
Text
“負載數據”是編碼為UTF-8的文本數據。注意,一個特定的文本幀可能包括部分UTF-8序列;不管怎么樣,整個消息必須包含有效的UTF-8。重新組裝的消息中的無效的UTF-8的處理描述在8.1節。
Binary
“負載數據”是隨意的二進制數據,其解釋僅僅是在應用層。
## [](https://github.com/zhangkaitao/websocket-protocol/wiki/5.%E6%95%B0%E6%8D%AE%E5%B8%A7#57%E7%A4%BA%E4%BE%8B)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響應
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字節的二進制數據]
* 單個未掩碼幀中的64KB的二進制消息
0x82 0x7F 0x0000000000010000 [65536字節的二進制數據]
## [](https://github.com/zhangkaitao/websocket-protocol/wiki/5.%E6%95%B0%E6%8D%AE%E5%B8%A7#58%E5%8F%AF%E6%89%A9%E5%B1%95%E6%80%A7)5.8.可擴展性
協議被設計為允許擴展,這將增加功能到基礎協議。端點的一個連接必須在打開階段握手期間協商使用的任何擴展。本規范提供了用于擴展的操作碼0x3到0x7和0xB到0xF、“擴展數據”字段、和幀-rsv1、幀rsv2、和幀rsv3幀頭位。9.1節進一步討論了擴展協商。以下是一些預期使用的擴展。這個列表是不完整的也不規范的。
* “擴展數據”可以放置在“負載數據”中的“應用數據”之前。
* 保留的位可以分配給需要的每個幀。
* 保留的操作碼值能被定義。
* 如果需要更多的操作碼值,保留的位可以分配給操作碼字段。
* 一個保留的位或一個“擴展”操作碼可以定義以從“負載數據”中分配額外的位來定義更大的操作碼或更多的每幀位。