在WebSocket協(xié)議中,數(shù)據(jù)使用幀序列來傳輸。為避免混淆網(wǎng)絡(luò)中間件(例如攔截代理)和出于安全原因,第10.3節(jié)進一步討論,客戶端必須掩碼(mask)它發(fā)送到服務(wù)器的所有幀(更多詳細信息請參見5.3節(jié))。(注意不管WebSocket協(xié)議是否運行在TLS至上,掩碼都要做。) 當(dāng)收到一個沒有掩碼的幀時,服務(wù)器必須關(guān)閉連接。在這種情況下,服務(wù)器可能發(fā)送一個定義在7.4.1節(jié)的狀態(tài)碼1002(協(xié)議錯誤)的Close幀。服務(wù)器必須不掩碼發(fā)送到客戶端的所有幀。如果客戶端檢測到掩碼的幀,它必須關(guān)閉連接。在這種情況下,它可能使用定義在7.4.1節(jié)的狀態(tài)碼1002(協(xié)議錯誤)。(這些規(guī)則可能在未來規(guī)范中放寬。)
基本幀協(xié)議定義了帶有操作碼(opcode)的幀類型、負載長度、和用于“擴展數(shù)據(jù)”與“應(yīng)用數(shù)據(jù)”及它們一起定義的“負載數(shù)據(jù)”的指定位置。某些字節(jié)和操作嗎保留用于未來協(xié)議的擴展。
一個數(shù)據(jù)幀可以被客戶端或者服務(wù)器在打開階段握手完成之后和端點發(fā)送Close幀之前的任何時候傳輸(5.5.1節(jié))。
用于數(shù)據(jù)傳輸部分的報文格式是通過本節(jié)中詳細描述的ABNF來描述。(注意,不像本文檔的其他章節(jié),本節(jié)中的ABNF是在位(bit)組上操作。每一個位組的長度在注釋中指出。在編碼報文時,最重要的位是在ABNF的最左邊。)下圖給出了幀的高層次概述。在下圖和在本節(jié)后邊指定的ABNF之間沖突的,這個圖表是權(quán)威的。
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,除非一個擴展協(xié)商為非零值定義含義。如果收到一個非零值且沒有協(xié)商的擴展定義這個非零值的含義,接收端點必須失敗WebSokcket連接。
Opcode: 4 bits
定義了“負載數(shù)據(jù)”的解釋。如果收到一個未知的操作碼,接收端點必須失敗WebSocket連接。定義了以下值。
Mask: 1 bit
定義是否“負載數(shù)據(jù)”是掩碼的。如果設(shè)置為1,一個掩碼鍵出現(xiàn)在masking-key,且這個是用于根據(jù)5.3節(jié)解掩碼(unmask)“負載數(shù)據(jù)”。從客戶端發(fā)送到服務(wù)器的所有幀有這個位設(shè)置為1。
Payload length: 7 bits, 7+16 bits, 或者 7+64 bits
“負載數(shù)據(jù)”的長度,以字節(jié)為單位:如果0-125,這是負載長度。如果126,之后的兩字節(jié)解釋為一個16位的無符號整數(shù)是負載長度。如果127,之后的8字節(jié)解釋為一個64位的無符號整數(shù)(最高有效位必須是0)是負載長度。多字節(jié)長度數(shù)量以網(wǎng)絡(luò)字節(jié)順序來表示。注意,在所有情況下,最小數(shù)量的字節(jié)必須用于編碼長度,例如,一個124字節(jié)長的字符串的長度不能被編碼為序列126,0,124。負載長度是“擴展數(shù)據(jù)”長度+“應(yīng)用數(shù)據(jù)”長度。“擴展數(shù)據(jù)”長度可能是零,在這種情況下,負載長度是“應(yīng)用數(shù)據(jù)”長度。
Masking-key: 0 or 4 bytes
客戶端發(fā)送到服務(wù)器的所有幀通過一個包含在幀中的32位值來掩碼。如果mask位設(shè)置為1,則該字段存在,如果mask位設(shè)置為0,則該字段缺失。詳細信息請參見5.3節(jié) 客戶端到服務(wù)器掩碼。
Payload data: (x+y) bytes
“負載數(shù)據(jù)”定義為“擴展數(shù)據(jù)”連接“應(yīng)用數(shù)據(jù)”。
Extension data: x bytes
“擴展數(shù)據(jù)”是0字節(jié)除非已經(jīng)協(xié)商了一個擴展。任何擴展必須指定“擴展數(shù)據(jù)”的長度,或長度是如何計算的,以及擴展如何使用必須在打開階段握手期間協(xié)商。 如果存在,“擴展數(shù)據(jù)”包含在總負載長度中。
Application data: y bytes
任意的“應(yīng)用數(shù)據(jù)”,占用“擴展數(shù)據(jù)”之后幀的剩余部分?!皯?yīng)用數(shù)據(jù)”的長度等于負載長度減去“擴展數(shù)據(jù)”長度。
基本幀協(xié)議是由以下ABNF[RFC5234]正式定義。重要的是要注意這個數(shù)據(jù)是二進制表示的,而不是ASCII字符。因此,一個1位長度的字段取值為%x0 / %x1 是表示為單個位,其值為0或1,不是以ASCII編碼代表字符“0”或“1”的完整的字節(jié)(8位位組)。4位長度的字段值介于%0-F之間,是通過4位表示的,不是通過ASCII字符或這些值的完整字節(jié)(8位位組)。[RFC5234]沒有指定字符編碼:“規(guī)則解析為最終值的字符串,有時候被稱為字符。在ABNF中,一個字符僅僅是一個非負整數(shù)。在某些上下文中,一個值到一個字符集的特定映射(編碼)將被指定?!?在這里,指定的編碼是二進制編碼,每一個最終值是編碼到指定數(shù)量的比特中,每個字段是不同的。
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 ; 這條消息后續(xù)還有更多的幀
/ %x1 ; 這條消息的最終幀
; 1位長度
frame-rsv1 = %x0 / %x1
; 1位長度,必須是0,除非協(xié)商其他
frame-rsv2 = %x0 / %x1
; 1位長度,必須是0,除非協(xié)商其他
frame-rsv3 = %x0 / %x1
; 1位長度,必須是0,除非協(xié)商其他
frame-opcode = frame-opcode-non-control /
frame-opcode-control /
frame-opcode-cont
frame-opcode-cont = %x0 ; 幀繼續(xù)
frame-opcode-non-control= %x1 ; 文本幀
/ %x2 ; 二進制幀
/ %x3-7
; 4位長度,保留用于未來的非控制幀
frame-opcode-control = %x8 ; 連接關(guān)閉
/ %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 )
; 僅當(dāng)frame-masked 是 1時存在
; 32位長度
frame-payload-data = (frame-masked-extension-data
frame-masked-application-data)
; 當(dāng)frame-masked是1
/ (frame-unmasked-extension-data
frame-unmasked-application-data)
; 當(dāng)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.2節(jié)定義的字段frame-masked設(shè)置為1。 掩碼鍵完全包含在幀中,5.2節(jié)定義的frame-masking-key。它用于掩碼定義在相同章節(jié)的frame-payload-data 中的“負載數(shù)據(jù)”,其包含“擴展數(shù)據(jù)”和“應(yīng)用數(shù)據(jù)”。
掩碼鍵是由客戶端隨機選擇的32位值。當(dāng)準(zhǔn)備一個掩碼的幀時,客戶端必須從允許的32位值集合中選擇一個新的掩碼鍵。掩碼鍵需要是不可預(yù)測的;因此,掩碼鍵必須來自一個強大的熵源,且用于給定幀的掩碼鍵必須不容易被服務(wù)器/代理預(yù)測用于后續(xù)幀的掩碼鍵。掩碼鍵的不可預(yù)測性對防止惡意應(yīng)用的作者選擇出現(xiàn)在報文上的字節(jié)是必要的。RFC 4086[RFC4086]討論了什么需要一個用于安全敏感應(yīng)用的合適的熵源。
掩碼不影響“負載數(shù)據(jù)”的長度。變換掩碼數(shù)據(jù)到解掩碼數(shù)據(jù),或反之亦然,以下算法被應(yīng)用。相同的算法應(yīng)用,不管轉(zhuǎn)化的方向,例如,相同的步驟即應(yīng)用到掩碼數(shù)據(jù)也應(yīng)用到解掩碼數(shù)據(jù)。
變換數(shù)據(jù)的八位位組i ("transformed-octet-i")是原始數(shù)據(jù)的八位位組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表示,不包括掩碼鍵的長度。它是“負載數(shù)據(jù)”的長度,例如,跟在掩碼鍵后邊的字節(jié)數(shù)。
分片的主要目的是允許當(dāng)消息開始但不必緩沖該消息時發(fā)送一個未知大小的消息。如果消息不能被分片,那么端點將不得不緩沖整個消息以便在首字節(jié)發(fā)生之前統(tǒng)計出它的長度。對于分片,服務(wù)器或中間件可以選擇一個合適大小的緩沖,當(dāng)緩沖滿時,寫一個片段到網(wǎng)絡(luò)。 第二個分片的用例是用于多路復(fù)用,一個邏輯通道上的一個大消息獨占輸出通道是不可取的,因此多路復(fù)用需要可以分割消息為更小的分段來更好的共享輸出通道。(注意,多路復(fù)用擴展在本文檔中沒有描述)
除非另有擴展指定,幀沒有語義含義。一個中間件可能合并且/或分割幀,如果客戶端和服務(wù)器沒有協(xié)商擴展;或如果已協(xié)商了一些擴展,但中間件理解所有協(xié)商的擴展且知道如何去合并且/或分割在這些擴展中存在的幀。這方面的一個含義是,在沒有擴展情況下,發(fā)送者和接收者必須不依賴于特定幀邊界的存在。
以下規(guī)則應(yīng)用到分片:
一個分片的消息由單個帶有FIN位清零(5.2節(jié))和一個非0操作碼的幀組成,跟隨零個或多個帶有FIN位清零和操作碼設(shè)置為0的幀,且終止于一個帶有FIN位設(shè)置且0操作碼的幀。一個分片的消息概念上是等價于單個大的消息,其負載是等價于按順序串聯(lián)片段的負載;然而,在存在擴展的情況下,這個可能不適用擴展定義的“擴展數(shù)據(jù)”存在的解釋。例如,“擴展數(shù)據(jù)”可能僅在首個片段開始處存在且應(yīng)用到隨后的片段,或 “擴展數(shù)據(jù)”可以存在于僅用于到特定片段的每個片段。在沒有“擴展數(shù)據(jù)”的情況下,以下例子展示了分片如何工作。 例子:對于一個作為三個片段發(fā)送的文本消息,第一個片段將有一個0x1操作碼和一個FIN位清零,第二個片段將有一個0x0操作碼和一個FIN位清零,且第三個片段將有0x0操作碼和一個FIN位設(shè)置。
控制幀(參見5.5節(jié))可能被注入到一個分片消息的中間。控制幀本身必須不被分割。
注意:如果控制幀不能被插入,一個ping延遲,例如,如果跟著一個大消息將是非常長的。因此,要求在分片消息的中間處理控制幀。
實現(xiàn)注意:在沒有任何擴展時,一個接收者不必按順序緩沖整個幀來處理它。例如,如果使用了一個流式API,一個幀的一部分能被交付到應(yīng)用。但是,請注意這個假設(shè)可能不適用所有未來的WebSocket擴展。
控制幀由操作碼確定,其中操作碼最重要的位是1。當(dāng)前定義的用于控制幀的操作碼包括0x8 (Close)、0x9(Ping)、和0xA(Pong)。 操作碼0xB-0xF保留用于未來尚未定義的控制幀。
控制幀用于傳達有關(guān)WebSocket的狀態(tài)??刂茙梢圆迦氲椒制⒌闹虚g。
所有控制幀必須有一個125字節(jié)的負載長度或更少, 必須不被分段。
關(guān)閉(Close)幀包含0x8操作碼。
關(guān)閉幀可以包含內(nèi)容體(“幀的“應(yīng)用數(shù)據(jù)”部分)指示一個關(guān)閉的原因,例如端點關(guān)閉了、端點收到的幀太大、或端點收到的幀不符合端點期望的格式。如果有內(nèi)容體,內(nèi)容體的頭兩個字節(jié)必須是2字節(jié)的無符號整數(shù)(按網(wǎng)絡(luò)字節(jié)順序)代表一個在7.4節(jié)的/code/值定義的狀態(tài)碼。跟著2字節(jié)的整數(shù),內(nèi)容體可以包含UTF-8編碼的/reason/值,本規(guī)范沒有定義它的解釋。數(shù)據(jù)不必是人類可讀的但可能對調(diào)試或傳遞打開連接的腳本相關(guān)的信息是有用的。由于數(shù)據(jù)不保證人類可讀,客戶端必須不把它顯示給最終用戶。
客戶端發(fā)送到服務(wù)器的關(guān)閉幀必須根據(jù)5.3節(jié)被掩碼。
在應(yīng)用發(fā)送關(guān)閉幀之后,必須不發(fā)送任何更多的數(shù)據(jù)幀。
如果一個端點接收到一個關(guān)閉幀且先前沒有發(fā)送一個關(guān)閉幀,端點必須在響應(yīng)中發(fā)送一個關(guān)閉幀。(當(dāng)在響應(yīng)中發(fā)生關(guān)閉幀時,端點通?;厮退邮盏降臓顟B(tài)碼) 它應(yīng)該根據(jù)實際情況盡快這樣做。端點可以延遲發(fā)送關(guān)閉幀知道它當(dāng)前消息發(fā)送了(例如,如果一個分片消息的大多數(shù)已經(jīng)發(fā)送了,端點可以發(fā)送剩余的片段在發(fā)送一個關(guān)閉幀之前)。但是,不保證一個已經(jīng)發(fā)送關(guān)閉幀的端點將繼續(xù)處理數(shù)據(jù)。 發(fā)送并接收一個關(guān)閉消息后,一個端點認為WebSocket連接關(guān)閉了且必須關(guān)閉底層的TCP連接。服務(wù)器必須立即關(guān)閉底層TCP連接,客戶端應(yīng)該等待服務(wù)器關(guān)閉連接但可能在發(fā)送和接收一個關(guān)閉消息之后的任何時候關(guān)閉連接,例如,如果它沒有在一個合理的時間周期內(nèi)接收到服務(wù)器的TCP關(guān)閉。
如果客戶端和服務(wù)器同時都發(fā)送了一個關(guān)閉消息,兩個端點都將發(fā)送和接收一個關(guān)閉消息且應(yīng)該認為WebSocket連接關(guān)閉了并關(guān)閉底層TCP連接。
Ping幀包含0x9操作碼。
Ping幀可以包含“應(yīng)用數(shù)據(jù)”。
當(dāng)收到一個Ping幀時,一個端點必須在響應(yīng)中發(fā)送一個Pong幀,除非它早已接收到一個關(guān)閉幀。它應(yīng)該盡可能快地以Pong幀響應(yīng)。Pong幀在5.5.3節(jié)討論。
一個端點可以在連接建立之后并在連接關(guān)閉之前的任何時候發(fā)送一個Ping幀。 注意:一個Ping即可以充當(dāng)一個keepalive,也可以作為驗證遠程端點仍可響應(yīng)的手段。
Pong幀包含一個0xA操作碼。
5.5.2節(jié)詳細說明了應(yīng)用Ping和Pong幀的要求。
一個Pong幀在響應(yīng)中發(fā)送到一個Ping幀必須有在將回復(fù)的Ping幀的消息內(nèi)容體中發(fā)現(xiàn)的相同的“應(yīng)用數(shù)據(jù)”。
如果端點接收到一個Ping幀且尚未在響應(yīng)中發(fā)送Pong幀到之前的Ping幀,端點可以選擇僅為最近處理的Ping幀發(fā)送一個Pong幀。
一個Pong幀可以未經(jīng)請求的發(fā)送。這個充當(dāng)單向的心跳(heartbeat)。到未經(jīng)請求的Pong幀的一個響應(yīng)是不期望的。
數(shù)據(jù)幀(例如,非控制幀)由操作碼最高位是0的操作碼標(biāo)識。當(dāng)前為數(shù)據(jù)幀定義的操作碼包括0x1(文本)、0x2(二進制)。操作碼0x3-0x7保留用于未來尚未定義的非控制幀。
數(shù)據(jù)幀攜帶應(yīng)用層和/或擴展層數(shù)據(jù)。操作碼決定了數(shù)據(jù)的解釋:
Text
“負載數(shù)據(jù)”是編碼為UTF-8的文本數(shù)據(jù)。注意,一個特定的文本幀可能包括部分UTF-8序列;不管怎么樣,整個消息必須包含有效的UTF-8。重新組裝的消息中的無效的UTF-8的處理描述在8.1節(jié)。
Binary
“負載數(shù)據(jù)”是隨意的二進制數(shù)據(jù),其解釋僅僅是在應(yīng)用層。
未掩碼文件消息的單個幀
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響應(yīng)
0x89 0x05 0x48 0x65 0x6c 0x6c 0x6f
(包含內(nèi)容體"Hello"、但內(nèi)容體的內(nèi)容是隨意的)
0x8a 0x85 0x37 0xfa 0x21 0x3d 0x7f 0x9f 0x4d 0x51 0x58
(包含內(nèi)容體"Hello"、匹配ping的內(nèi)容體)
單個未掩碼幀中的256字節(jié)的二進制消息
0x82 0x7E 0x0100 [256字節(jié)的二進制數(shù)據(jù)]
單個未掩碼幀中的64KB的二進制消息
0x82 0x7F 0x0000000000010000 [65536字節(jié)的二進制數(shù)據(jù)]
協(xié)議被設(shè)計為允許擴展,這將增加功能到基礎(chǔ)協(xié)議。端點的一個連接必須在打開階段握手期間協(xié)商使用的任何擴展。本規(guī)范提供了用于擴展的操作碼0x3到0x7和0xB到0xF、“擴展數(shù)據(jù)”字段、和幀-rsv1、幀rsv2、和幀rsv3幀頭位。9.1節(jié)進一步討論了擴展協(xié)商。以下是一些預(yù)期使用的擴展。這個列表是不完整的也不規(guī)范的。
更多建議: