golang websocket
內容不斷更新,目前包括協議中握手和數據幀的分析
1.1 背景
1.2 協議概覽
協議包含兩部分:握手,數據傳輸。
客戶端的握手如下:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
服務端的握手如下:
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= Sec-WebSocket-Protocol: chat
客戶端和服務端都發送了握手,並且成功,數據傳輸即可開始。
1.3 發起握手
發起握手是為了兼容基於HTTP的服務端程序,這樣一個端口可以同時處理HTTP客戶端和WebSocket客戶端
因此WebSocket客戶端握手是一個HTTP Upgrade請求:
GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Origin: http://example.com Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13
握手中的域的順序是任意的。
5 數據幀
5.1 概述
WebScoket協議中,數據以幀序列的形式傳輸。
考慮到數據安全性,客戶端向服務器傳輸的數據幀必須進行掩碼處理。服務器若接收到未經過掩碼處理的數據幀,則必須主動關閉連接。
服務器向客戶端傳輸的數據幀一定不能進行掩碼處理。客戶端若接收到經過掩碼處理的數據幀,則必須主動關閉連接。
針對上情況,發現錯誤的一方可向對方發送close幀(狀態碼是1002,表示協議錯誤),以關閉連接。
5.2 幀協議
WebSocket數據幀結構如下圖所示:
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位
表示這是消息的最後一幀(結束幀),一個消息由一個或多個數據幀構成。若消息由一幀構成,起始幀即結束幀。
RSV1,RSV2,RSV3:各1位
MUST be 0 unless an extension is negotiated that defines meanings for non-zero values. If a nonzero value is received and none of the negotiated extensions defines the meaning of such a nonzero value, the receiving endpoint MUST _Fail the WebSocket Connection_.
這裏我翻譯不好,大致意思是如果未定義擴展,各位是0;如果定義了擴展,即為非0值。如果接收的幀此處非0,擴展中卻沒有該值的定義,那麽關閉連接。
OPCODE:4位
解釋PayloadData,如果接收到未知的opcode,接收端必須關閉連接。
0x0表示附加數據幀
0x1表示文本數據幀
0x2表示二進制數據幀
0x3-7暫時無定義,為以後的非控制幀保留
0x8表示連接關閉
0x9表示ping
0xA表示pong
0xB-F暫時無定義,為以後的控制幀保留
MASK:1位
用於標識PayloadData是否經過掩碼處理。如果是1,Masking-key域的數據即是掩碼密鑰,用於解碼PayloadData。客戶端發出的數據幀需要進行掩碼處理,所以此位是1。
Payload length:7位,7+16位,7+64位
PayloadData的長度(以字節為單位)。
如果其值在0-125,則是payload的真實長度。
如果值是126,則後面2個字節形成的16位無符號整型數的值是payload的真實長度。註意,網絡字節序,需要轉換。
如果值是127,則後面8個字節形成的64位無符號整型數的值是payload的真實長度。註意,網絡字節序,需要轉換。
長度表示遵循一個原則,用最少的字節表示長度(我理解是盡量減少不必要的傳輸)。舉例說,payload真實長度是124,在0-125之間,必須用前7位表示;不允許長度1是126或127,然後長度2是124,這樣違反原則。
Payload長度是ExtensionData長度與ApplicationData長度之和。ExtensionData長度可能是0,這種情況下,Payload長度即是ApplicationData長度。
WebSocket協議規定數據通過幀序列傳輸。
客戶端必須對其發送到服務器的所有幀進行掩碼處理。
服務器一旦收到無掩碼幀,將關閉連接。服務器可能發送一個狀態碼是1002(表示協議錯誤)的Close幀。
而服務器發送客戶端的數據幀不做掩碼處理,一旦客戶端發現經過掩碼處理的幀,將關閉連接。客戶端可能使用狀態碼1002。
消息分片
分片目的是發送長度未知的消息。如果不分片發送,即一幀,就需要緩存整個消息,計算其長度,構建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必須等待消息傳輸完成,才能發送出去,會導致較大的延遲。為了避免類似問題,需要允許控制幀夾雜在消息分片之間。
控制幀
根據官方文檔整理,官方文檔參考http://datatracker.ietf.org/doc/rfc6455/?include_text=1
golang websocket