【譯】 WebSocket 協議第七章——關閉連線(Closing the Connection)

黃Java發表於2019-01-14

概述

本文為 WebSocket 協議的第七章,本文翻譯的主要內容為 WebSocket 連線關閉相關內容。

有興趣瞭解該文件之前幾章內容的同學可以見:

關閉連線(協議正文)

7.1 定義

7.1.1 關閉 WebSocket 連線

關閉 WebSocket 連線,終端需要關閉底層的 TCP 連線。終端需要使用一個方法來乾淨的關閉TCP連線,還有 TLS 會話,如果可能的話,拋棄後面可能受到的任意字元。終端可能會在需要的時候,通過任何方式來關閉連線,例如在收到攻擊時。

在底層的 TCP 連線中,通常大多數情況下,服務端應該先關閉,所以是服務端而不是客戶端保持 TIME_WAIT 狀態(因為客戶端先關閉的話,這會阻止服務端在2 MSL 內重新開啟這條連線,而如果伺服器處於 TIME_WAIT 狀態下,如果收到了一個帶有更大序列號的新的 SYN 包時,也能夠立即響應重新開啟連線,從而不會對伺服器產生影響)。反常情況(例如在合理的時間後,服務端收到一個 TCP 關閉包)下,客戶端應該開始關閉 TCP 連線。像這樣的,當服務端進入關閉 WebSocket 連線狀態時,它應該立刻準備關閉 TCP 連線,然後當客戶端客戶端準備關閉連線時,他應該等待服務端的 TCP 關閉包。

用 C 語言的 Berkeley socket 作為例子來展示如何徹底的關閉連線,一端需要用 SHUP_WR 呼叫 shutdown() 方法,呼叫 recv() 直到獲得一個值為 0 的表示對面也準備有序關閉連線的返回值,然後最後呼叫 close() 來關閉 socket 通道。

7.1.2 開始進行 WebSocket 關閉握手

用一個狀態碼 code (第 7.4 節)和一個可選的關閉原因 reason (第 7.1.6 節)來開始 WebSocket 關閉握手,終端必須傳送一個在第 5.5.1 節中描述的一樣的關閉幀,將狀態碼設定為 code 欄位,將關閉原因設定為 reaons 欄位。一旦終端已經傳送和收到了關閉控制幀,那麼終端應該像第 7.1.1 節中定義的一樣關閉 WebSocket 連線

7.1.3 已經開始 WebSocket 關閉握手

在傳送或者收到了關閉幀時,我們可以說已經開始 WebSocket 關閉握手,並且 WebSocket 連線的狀態已經到了“關閉中”(CLOSING)狀態。

7.1.4 WebSocket 連線已關閉

當底層的 TCP 連線關閉後,我們可以說WebSocket 連線已關閉,並且 WebSocket 連線已經到了”關閉“(CLOSED)狀態。如果 TCP 連線在 WebSocket 關閉握手完成之後已經關閉,那麼我們可以說 WebSocket 連線已經被徹底關閉。

如果 WebSocket 連線沒有被建立,我們也說WebSocket已經關閉,但是不徹底

7.1.5 WebSocket 關閉狀態碼

就像在第 5.5.1 和第 7.4 節中定義的一樣,關閉幀可以包含一個關閉的狀態碼和指定的原因。WebSocket 連線的關閉可能是同時由另一個終端發起。WebSocket 關閉狀態碼是在第 7.4 節中定義的在第一關閉幀中的由實現該協議的應用程式接收的狀態碼。如果關閉幀中沒有包含狀態碼,WebSocket 關閉狀態碼被預設為1005。如果WebSocket 已經關閉並且終端沒有收到任何的關閉幀(例如發生了可能底層的傳輸連線突然丟失的情況),那麼WebSocket 關閉狀態碼被預設為1006。

注:兩個終端可能沒有就WebSocket 關閉狀態碼的值達成一致。例如:如果遠端傳送一個關閉幀,但是本地應用沒有從它的 socket 緩衝區中讀到關閉幀的資料,同時本地應用單獨的決定關閉連線並且傳送了一個關閉幀,那麼兩個終端都傳送了並且會收到一個關閉幀,同時不會傳送更多的關閉幀。每一個終端會看到另一個終端傳送過來的WebSocket 關閉狀態碼的狀態碼。像這樣的,在這個示例裡面,有可能兩個終端都沒有協商過WebSocket 關閉狀態碼,兩個終端都幾乎在同一時間單獨開始 WebSocket 關閉握手

7.1.6 WebSocket 連線關閉原因

像第 5.5.1 節和第 7.4 節中定義的一樣,一個關閉幀可能包含一個用於關閉的表示原因的狀態碼,然後是 UTF-8 編碼的資料,資料的解析方式是留給終端來解釋,而不在這個協議中定義。一個正在關閉中的 WebSocket 連線可能是同時從另一端開始的。WebSocket 連線關閉原因是實現了該協議的應用收到的緊跟在狀態碼(第 7.4 節)之後的包含在第一個關閉控制幀中的 UTF-8 編碼資料。如果在關閉控制幀中沒有這些資料,那麼WebSocket 連線關閉原因的值就是一個空字串。

注:和在第 7.1.5 中被提到的邏輯一樣,兩個終端可能沒有協商過WebSocket 連線關閉原因

7.1.7 WebSocket 連線失效

某些演算法和規範要求終端有WebSocket 連線失效。為了實現這些,客戶端必須關閉 WebSocket 連線,並且可以用一個合適的方式向使用者上報相關問題(尤其是對開發者有幫助的內容)。相似的,為了實現這個,服務端必須關閉 WebSocket 連線,並且應該用日誌記錄這個問題。

如果在此之前WebSocket 已經建立連線,此時終端需要讓WebSocket 連線失效,那麼在進行關閉 WebSocket 連線之前,終端需要傳送一個包含恰當的狀態碼(第 7.4 節)。終端在確認另一端沒有能力接收或者處理關閉幀時,可能會選擇省略傳送關閉幀,從而在一開始就進入正常錯誤流程導致 WebSocket 連線關閉。終端在接到WebSocket 連線失效的指令後,不能繼續嘗試處理來自另一端的資料(包括響應的關閉幀)。

除了上面說到的場景和應用層指定的場景(例如:指令碼使用了 WebSocket 的 API)外,客戶端不應該關閉連線。

7.2 異常關閉

7.2.1 客戶端主動關閉

在開始握手中的某些特定演算法,需要客戶端讓WebSocket 連線失效。為了實現這些,客戶端必須像第 7.1.7 節中定義的一樣讓WebSocket 連線失敗。

如果任意一端底層的傳輸連線意外丟失,客戶端必須讓WebSocket 連線失敗

除了上面指定的情況和應用層的約束(例如,指令碼使用了 WebSocket 的 API)外,客戶端不應該關閉連線。

7.2.2 服務端主動關閉

在開始監建立連線握手時,有些演算法要求或者推薦服務端終端 WebSocket 連線。為了實現這些,服務端必須關閉 WebSocket 連線(第 7.1.1 節)。

7.2.3 從異常關閉中恢復

導致異常關閉的原因有很多。例如是由於一個臨時的錯誤導致的關閉,在這種情況下能夠恢復就能夠帶來一個穩定的連線,恢復正常的操作。有些問題也有可能是一個非臨時的問題導致的,在這種情況下如果每個客戶端都遇到了異常的關閉,客戶端立刻重試連線並且不間斷情況下,服務端可能會收到由於大量客戶端重新連線帶來的拒絕服務攻擊。最終的結果就是這個方案可能會導致服務沒有辦法及時的恢復,或者讓服務恢復變得困難的多。

為了避免這個問題,客戶端應該在異常終端嘗試恢復連線時,使用在這一節中定義的一些備選策略。

第一次嘗試恢復連線應該在一個隨機長度時間後。隨機事件的引數如何選擇,這個交給客戶端來決定;選擇 0 到 5 秒之間的隨機值是一個合理的初始延時,但是客戶端可以根據自己的經驗和特定的應用來選擇不同長度的時間延時。

如果第一次重試連線失敗,接下來的連線的延時應該變大,使用如截斷二進位制指數退避方法(譯者注:解決乙太網碰撞演算法,見截斷二進位制質數退避演算法)等來進行設定這個延時。

7.3 連線正常關閉

服務端可以在任意需要時關閉 WebSocket 連線。客戶端不應該任意關閉 WebSocket 連線。在任一情況中,終端要發起關閉都必須遵循開始 WebSocket 連線關閉的步驟。

7.4 狀態碼

當關閉一個連線時(如:在開始握手已經完成後,傳送一個關閉幀),終端可能會說明關閉的原因。終端的這個原因的描述和終端應該採取的行動,在這個文件中都沒有說明。這個文件提前定義了一些可能用於擴充套件、框架和終端應用的狀態碼和狀態碼範圍。這些狀態碼和任何有關聯的的文字訊息在關閉幀中都是可選的。

7.4.1 定義狀態碼

在傳送一個關閉幀時,終端可以提前定義如下的狀態碼。

1000

1000 表示一個正常的關閉,意味著連線建立的目標已經完成了。

1001

1001 表示終端已經“走開”,例如伺服器停機了或者在瀏覽器中離開了這個頁面。

1002

1002 表示終端由於協議錯誤中止了連線。

1003

1003 表示終端由於收到了一個不支援的資料型別的資料(如終端只能怪理解文字資料,但是收到了一個二進位制資料)從而關閉連線。

1004

保留欄位。這意味著這個狀態碼可能會在將來被定義。

1005

1005 是一個保留值並且不能被終端當做一個關閉幀的狀態碼。這個狀態碼是為了給上層應用表示當前沒有狀態碼。

1006

1006 是一個保留值並且不能被終端當做一個關閉幀的狀態碼。這個狀態碼是為了給上層應用表示連線被異常關閉如沒有傳送或者接受一個關閉幀這種場景的使用而設計的。

1007

1007 表示終端因為收到了型別不連續的訊息(如非 UTF-8 編碼的文字訊息)導致的連線關閉。

1008

1008 表示終端是因為收到了一個違反政策的訊息導致的連線關閉。這是一個通用的狀態碼,可以在沒有什麼合適的狀態碼(如 1003 或者 1009)時或者可能需要隱藏關於政策的具體資訊時返回。

1009

1009 表示終端由於收到了一個太大的訊息無法進行處理從而關閉連線。

1010

1010 表示終端(客戶端)因為預期與服務端協商一個或者多個擴充套件,但是服務端在 WebSocket 握手中沒有響應這個導致的關閉。需要的擴充套件清單應該出現在關閉幀的原因(reason)欄位中。

1001

1001 表示服務端因為遇到了一個意外的條件阻止它完成這個請求從而導致連線關閉。

1015

1015 是一個保留值,不能被終端設定到關閉幀的狀態碼中。這個狀態碼是用於上層應用來表示連線失敗是因為 TLS 握手失敗(如服務端證書沒有被驗證過)導致的關閉的。

7.4.2 保留狀態碼範圍

0-999

0-999 的狀態碼都沒有被使用。

1000-2999

1000-2999 的狀態碼是在這個文件、將來的修訂和擴充套件中定義的保留欄位,用於永久的可用的公共文件。

3000-3999

3000-3999 的狀態碼是保留給庫、框架和應用使用的。這些狀態碼被IANA直接註冊了。這些狀態碼在這篇文件中沒有進行解釋。

4000-4999

4000-4999 的狀態碼是保留下來私用的,因此這些狀態碼不能被註冊。這些狀態碼可以使用在 WebSocket 應用之前的協議上。這些狀態碼在這篇文件中沒有進行解釋。

相關文章