2020 年國內市場 App 較 2019 年減少 22 萬款,5G 創新應用超 5000 個
WebSocket協議
簡介
該協議為了提供一種基於瀏覽器與伺服器進行雙向通訊的應用程式,不依賴於開啟多個HTTP連線。
第一章
1.1
在WebSocket之前,建立一個客戶端和服務端的雙向資料Web應用(例如IM應用和遊戲應用)需要向服務端頻繁傳送不同於一般HTTP請求的HTTP輪詢請求來從服務端上游更新資料。
這個方法有許多的問題:
服務端被迫使用大量的的潛在的TCP連線與客戶端進行互動:一部分是用來發送資料,而另一部分是用來接收資料。
應用層無線傳輸協議(HTTP)開銷較大,每一個客戶端到服務端的訊息都有一個HTTP頭。
客戶端指令碼必須包含一個傳送和接收對應的對映表來進行對應資料處理。
一個簡單的解決方案是使用一個簡單的TCP連結來進行雙向資料傳輸。這就是WebSocket提供的能力。結合WebSocket的API,它能夠提供一個可以替代HTTP輪詢的方法來滿足Web頁面和遠端伺服器的雙向資料通訊。
相同的技術可以被用到許多的Web應用:遊戲、股票應用、多人協作應用、與後端服務實時互動的使用者介面等。
HTTP協議最初也不是用來做雙向資料通訊的。WebSocket協議嘗試實現基於現有的HTTP基礎服務來實現在現有環境中雙向通訊技術的目標;它在設計中仍然使用了HTTP的80和443埠,以及支援HTTP代理。然而,這個設計並沒有限制WebSocket只能使用HTTP埠,在以後的實現中也可以使用一個簡單的握手方式來使用特定的埠而不需要改動整個協議。最後一點很重要,因為雙向訊息的通訊方式不是很符合標準HTTP的模式,可能導致在某些元件中出現異常的負載。
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
客戶端請求的第一行(leading line)遵循了HTTP請求行的格式。
服務端的第一行(leading line)遵循了HTTP狀態行的格式。
1.3 開始握手
開始握手為了與基於http的服務端軟體和中介相容,因此一個獨立的埠既能夠同時滿足HTTP客戶端與伺服器進行互動,又能夠滿足WebSocket客戶端與服務進行互動。最終,WebSocket客戶端的握手是一個基於http的升級請求。
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
客戶端在每一個握手的Hostheader裡面包含了一個主機域名。所以客戶端和服務端都可以校驗哪些域名在使用中。
另外的header欄位是用來確定WebSocket協議的選項。這個版本中提供的特定選項是子協議選擇(Sec-WebSocket-Protocol)、客戶端支援的擴充套件列表(Sec-WebSocket-Extensions)、Originheader欄位等。請求header欄位Sec-WebSocket-Protocol可以用來標識哪些子協議(基於WebSocket的應用高層協議)是客戶端可以支援的。服務端會從中選擇零個或者一個支援的協議並且在響應握手中輸出它選擇的那個協議。
1.4 結束握手
結束握手遠比連線握手簡單。
任何一端都可以傳送一個包含特定關閉握手的控制幀資料。收到此幀後,另一端在不傳送任何資料後會傳送一個結束幀作為響應。收到另一端的結束幀後,最開始傳送控制幀的端在沒有資料需要傳送時,就會安全的關閉此連線。
在傳送了一個表明連線需要被關閉的控制幀後,這個客戶端不會再發送任何的資料;在收到一個表明連線需要被關閉的控制幀後,這個客戶端會丟棄此後的所有資料。
1.5 設計哲學
WebSocket協議設計的原理,將框架最小化,對框架的唯一的約束就是使這個協議是基於幀而不是流並且可以支援Unicode文字和二進位制幀兩者中的任意一種。
從概念上來看,WebSocket層是基於TCP實現的,增加了以下的內容:
- 增加了一個基於瀏覽器的同源策略模型
- 增加了一個地址和協議命名機制用以在同一個埠上支援多個服務,在同一個IP地址自持多個主機名
- 在TCP協議上分層構建框架機制回到TCP使用的IP包機制,但是沒有長度限制
- 包含一個設計用於處理有代理和其他網路中介的情況的額外的結束握手協議
除此之外,WebSocket沒有增加任何東西。基本上WebSocket的的目標是在約束的條件下向指令碼提供儘可能接近原生的TCP的Web服務。它同時考慮了伺服器在進行握手和處理有效的HTTP升級請求時,可以和HTTP共用一個服務。大家也可以使用其他協議來建立從客戶端到服務端的訊息通訊,但WebSocket的協議的目的是為了提供一個相對簡單的可以和HTTP共存,並且依賴於HTTP基礎設施(如代理)的協議。這個非常接近TCP的協議因為基於安全的基礎設施和針對性的能夠簡單使用和讓事情變得更簡單的補充(例如訊息語義的補充),因此可以安全使用。
1.6 安全模型
1.7 與TCP和HTTP的關係
WebSocket協議是獨立的基於TCP的協議。他和HTTP的唯一關係是建立連線的握手操作的升級請求是基於HTTP伺服器的。
WebSocket預設使用80埠進行連線,而基於TLS(RFC2818)的WebSocket連線是基於443埠的。
1.8 建立連線
1.9 使用WebSocket協議的子協議
第三章
WebSocket URIs
ws-URI =ws:
//
host [:
port ] path [?
query ]
wss-URI =wss:
//
host [:
port ] path [?
query ]
埠欄位是可選的,預設的ws
埠是80,而預設的wss
埠是443。
命中不論大小寫的wss
方案欄位就表明這個URI可以被稱為安全的(已經設定安全標記)。
resource-name
欄位(在4.1節也被稱為/resouce name/欄位)可以通過以下的資料串聯起來:
/
,表示路徑(path)欄位為空
路徑(path)欄位?
,表示非空的查詢引數(query)
空查詢引數(query)
在WebSocket URIs的裡,身份標識片段是沒有意義的,而且禁止使用在這些URI裡面。與任何的URI方案一樣,#
字元不是表示片段(fragment)開始時,都必須編碼為%23。
第四章 握手
客戶端要求
第五章 資料幀
5.1 概覽
在WebSocket協議中,資料是通過一系列資料幀來進行傳輸的。為了避免由於網路中介(例如一些攔截代理)或者一些在第10.3節討論的安全原因,客戶端必須在它傳送到伺服器的所有幀中新增掩碼(Mask)(具體細節見5.3節)。(注意:無論WebSocket協議是否使用了TLS,幀都需要新增掩碼)。服務端收到沒有新增掩碼的資料幀以後,必須立即關閉連線。在這種情況下,服務端可以傳送一個在7.4.1節定義的狀態碼為1002(協議錯誤)的關閉幀。服務端禁止在傳送資料幀給客戶端時新增掩碼。客戶端如果收到了一個添加了掩碼的幀,必須立即關閉連線。在這種情況下,它可以使用第7.4.1節定義的1002(協議錯誤)狀態碼。(這些規則可能會在將來的規範中放開)。
一個數據幀可以在開始握手完成之後和終端傳送了一個關閉幀之前的任意一個時間通過客戶端或者服務端進行傳輸(第5.5.1節)。
5.2 基礎幀協議
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
表示這是訊息的最後一個片段。第一個片段也有可能是最後一個片段。值為1代表最後一個片段
RSV1,RSV2,RSV3: 每個1 bit
必須設定為0,除非擴充套件了非0值含義的擴充套件。如果收到了一個非0值但是沒有擴充套件任何非0值的含義,接收終端必須斷開WebSocket連線。
Opcode: 4 bit
定義“有效負載資料”的解釋。如果收到一個未知的操作碼,接收終端必須斷開WebSocket連線。下面的值是被定義過的。
%x0 表示一個持續幀
%x1 表示一個文字幀
%x2 表示一個二進位制幀
%x3-7 預留給以後的非控制幀
%x8 表示一個連線關閉包
%x9 表示一個ping包
%xA 表示一個pong包
%xB-F 預留給以後的控制幀
Mask: 1 bit
mask標誌位,定義“有效負載資料”是否新增掩碼。如果設定為1,那麼掩碼的鍵值存在於Masking-Key中,根據5.3節描述,這個一般用於解碼“有效負載資料”。所有的從客戶端傳送到服務端的幀都需要設定這個bit位為1。
Payload length: 7 bits, 7+16 bits, or 7+64 bits
以位元組為單位的“有效負載資料”長度,如果值為0-125,那麼就表示負載資料的長度。如果是126,那麼接下來的2個bytes解釋為16bit的無符號整形作為負載資料的長度。如果是127,那麼接下來的8個bytes解釋為一個64bit的無符號整形(最高位的bit必須為0)作為負載資料的長度。多位元組長度量以網路位元組順序表示(譯註:應該是指大端序和小端序)。在所有的示例中,長度值必須使用最小位元組數來進行編碼,例如:長度為124位元組的字串不可用使用序列126,0,124進行編碼。有效負載長度是指“擴充套件資料”+“應用資料”的長度。“擴充套件資料”的長度可能為0,那麼有效負載長度就是“應用資料”的長度。
Masking-Key: 0 or 4 bytes
所有從客戶端發往服務端的資料幀都已經與一個包含在這一幀中的32 bit的掩碼進行過了運算。如果mask標誌位(1 bit)為1,那麼這個欄位存在,如果標誌位為0,那麼這個欄位不存在。在5.3節中會介紹更多關於客戶端到服務端增加掩碼的資訊。
Payload data: (x+y) bytes
“有效負載資料”是指“擴充套件資料”和“應用資料”。
Extension data: x bytes
除非協商過擴充套件,否則“擴充套件資料”長度為0 bytes。在握手協議中,任何擴充套件都必須指定“擴充套件資料”的長度,這個長度如何進行計算,以及這個擴充套件如何使用。如果存在擴充套件,那麼這個“擴充套件資料”包含在總的有效負載長度中。
Application data: y bytes
任意的“應用資料”,佔用“擴充套件資料”後面的剩餘所有欄位。“應用資料”的長度等於有效負載長度減去“擴充套件應用”長度。
5.3 客戶端到服務端新增掩碼
新增掩碼的資料幀必須像5.2節定義的一樣,設定frame-masked欄位為1。
掩碼值像第5.2節說到的完全包含在幀中的frame-masking-key上。它是用於對定義在同一節中定義的幀負載資料Payload data欄位中的包含Extension data和Application data的資料進行新增掩碼。
掩碼欄位是一個由客戶端隨機選擇的32bit的值。當準備掩碼幀時,客戶端必須從允許的32bit值中選擇一個新的掩碼值。掩碼值必須是不可被預測的;因此,掩碼必須來自強大的熵源(entropy),並且給定的掩碼不能讓伺服器或者代理能夠很容易的預測到後續幀。掩碼的不可預測性對於預防惡意應用作者在網上暴露相關的位元組資料至關重要。RFC 4086討論了安全敏感的應用需要一個什麼樣的合適的強大的熵源。
掩碼不影響Payload data的長度。進行掩碼的資料轉換為非掩碼資料,或者反過來,根據下面的演算法即可。這個同樣的演算法適用於任意操作方向的轉換,例如:對資料進行掩碼操作和對資料進行反掩碼操作所涉及的步驟是相同的。
表示轉換後資料的八位位元組的i(transformed-octet-i )是表示的原始資料的i(original-octet-i)與索引i模4得到的掩碼值(masking-key-octet-j)經過異或操作(XOR)得到的:
j = i MOD 4
transfromed-octed-i = original-octet-i XOR masking-key-octet-j
5.4 訊息分片
5.5 控制幀
控制幀是通過操作碼最高位的值為1來進行區分的。當前已經定義的控制幀操作碼包括0x8(關閉),0x9(心跳Ping)和0xA(心跳Pong)。操作碼0xB-0xF沒有被定義,當前被保留下來做為以後的控制幀。
控制幀是用於WebSocket的通訊狀態的。控制幀可以被插入到訊息片段中進行傳輸。
所有的控制幀必須有一個126位元組或者更小的負載長度,並且不能被分片。
5.5.1 關閉
控制幀的操作碼值是0x8。
關閉幀可能包含內容(body)(幀的“應用資料”部分)來表明連線關閉的原因,例如終端的斷開,或者是終端收到了一個太大的幀,或者是終端收到了一個不符合預期的格式的內容。如果這個內容存在,內容的前兩個位元組必須是一個無符號整型(按照網路位元組序)來代表在7.4節中定義的狀態碼。跟在這兩個整型位元組之後的可以是UTF-8編碼的的資料值(原因),資料值的定義不在此文件中。資料值不一定是要人可以讀懂的,但是必須對於除錯有幫助,或者能傳遞有關於當前開啟的這條連線有關聯的資訊。資料值不保證人一定可以讀懂,所以不能把這些展示給終端使用者。
從客戶端傳送給服務端的控制幀必須新增掩碼,具體見5.3節。
應用禁止在傳送了關閉的控制幀後再發送任何的資料幀。
如果終端收到了一個關閉的控制幀並且沒有在以前傳送一個關閉幀,那麼終端必須傳送一個關閉幀作為迴應。(當傳送一個關閉幀作為迴應時,終端通常會輸出它收到的狀態碼)響應的關閉幀應該儘快傳送。終端可能會推遲傳送關閉幀直到當前的訊息都已經發送完成(例如:如果大多數分片的訊息已經發送了,終端可能會在傳送關閉幀之前將剩餘的訊息片段傳送出去)。然而,已經發送關閉幀的終端不能保證會繼續處理收到的訊息。
在已經發送和收到了關閉幀後,終端認為WebSocket連線以及關閉了,並且必須關閉底層的TCP連線。服務端必須馬上關閉底層的TCP連線,客戶端應該等待服務端關閉連線,但是也可以在收到關閉幀以後任意時間關閉連線。例如:如果在合理的時間段內沒有收到TCP關閉指令。
如果客戶端和服務端咋同一個時間傳送了關閉幀,兩個終端都會發送和接收到一條關閉的訊息,並且應該認為WebSocket連線已經關閉,同時關閉底層的TCP連線。
5.5.2 心跳Ping
心跳Ping幀包含的操作碼是0x9。
關閉幀可能包含“應用資料”。
如果收到了一個心跳Ping幀,那麼終端必須傳送一個心跳Pong 幀作為迴應,除非已經收到了一個關閉幀。終端應該儘快恢復Pong幀。Pong幀將會在5.5.3節討論。
終端可能會在建立連線後與連線關閉前中間的任意時間傳送Ping幀。
注意:Ping幀可能是用於保活或者用來驗證遠端是否仍然有應答。
5.5.3 心跳Pong
心跳Ping幀包含的操作碼是0xA。
5.5.2節詳細說明了Ping幀和Pong幀的要求。
作為迴應傳送的Pong幀必須完整攜帶Ping幀中傳遞過來的“應用資料”欄位。
如果終端收到一個Ping幀但是沒有傳送Pong幀來回應之前的pong幀,那麼終端可能選擇用Pong幀來回復最近處理的那個Ping幀。
Pong幀可以被主動傳送。這會作為一個單項的心跳。預期外的Pong包的響應沒有規定。
5.6 資料幀
資料幀(例如非控制幀)的定義是操作碼的最高位值為0。當前定義的資料幀操作嗎包含0x1(文字)、0x2(二進位制)。操作碼0x3-0x7是被保留作為非控制幀的操作碼。
資料幀會攜帶應用層/擴充套件層資料。操作碼決定了攜帶的資料解析方式:
文字
“負載欄位”是用UTF-8編碼的文字資料。注意特殊的文字幀可能包含部分UTF-8序列;然而,整個訊息必須是有效的UTF-8編碼資料。重新組合訊息後無效的UTF-8編碼資料處理見8.1節。
二進位制
“負載欄位”是任意的二進位制資料,二進位制資料的解析僅僅依靠應用層。
第六章 傳送與接收訊息
6.1 傳送資料
為了通過 WebSocket 連線傳送一條 WebSocket 訊息,終端必須遵循以下幾個步驟:
終端必須保證 WebSocket 連線處於 OPEN 狀態(見第 4.1 節和第 4.2.2 節)。如果 WebSocket 連線的任意一端的狀態發生了改變,終端必須中止以下步驟。
終端必須將資料按照第 5.2 節定義的 WebSocket 幀進行封裝。如果需要傳送的資料過大或者在終端希望開始發訊息時,如果資料在整體性這一點上不可用,那麼終端可能會選擇通過在第 5.4 節中定義的一系列幀來進行封裝。
包含資料的第一幀操作碼(幀操作碼)必須根據第 5.2 節中的內容設定的合適的值,以便接收者將資料解析為文字或者二進位制資料。
最後一個包含資料的幀的 FIN ( FIN 幀)欄位必須和第 5.2 節中定義的一樣設定為 1 。
如果資料被髮送到了客戶端,資料幀必須和第 5.3 節中定義的一樣新增掩碼。
如果在 WebsSocket 連線中有協商擴充套件(第 9 章),在這些擴充套件中的定義和注意事項也許要額外考慮。
被格式化的幀必須通過底層的網路連線進行傳輸。
6.2 接收資料
為了接收 WebSocket 資料,終端需要監聽底層網路連線。輸入的資料必須通過第 5.2 節定義的 WebSocket 幀進行解析。如果收到了一個控制幀(第 5.5 節),那麼這個幀必須如 5.5 節中定義的方式進行處理。如果收到的是一個數據幀,那麼終端必須注意 5.2 節中的定義在操作碼(幀操作碼)中的資料型別。在這一幀中的“應用資料”被定義為訊息的資料。如果幀中包含未分片的資料(第 5.4 節),那麼就認為:一條 WebSocket 訊息的資料和型別被收到了。如果幀是分片資料的一部分,那麼隨後的幀包含的“應用資料”連起來就是資料的格式。當通過 FIN 欄位(FIN幀)表示的最後一個片段被收到時,我們可以說:一條 WebSocket 訊息的資料(由片段組裝起來的“應用資料”資料組成)和型別(注意分片訊息的第一幀)已經被收到了。接下來的資料幀必須是屬於一條新的 WebSocket 訊息。
擴充套件(第 9 章)可能改變資料如何理解的方式,具體包括訊息的內容邊界。擴充套件,除了在“應用資料”之前新增“擴充套件資料”之外,也可以修改“應用資料”(例如壓縮它)。
像第 5.3 節中說的那樣,服務端在收到客戶端的資料幀時必須去除掩碼。
參考:https://segmentfault.com/a/1190000018217630