摘要:可以有消息體,指明消息原因,可作為日志進行記錄。端點在接受到關閉幀后,可以延遲響應關閉幀,繼續發送或接受數據幀,但不保證一個已經發送關閉幀的端點繼續處理數據。發送并接收了關閉幀的端點,被認為是關閉了連接,其必須關閉底層的連接。
參考文章
websocket RFC github 中文翻譯
Websocket RFC 文檔
workerman websocket 協議實現
協議組成協議由一個開放握手組成,其次是基本的消息成幀,分層的TCP.
解決的問題基于瀏覽器的機制,實現客戶端與服務端的雙向通信.
協議概述來自客戶端握手
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
以上,頭順序無所謂.
一旦客戶端和服務器都發送了握手信號,如果握手成功,數據傳輸部分啟動。這是雙方溝通的渠道,獨立于另一方,可隨意發送數據。
服務器的響應,不是隨意的,需要遵循一定的規則 請參考RFC 文檔 第 6/7頁:
獲取客戶端請求的 Sec-Weboscket-Key 字段值,去除收尾空白字符
與全球唯一標識符拼接 258EAFA5-E914-47DA-95CA-C5AB0DC85B11
sha1 加密(短格式)
base64 加密
PHP 程序描述:
$client_key = "dGhlIHNhbXBsZSBub25jZQ=="; $client_key = trim($client_key); $guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; $key = $client_key . $guid; $key = sha1($key , true); $key = base64_encode($key);
上述結果得出的值即是服務端返回給客戶端握手的 Sec-Websocket-Accept 頭字段值.
關閉鏈接接收到一個 0x8 控制幀后,鏈接也許立即斷開,也許在接收完剩下的數據后斷開。
可以有消息體,指明消息原因,可作為日志進行記錄。
應用發送關閉幀后必須不在發送更多數據幀。
如果一個端點接受到一個關閉幀且先前沒有發送關閉幀,則必須發送一個關閉幀。
端點在接受到關閉幀后,可以延遲響應關閉幀,繼續發送或接受數據幀,但不保證一個已經發送關閉幀的端點繼續處理數據。
發送并接收了關閉幀的端點,被認為是關閉了 websocket 連接,其必須關閉底層的 TCP 連接。
設計理念基于框架而不是基于流/文本或二進制幀.
鏈接要求 針對客戶端要求握手必須是一個有效的 HTTP 請求
請求的方法必須為 GET,且 HTTP 版本必須是 1.1
請求的 REQUEST-URI 必須符合文檔規定的要求(詳情查看 Page 13)
請求必須包含 Host 頭
請求必須包含 Upgrade: websocket 頭,值必須為 websocket
請求必須包含 Connection: Upgrade 頭,值必須為 Upgrade
請求必須包含 Sec-WebSocket-Key 頭
請求必須包含 Sec-WebSocket-Version: 13 頭,值必須為 13
請求必須包含 Origin 頭
請求可能包含 Sec-WebSocket-Protocol 頭,規定子協議
請求可能包含 Sec-WebSocket-Extensions ,規定協議擴展
請求可能包含其他字段,如 cookie 等
不符合上述要求的服務器響應,客戶端都會斷開鏈接.
如果響應不包含 Sec-WebSocket-Protocol 中指定的子協議,客戶端斷開
如果響應 HTTP/1.1 101 Switching Protocols 狀態碼不是 101,客戶端斷開
針對服務端要求如果請求是 HTTP/1.1 或更高的 GET 請求,包含 REQUEST-URI 則應正確地按照文檔要求進行解析.
必須驗證 Host 字段
Upgrade 頭字段值必須是大小寫不敏感的 websocket
Sec-WebSocket-keyd 解碼時長度為 16Byte
Sec-WebSocket-Version 值必須是 13
Host 如果沒有被包含,則鏈接不應該被解釋為瀏覽器發起的行為
Sec-WebSocket-Protocol 中列出的客戶端請求的子協議,服務端應按照優先順序排列,響應
任選的其他字段
響應要求:
驗證 Origin 字段,如果不符合要求的請求則返回適當的錯誤代碼(例如:403)
Sec-WebSocket-Key 值是一個 base64 加密后的值,服務端不需要對其進行解碼,而僅是用來創建服務器的握手.
驗證 Sec-WebSocket-Version 值,如果不是 13,則返回一個適當的錯誤代碼(例如:HTTP/1.1 426 Upgrade Required)
資源名驗證
子協議驗證
extensions 驗證
如果通過了上述驗證,則服務器表示接受該鏈接.那么起響應必須符合以下要求詳情查看 Page 23:
必須,狀態行 HTTP/1.1 101 Switching Protocols
必須,協議升級頭 Upgrade: websocket
必須,表示連接升級的頭字段 Connection: Upgrade
必須,Sec-WebSocket-Accept 頭字段,詳情請查閱 協議概述 部分
可選:Sec-WebSocket-Protocols 頭部
完整的響應代碼如下(嚴格按照如下格式響應!!頭部順序無所謂!關鍵是后面的換行符注意了!嚴格控制數量!):
HTTP/1.1 101 Switching Protocols Connection: Upgrade Upgrade: websocket Sec-WebSocket-Accept: 3nlEzv+LqVBYnTHclAqtk62uOTQ= // 下面這個頭字段為可選字段 Sec-WebSocket-Protocols: chat基本框架協議
數據傳輸部分對 位 進行了分組?。∮捎谑窃?b>bit層面上進行的數據封裝,所以如果直接取出的話,獲取到的將是處理后的數據,需要解密。下圖是傳輸數據格式:
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 ... | +---------------------------------------------------------------+1. 特殊名詞含義介紹
1bit,FIN
每個 1bit, RSV1、RSV2、RSV3
4bit,opcode(以下定義在ABNF中)
%x0 連續幀
%x1 文本幀
%x2 二進制幀
%x3 - %x7 保留幀
%x8 鏈接關閉
%x9 ping
%xA pong
%xB-F 保留的控制幀
以上表示的都是 16 進制數值
1bit, mask
客戶端發送給服務端的數據都需要設置為 1
也就是說數據都是經過掩碼處理過的
7bit、7 + 16bit、7 + 64bit,Payload length 具體范圍請參閱 RFC 文檔(Page 31)
Playload length = Extended Payload length + Application Payload length
有效載荷長度 = 擴展數據長度 + 應用程序數據長度
擴展數據長度有可能為 0,所以當 擴展數據長度 = 0 的時候,有效載荷長度 = 應用程序長度
有效載荷數據的長度單位為 Byte
0/4 byte, masking-key
客戶端發送給服務端的數據都是經過掩碼處理的,長度為 32bit
服務端發送給客戶端的數據都是未經過掩碼處理的,長度為 0bit
x + y Byte, Payload Data
有效載荷數據
x Byte, Extension Data
擴展數據
y Byte, Application Data
應用數據
2. 理解圖中表示遵循 websocket 協議進行傳輸的數據,由于是經過 websocket 協議處理后的數據,所以無法直接獲取有效數據。如果想要獲取有效數據,就需要按照 websocket 協議規定進行解讀。
圖中從左往右,按高位到低位進行排列。
什么是低位、高位??
就像是十進制數字,如果有一個描述是這樣的:3表示個位,2 表示十位,1表示百位,請問這個數字是??答案:123。
這就很好理解了,個位、十位、百位 描述了排列順序;同樣的,在程序領域,低位到高位描述的也是排列順序!不過 個位、十位、百位描述的是10進制的排列順序,而 低位、高位描述的是 2進制 的排列順序,具體描述是 位0、位1、位2.... 等(當前舉例中的的排列順序為低位到高位),以下是圖片描述:
理解了低位、高位,就清楚了上圖描述的數據排列順序。
眾所周知,位(bit)是內存中的最小存儲單位,僅能存 0、1兩個數值。所以要想獲取、設置某位的值,需要進行位操作。由于是在位上進行操作者,所以,圖中描述的內容是在補碼的基礎上進行的。
客戶端發送給服務端的數據是經過掩碼處理的! 需要進行解析,解析數據流程:
// 按照 websocket 規范解析客戶端加密數據 function decode(string $buffer){ // buffer[0] 獲取第一個字節,8bit // 對照那張圖,表示的是 fin + rsv1 + rsv2 + rsv 3 + opcode // 之所以要轉換為 ASCII 碼值 // 是為了確保位運算結果正確! // php 位運算詳情參考:https://note.youdao.com/share/?id=927bfc2f40a8d62f4c9165de30a41e75&type=note#/ // 這邊做一點簡單解釋 // 后面的代碼會有 $first_byte >> 7 這樣的代碼 // php 中 << >> 都會將操作數當成是整型數(int) // 所以如果不轉換成 ascii 值的話,過程將會是 // (int) $buffer[0] >> 7 // 這樣的結果將是錯誤的?。? // ord((int) $buffer[0]) !== ord($buffer[0]) 就是最好的證明 // 因為 ascii 值不一樣,則二進制值(嚴格一點,我認為應該說成是:補碼)也不一樣 // 這違反了 websocket 規定的協議 // 會導致解析錯誤 $first_byte = ord($buffer[0]); // buffer[1] 獲取第二個字節,8bit // 對照那張圖,表示的是 mask + payload len $second_byte = ord($buffer[1]); // 獲取左邊第一位值 $fin = $first_byte >> 7; // 對照那張圖,要想獲取 payload len 表示的值 // 需要設置 位 7 為 0 // 因為位 7 表示的是掩碼,位 0 - 6 表示的是 paylaod len 的補碼 // 所以要想獲取 payload len 的值 // 0111 1111 => 127 $payload_len = $second_byte & 127; // 客戶端發送給服務端的數據是經過掩碼處理的 // 所以要獲取 掩碼鍵 + 掩碼處理過后的客戶端數據 // 獲取 mask-key + payload data if ($payload_len === 127) { // 如果 payload len = 127 byte // payload len 本身占據 7bit // extended payload lenght 占據 64bit $mask_key = substr($buffer , 10 , 4); $encoded_data = substr($buffer , 14); } else if ($payload_len === 126) { // 如果 payload len = 126 byte // payload length 本身占據 7bit // extended payload lenght 占據 16bit $mask_key = substr($buffer , 4 , 4); $encoded_data = substr($buffer , 8); } else { // 如果 payload len = 126 byte // payload length 本身占據 7bit // extended payload lenght 占據 0bit $mask_key = substr($buffer , 2 , 4); $encoded_data = substr($buffer , 6); } // 對 payload data 進行解碼 $decoded_data = ""; // 對每一個有效載荷數據進行解碼操作 // 解碼規則在 RFC 文檔中有詳細描述 for ($index = 0; $index < count($encoded_data); ++$index) { $k = $index % 4; $valid_data = $encoded_data[$index] ^ $mask_data[$k]; $decoded_data .= $valid_data; } // 這個就是客戶端發送的真實數據!! return $decoded_data; }
相反,如果服務器想要發送數據給 websocket 客戶端,則也要對數據進行相應處理!處理流程:
// 按照 websocket 規范封裝發送給客戶端的消息 function encode($msg){ if (!is_scalar($msg)) { print_r("只允許發送標量數據"); } // 數據長度 $len = strlen($msg); // 這邊僅實現傳輸文本幀!第一個字節,文本幀 1000 0001 => 129 // 如果需要例如二進制幀,用于傳輸大文件,請另行實現 $first_byte = chr(129); if ($len <= 125) { // payload length = 7bit 支持的最大范圍! $second_byte = chr($len); } else { if ($len <= 65535) { // payload length = 7 , extended payload length = 16bit,支持的最大范圍 65535 // 最后16bit 被解釋為無符號整數,排序為:大端字節序(網絡字節序) $second_byte = chr(126) . pack("n" , $len); } else { // payload length = 7,extended payload length = 64bit // 最后 64 位被解釋為無符號整數,大端字節序(網絡字節序) $second_byte = chr(127) . pack("J" , $len); } } // 注意了,發送給客戶端的數據不需要處理 // 詳情查看 websocket 文檔?。? $encoded_data = $first_byte . $second_byte . $buffer; // 這個就是發送給客戶端的數據! return $encoded_data; }消息分片 分片目的
消息分片的主要目的是允許消息開始但不必緩沖整個消息時,發送一個未知大小的消息;未分片的消息需要緩沖整個消息,以便獲取消息大小;
分片要求:首個分片 Fin = 0,opcode != 0x0,其后跟隨多個 Fin = 0,opcode = 0x0的分片,終止于 Fin = 1,opcode = 0x0的片段
擴展數據可能發生在分片中的任意一個分片中
控制幀可能被注入到分片消息的中間,控制幀本身必須不被分割
消息分片必須按照發送者發送順序交付給收件人
片段中的一個消息必須不能與片段中的另一個消息交替,除非已協商了一個能解釋交替的擴展。
websocket服務器應能夠處理分片消息中間的控制幀
一個發送者可以為非控制消息(非控制幀)創建任何大小的片段
不能處理控制幀
如果使用了任何保留的位值且這些值的意思對中間件是未知的,一個中間件必須不改變一個消息的分片。
在一個連接上下文中,已經協商了擴展且中間件不知道協商的擴展的語義,一個中間件必須不改變任何消息的分片。同樣,沒有看見WebSocket握手(且沒被通知有關它的內容)、導致一個WebSocket連接的一個中間件,必須不改變這個鏈接的任何消息的分片。
由于這些規則,一個消息的所有分片是相同類型,以第一個片段的操作碼設置。因為控制幀不能被分片,用于一個消息中的所有分片的類型必須或者是文本、或者二進制、或者一個保留的操作碼。
ping接受到一個 ping(0x9) 控制幀,必須返回一個 pong(0xa) 控制幀,表示進程還在!!實際就是心跳檢查
pong可以在接收到 ping(0x9) 控制幀后,作為響應消息返回。
也可以單向發送 pong 幀,表示發送方進程還在,作為單向心跳
狀態碼1000,正常關閉
1001,正在離開
1003,正在關閉連接
1004,保留
1005,保留
1006,保留
1007,端點正在終止連接,因為它收到的消息中沒有與消息類型一致。
1008,端點正在終止鏈接,因為接收到了違反其規則的消息。
1009,端點正在終止鏈接,因為接受到的消息太大
1010,端點正在終止鏈接,因為擴展問題
1011,端點正在終止鏈接,發生了以外錯誤
1015,保留
.....省略了部分,詳情參考 rfc 文檔
尾部以上個人理解,僅供參考,有錯歡迎糾正,未完待續 ....
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/107519.html
摘要:概述本文為協議的第十一章,本文翻譯的主要內容為的相關注意事項。應用協議使用這個協議規范互操作性注意事項使用時需要使用或者更高版本的協議。安全性注意事項見安全性注意事項一節。 概述 本文為 WebSocket 協議的第十一章,本文翻譯的主要內容為 WebSocket 的 IANA 相關注意事項。 IANA 注意事項(協議正文) 11.1 注冊新 URI 協議 11.1.1 注冊 ws 協...
摘要:本文作為系列的第四篇內容,將會用一個簡單的聊天應用把整個傳輸二進制數據類型的內容連接起來,讓用戶對整個傳輸二進制數據的方法有個了解。如何發送二進制數據通過如何設計一個二進制協議一章,我們知道了如何定義傳輸的二進制數據格式。 概述 通過前三篇博客,我們能夠了解在通過WebSocket發送數據之前,我們需要傳遞的數據是如何變成ArrayBuffer二進制數據的;在我們收到二進制數據之后,我...
摘要:幀是發送數據的基本單位,下邊是它的報文格式報文內容中規定了數據標示操作代碼掩碼數據數據長度等格式。首先我們明白了客戶端和服務端進行消息傳遞是這樣的客戶端將消息切割成多個幀,并發送給服務端。服務端接收消息幀,并將關聯的幀重新組裝成完整的消息。 本文概述 Web Sockets的目標是在一個單獨的持久連接上提供全雙工、雙向通信。在Javascript創建了Web Socket之后,會有一個...
摘要:幀是發送數據的基本單位,下邊是它的報文格式報文內容中規定了數據標示操作代碼掩碼數據數據長度等格式。首先我們明白了客戶端和服務端進行消息傳遞是這樣的客戶端將消息切割成多個幀,并發送給服務端。服務端接收消息幀,并將關聯的幀重新組裝成完整的消息。 本文概述 Web Sockets的目標是在一個單獨的持久連接上提供全雙工、雙向通信。在Javascript創建了Web Socket之后,會有一個...
閱讀 3554·2021-11-08 13:15
閱讀 2112·2019-08-30 14:20
閱讀 1394·2019-08-28 18:08
閱讀 985·2019-08-28 17:51
閱讀 1490·2019-08-26 18:26
閱讀 2994·2019-08-26 13:56
閱讀 1492·2019-08-26 11:46
閱讀 2592·2019-08-23 14:22