之前總結了關於Websocket協議的握手連接方式等其他細節,現在對socket連接建立后的數據幀傳輸和關閉細節總結。
一、數據幀格式
數據傳輸使用的是一系列數據幀,出於安全考慮和避免網絡截獲,客戶端發送的數據幀必須進行掩碼處理后才能發送到服務器,不論是否是在TLS安全協議上都要進行掩碼處理。服務器如果沒有收到掩碼處理的數據幀時應該關閉連接,發送一個1002的狀態碼。服務器不能將發送到客戶端的數據進行掩碼處理,如果客戶端收到掩碼處理的數據幀必須關閉連接。
基本的數據幀為一個opcode、一個payload長度和發送的應用數據,根據ABNF的定義,詳細信息如下圖
這里使用的是數據存儲的位(bit),當進行加密的時候,最終要的一位就是最左邊的第一個。
- FIN :1bit ,表示是消息的最后一幀,如果消息只有一幀那么第一幀也就是最后一幀。
- RSV1,RSV2,RSV3:每個1bit,必須是0,除非擴展定義為非零。如果接受到的是非零值但是擴展沒有定義,則需要關閉連接。
- Opcode:4bit,解釋Payload數據,規定有以下不同的狀態,如果是未知的,接收方必須馬上關閉連接。狀態如下:0x0(附加數據幀) 0x1(文本數據幀) 0x2(二進制數據幀) 0x3-7(保留為之后非控制幀使用) 0xB-F(保留為后面的控制幀使用) 0x8(關閉連接幀) 0x9(ping) 0xA(pong)
- Mask:1bit,掩碼,定義payload數據是否進行了掩碼處理,如果是1表示進行了掩碼處理。
Masking-key域的數據即是掩碼密鑰,用於解碼PayloadData。客戶端發出的數據幀需要進行掩碼處理,所以此位是1。
- Payload length:7位,7 + 16位,7+64位,payload數據的長度,如果是0-125,就是真實的payload長度,如果是126,那么接着后面的2個字節對應的16位無符號整數就是payload數據長度;如果是127,那么接着后面的8個字節對應的64位無符號整數就是payload數據的長度。
- Masking-key:0到4字節,如果MASK位設為1則有4個字節的掩碼解密密鑰,否則就沒有。
- Payload data:任意長度數據。包含有擴展定義數據和應用數據,如果沒有定義擴展則沒有此項,僅含有應用數據。
二、客戶端到服務器端掩碼處理
三、消息分片
分片目的是發送長度未知的消息。如果不分片發送,即一幀,就需要緩存整個消息,計算其長度,構建frame並發送;使用分片的話,可使用一個大小合適的buffer,用消息內容填充buffer,填滿即發送出去。
分片規則:
1.一個未分片的消息只有一幀(FIN為1,opcode非0)
2.一個分片的消息由起始幀(FIN為0,opcode非0),若干(0個或多個)幀(FIN為0,opcode為0),結束幀(FIN為1,opcode為0)。
3.控制幀可以出現在分片消息中間,但控制幀本身不允許分片。
4.分片消息必須按次序逐幀發送。
5.如果未協商擴展的情況下,兩個分片消息的幀之間不允許交錯。
6.能夠處理存在於分片消息幀之間的控制幀
7.發送端為非控制消息構建長度任意的分片
8.client和server兼容接收分片消息與非分片消息
9.控制幀不允許分片,中間媒介不允許改變分片結構(即為控制幀分片)
10.如果使用保留位,中間媒介不知道其值表示的含義,那么中間媒介不允許改變消息的分片結構
11.如果協商擴展,中間媒介不知道,那么中間媒介不允許改變消息的分片結構,同樣地,如果中間媒介不了解一個連接的握手信息,也不允許改變該連接的消息的分片結構
12.由於上述規則,一個消息的所有分片是同一數據類型(由第一個分片的opcode定義)的數據。因為控制幀不允許分片,所以一個消息的所有分片的數據類型是文本、二進制、opcode保留類型中的一種。
需要注意的是,如果控制幀不允許夾雜在一個消息的分片之間,延遲會較大,比如說當前正在傳輸一個較大的消息,此時的ping必須等待消息傳輸完成,才能發送出去,會導致較大的延遲。為了避免類似問題,需要允許控制幀夾雜在消息分片之間。
數據幀示例:
未掩碼處理的文本單數據幀: 0x81 0x05 0x48 0x65 0x6c 0x6c 0x6f (contains "Hello")
掩碼處理的文本單數據幀: 0x81 0x85 0x37 0xfa 0x21 0x3d 0x7f 0x9f 0x4d 0x51 0x58
分片未掩碼處理的文本消息: 0x01 0x03 0x48 0x65 0x6c (contains "Hel")
0x80 0x02 0x6c 0x6f (contains "lo")
未掩碼處理的Ping請求和掩碼處理的響應:
0x89 0x05 0x48 0x65 0x6c 0x6c 0x6f (contains a body of "Hello", but the contents of the body are arbitrary)
0x8a 0x85 0x37 0xfa 0x21 0x3d 0x7f 0x9f 0x4d 0x51 0x58 (contains a body of "Hello", matching the body of the ping)
64K的二進制數據:0x82 0x7F 0x0000000000010000 [65536 bytes of binary data]
四、發送和接收數據
1、發送
- 端點必須確保WebSocket連接處於OPEN狀態。如果在任何時刻WebSocket連接的狀態改變了,端點必須終止以下步驟。
- 端點必須封裝/data/到一個WebSocket幀。如果要發送的數據太大或如果在端點想要開始發生數據時數據作為一個整體不可用,端點可以交替地封裝數據到一系列的幀中。
- 第一個包含數據的幀的操作碼(幀-opcode)必須設置為適當的值用於接收者解釋數據是文本還是二進制數據。
- 包含數據的最后幀的FIN位(幀-fin)必須設置位1。
- 如果數據正由客戶端發送,幀被掩碼。
- 如果任何擴展已經協商用於WebSocket連接,額外的考慮可以按照這些擴展定義來應用。
- 已成形的幀必須在底層網絡連接之上傳輸。
2、接收
為了接收WebSocket數據,端點監聽底層網絡連接。傳入數據必須解析為WebSocket幀。當接收到一個數據幀時,端點必須注意由操作碼(幀-opcode)定義的數據的/type/。這個幀的“應用數據”被定義為消息的/data/。如果幀由一個未分片的消息組成,這是說已經接收到一個WebSocket消息,其類型為/type/且數據為/data/。如果幀是一個分片消息的一部分,隨后數據幀的“應用數據”連接在一起形成/data/。當接收到由FIN位(幀-fin)指示的最后的片段時,這是說已經接收到一個WebSocket消息,其數據為/data/(由連續片段的“應用數據”組成)且類型為/type/(分配消息的第一個幀指出)。隨后的數據幀必須被解釋為屬於一個新的WebSocket消息。
擴展可以改變數據如何讀的語義,尤其包括什么組成一個消息的邊界。擴展,除了在負載中的“應用數據”之前添加“擴展數據”外,也可以修改“應用數據”(例如壓縮它)。服務器必須為從客戶端接收到的數據幀移除掩碼。
五、Websocket關閉
通信的兩端中任意一端關閉都可以關閉socket連接,關閉時應該清楚所有的TCP連接資源和TLS回話的資源,同時要丟棄所有的可能接收的字節數據。首先關閉的一方一般都應該是服務器端,然后處於TIME_WAIT狀態。
為了使用一個狀態碼關閉websocket,一端必須發送一個關閉的控制幀,當兩端都發送了關閉數據幀時,雙方都要關閉所有的連接資源。控制幀為一個“狀態碼”和一個“原因說明”,當關閉之后,雙方處於CLOSED狀態。