【譯】WebSocket協議第一章——介紹(Introduction)

黃Java發表於2018-06-08

概述

本文為WebSocket協議的第一章,本文翻譯的主要內容為針對整個WebSocket進行一個簡單而又全面的介紹。通過這篇文章我們能夠對WebSocket有一個整體的大致瞭解。

1 介紹

本章為協議正文內容的第一章(Introduction)。

1.1 背景

此章節為非規範章節。

在歷史上,建立一個客戶端和服務端的雙向資料Web應用(例如IM應用和遊戲應用)需要向服務端頻繁傳送不同於一般HTTP請求的HTTP輪詢請求來從服務端上游更新資料。

這個方法有許多的問題:

  • 服務端被迫使用大量的的潛在的TCP連線與客戶端進行互動:一部分是用來傳送資料,而另一部分是用來接收資料。
  • 應用層無線傳輸協議(HTTP)開銷較大,每一個客戶端到服務端的訊息都有一個HTTP頭。
  • 客戶端指令碼必須包含一個傳送和接收對應的對映表來進行對應資料處理。

一個簡單的解決方案是使用一個簡單的TCP連結來進行雙向資料傳輸。這就是WebSocket提供的能力。結合WebSocket的API,它能夠提供一個可以替代HTTP輪詢的方法來滿足Web頁面和遠端伺服器的雙向資料通訊。

相同的技術可以被用到許多的Web應用:遊戲、股票應用、多人協作應用、與後端服務實時互動的使用者介面等。

WebSocket協議設計的原因是取代已經存在的使用HTTP作為傳輸層的雙向通訊技術,從而使得已經存在的基礎服務(如代理、過濾器、認證服務)能夠受益。這種技術是基於效率和可靠性權衡後來進行實現的,而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狀態行的格式。

HTTP請求行和狀態行的規範定義在RFC2616

在兩個協議中,第一行header下面是一組無序的header欄位。這些header欄位包含的內容在本文的第四節。另外的header欄位如cookies,也有可能存在。格式和解析頭資訊被定義在了RFC2616

當客戶端和服務端都傳送了他們的握手協議,並且當握手已經成功,那麼資料傳輸就開始了。這是一個雙方都可以獨立傳送任意資料的雙向通訊渠道。

在握手成功以後,客戶端和服務端傳輸的資料來回傳輸的資料單位,我們在規範中稱為訊息(messages)。在傳輸中,一條訊息有一個或者多個幀組成。WebSocket中的訊息不需要對應特定網路層中的幀,一條零散的訊息可能由中間人合併或者拆分成網路層的幀。

幀有關聯的型別。同一條訊息的每一幀都包含相同型別的資料。通常來說,它可以是文字資料(UTF-8編碼)、二進位制資料(留給應用解析的資料)和控制幀資料(不是用來傳輸資料,而是用來作為協議層的特定符號,如關閉連線幀)。當前版本的協議定義了6中控制幀型別並且預留了10個保留型別。

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
複製程式碼

遵照RFC2616,客戶端在握手過程中傳送的header欄位可能是亂序的,所以收到的header欄位的順序不同也沒有太大影響。

GET方法的請求URI(Request-URI)是用於定義WebSocket連線的終端,允許同一個IP對多個域名提供服務,也允許多個WebSocket終端連線同一個伺服器。

客戶端在每一個握手的Hostheader裡面包含了一個主機域名。所以客戶端和服務端都可以校驗哪些域名在使用中。

另外的header欄位是用來確定WebSocket協議的選項。這個版本中提供的特定選項是子協議選擇(Sec-WebSocket-Protocol)、客戶端支援的擴充套件列表(Sec-WebSocket-Extensions)、Originheader欄位等。請求header欄位Sec-WebSocket-Protocol可以用來標識哪些子協議(基於WebSocket的應用高層協議)是客戶端可以支援的。服務端回從中選擇零或者一個支援的協議並且在響應握手中輸出它選擇的那個協議。

Sec-WebSocket-Protocol: chat
複製程式碼

服務端也可以設定cookie相關欄位來設定cookie相關屬性,具體文件見RFC6265

1.4 結束握手

此節為非規範章節。

結束握手遠比連線握手簡單。

任何一端都可以傳送一個包含特定關閉握手的控制幀資料(詳情見5.5.1節)。收到此幀後,另一端在不傳送任何資料後會傳送一個結束幀作為響應。收到另一端的結束幀後,最開始傳送控制幀的端在沒有資料需要傳送時,就會安全的關閉此連線。

在傳送了一個表明連線需要被關閉的控制幀後,這個客戶端不會再傳送任何的資料;在收到一個表明連線需要被關閉的控制幀後,這個客戶端會丟棄此後的所有資料。

這樣比兩邊同時發起握手要更加安全。

這個結束握手的目標是來補充TCP結束握手中的一些內容(FIN/ACK),而這是因為TCP結束握手在端與端之間並不一定可靠,尤其是有代理和其他的網路中介時會變得不可靠。

在傳送關閉幀等待接受另一端的響應關閉幀時,在某些情況下可以避免資料的不必要丟失。例如,在某些平臺中,如果一個socket在接收佇列有資料時被關閉,會傳送一個RST包,儘管資料還在等待被讀取,這也會導致接收到RST的一方資料接收失敗。

1.5 設計哲學

此節為非規範章節。

WebSocket協議設計的原理就是設計為最的小框架(唯一的約束就是使這個協議是基於幀而不是流,並且可以支援Unicode文字和二進位制幀兩者中的任意一種)。在基於WebSocket的應用層中,後設資料是應該分層的,就像基於TCP的應用層(例如HTTP)一樣。

從概念上來看,WebSocket層是基於TCP實現的,增加了以下的內容:

  • 增加了一個基於瀏覽器的同源策略模型
  • 增加了一個地址和協議命名機制來在同一個埠上支援多個服務,在同一個IP地址自持多個主機名
  • 在TCP協議上分層構建框架機制回到TCP使用的IP包機制,但是沒有長度限制
  • 包含一個設計用於處理有代理和其他網路中介的情況的額外的結束握手協議

除此之外,WebSocket沒有增加任何東西。基本上WebSocket的的目標是在約束的條件下像指令碼提供儘可能接近原生的TCP的Web服務。它同時考慮了伺服器在進行握手和處理有效的HTTP升級請求時,可以和HTTP共用一個服務。大家也可以使用其他協議來建立從客戶端到服務端的訊息通訊,但WebSocket的協議的目的是為了提供一個相對簡單的可以和HTTP共存,並且依賴於HTTP基礎設施(如代理)的協議。這個非常接近TCP的協議因為基於安全的基礎設施和針對性的能夠簡單使用和讓事情變得更簡單的補充(例如訊息語義的補充),因此可以安全使用。

這個協議具有可擴充套件性,未來的版本可能會引入一些新的概念如多路複用。

1.6 安全模型

此節為非規範章節。

當WebSocket協議在web網頁中應用時,WebSocket協議在Web頁面與WebSocket伺服器建立連線時使用基於web瀏覽器的同源策略模型。所以說,當WebSocket協議在一個特定的客戶端(不是web瀏覽器裡面的網頁)直接使用時,同源策略模型就不生效了,客戶端可以接受任意的源資料。

該協議無法與已經存在的如SMTP(RFC5421)和HTTP協議的伺服器建立連線,如果需要的話,HTTP伺服器可以選擇支援該協議。該協議還實現了嚴格約束的握手過程和限制資料不能在握手完成和建立連線之前插入資料進行傳輸(因此限制了許多被影響的伺服器)。

WebSocket伺服器同樣無法與其他協議尤其是HTTP建立連線。例如,一個HTML“表單”可能會提交給一個WebSocket伺服器。WebSocket服務端只能讀取包含特定的由WebSocket客戶端傳送的欄位的握手資料。尤其是在編寫這個規範時,攻擊者不能只使用HTML和JavaScript APIs的Web瀏覽器來傳送以Sec-開頭的欄位。

1.7 與TCP和HTTP的關係

此節為非規範章節。

WebSocket協議是獨立的基於TCP的協議。他和HTTP的唯一關係是建立連線的握手操作的升級請求是基於HTTP伺服器的。

WebSocket預設使用80埠進行連線,而基於TLS(RFC2818)的WebSocket連線是基於443埠的。

1.8 建立連線

此節為非規範章節。

當建立了一個和HTTP伺服器共享埠的連線時(這種情況很有可能傳送在與80和443埠通訊上),這個連結將會給HTTP伺服器傳送一個常規的GET請求來進行升級。在一個IP地址和一個單一的伺服器來應對單一主機名的通訊這種相對簡單的設定上,基於WebSocket協議的系統可以通過一個更加實用的方法來進行部署。在更詳細的設定(例如負載均衡和多伺服器),與HTTP伺服器分開的專屬的WebSocket連線叢集可能更加易於管理。在編寫這個規範時,我們應該知道在80埠和443埠建立WebSocket連線的成功率是不同的,在443埠上面建立的連線很明顯更容易成功,儘管這可能隨著時間的變化而改變。

1.9 使用WebSocket協議的子協議

客戶端可以通過在握手階段中的Sec-WebSocket-protocol欄位來請求服務端使用指定的子協議。如果指定了這個欄位,伺服器需要包含相同的欄位,並且從子協議的之中選擇一個值作為建立連線的響應。

子協議的名稱可以按照第11.5節的方法進行註冊。為了避免潛在的衝突,推薦使用包含ASCII碼的域名名稱作為子協議名。例如,Example Corporation創造了在Web上通過多個伺服器實現的一個聊天子協議(Chat subprotocol),他們可以叫做chat.example.com。如果Example Organization創造了他們相對的子協議叫做chat.example.org,這兩個子協議可以被伺服器同時實現,伺服器可以根據客戶端來動態的選擇使用哪一個子協議。

子協議也可以通過修改名字的方式來向後相容,例如:將bookings.example.net改為v2.bookings.example.net。WebSocket客戶端能夠完全的區分這些子協議。向後相容的版本控制可以通過複用相同的子協議字元和小心設計的子協議實現來保證這種擴充套件性。

總結

本文通過對WebSocket進行了一個全面的大致介紹,能夠讓大家對於WebSocket相關協議內容有一個初步的理解。

後續會持續對WebSocket協議進行翻譯,有興趣瞭解的同學可以持續關注下。

相關文章