# 關閉連接
## 7.1.定義
### [](https://github.com/zhangkaitao/websocket-protocol/wiki/7.%E5%85%B3%E9%97%AD%E8%BF%9E%E6%8E%A5#711%E5%85%B3%E9%97%ADwebsocket%E8%BF%9E%E6%8E%A5)7.1.1.關閉WebSocket連接
為*關閉WebSocket連接*,端點需關閉底層TCP連接。端點應該使用一個方法完全地關閉TCP連接,以及TLS會話,如果合適,丟棄任何可能已經接收的尾隨的字節。當必要時端點可以通過任何可用的手段關閉連接,例如當受到攻擊時。
底層TCP連接,在大多數正常情況下,應該首先被服務器關閉,所以它持有TIME_WAIT狀態而不是客戶端(因為這會防止它在2個報文最大生存時間(2MLS)內重新打開連接,然而當一個新的帶有更高的seq number的SYN時沒有對應的服務器影響TIME_WAIT連接被立即重新打開)。在異常情況下(例如在一個合理的時間量后沒有接收到服務器的TCP Close)客戶端可以發起TCP Close。因此,當服務器被指示*關閉WebSocket連接*,它應該立即發起一個TCP Close,且當客戶端被知識也這么做時,它應該等待服務器的一個TCP Close。
例如一個如何使用Berkeley socket在C中得到完全地關閉的例子,一端會在socket上以SHUT_WR調用shutdown(),調用recv()直到獲得一個指示那個節點也已經執行了一個有序關閉的0返回值,且最終在socket上調用close()方法。
### [](https://github.com/zhangkaitao/websocket-protocol/wiki/7.%E5%85%B3%E9%97%AD%E8%BF%9E%E6%8E%A5#712%E5%90%AF%E5%8A%A8websocket%E5%85%B3%E9%97%AD%E9%98%B6%E6%AE%B5%E6%8F%A1%E6%89%8B)7.1.2.啟動WebSocket關閉階段握手
為了*啟動WebSocket關閉階段握手*,其帶有一個狀態碼(7.4節)/code/和一個可選的關閉原因(7.1.6節)/reason/,一個端點必須按照5.5.1節的描述發送一個Close控制幀,其狀態碼設置為/code/且其關閉原因設置為/reason/。一旦一個端點已經發送并接收到一個Close控制幀,那個端點應該按照7.1.1節的描述*關閉WebSocket連接*。
### [](https://github.com/zhangkaitao/websocket-protocol/wiki/7.%E5%85%B3%E9%97%AD%E8%BF%9E%E6%8E%A5#713-websocket%E5%85%B3%E9%97%AD%E9%98%B6%E6%AE%B5%E6%8F%A1%E6%89%8B%E5%B7%B2%E5%90%AF%E5%8A%A8)7.1.3\. WebSocket關閉階段握手已啟動
一旦發送或接收到一個Close控制幀,這就是說,*WebSocket 關閉階段握手已啟動*,且WebSocket連接處于CLOSING狀態。
### [](https://github.com/zhangkaitao/websocket-protocol/wiki/7.%E5%85%B3%E9%97%AD%E8%BF%9E%E6%8E%A5#714-websocket%E5%B7%B2%E5%85%B3%E9%97%AD)7.1.4\. WebSocket已關閉
當底層TCP連接已關閉,這就是說*WebSocket連接已關閉*且WebSocket連接處于CLOSED狀態。如果TCP連接在WebSocket關閉階段我是已經完成后被關閉,WebSocket連接被說成已經*完全地*關閉了。 如果WebSocket連接不能被建立,這就是說,*WebSocket連接關閉了*,但不是*完全的*。
### [](https://github.com/zhangkaitao/websocket-protocol/wiki/7.%E5%85%B3%E9%97%AD%E8%BF%9E%E6%8E%A5#715websocket%E8%BF%9E%E6%8E%A5%E5%85%B3%E9%97%AD%E4%BB%A3%E7%A0%81)7.1.5.WebSocket連接關閉代碼
按照5.5.1和7.4節的定義,一個Close控制幀可以包含一個表示關閉原因的狀態碼。一個正關閉的WebSocket連接可以同時由兩個端點初始化。*WebSocket連接Close Code*定義為包含在由實現該協議的應用接收到的第一個Close控制幀的狀態碼(7.4節)。如果這個Close控制幀不包含狀態碼,*WebSocket連接Close Code*被認為是1005。如果*WebSocket連接已經關閉*且端點沒有接收到Close狀態碼(例如可能發生在底層傳輸連接丟失時),*WebSocket連接Close Code*被認為是1006。
注意:兩個端點可以有不一致的*WebSocket連接關閉代碼*。例如,如果遠程端點發送了一個Close幀,但本地應用還沒有從它的socket接收緩沖區中讀到包含Close幀的數據,且本地應用獨立地決定關閉連接和發送一個Close幀,兩個端點都將發送和接收Close幀且將不發送更多的Close幀。每一個端點將看見另一端發送的以*WebSocket連接關閉代碼*結束的狀態碼。例如,在兩個端點獨立且在大致相同的時間同時*開啟WebSocket關閉階段握手*的情況下,兩個端點可以有不一致的*WebSocket連接關閉代碼*是可能的。
### [](https://github.com/zhangkaitao/websocket-protocol/wiki/7.%E5%85%B3%E9%97%AD%E8%BF%9E%E6%8E%A5#716-websocket%E8%BF%9E%E6%8E%A5%E5%85%B3%E9%97%AD%E5%8E%9F%E5%9B%A0)7.1.6\. WebSocket連接關閉原因
按照5.5.1和7.4節的定義,一個控Close控制幀可以包含一個指示關閉原因的狀態碼,接著是UTF-8編碼的數據,上述數據留給斷點解釋且本協議沒有定義。WebSocket連接的關閉可以被任何一個端點初始化,可能同時發生。*WebSocket 連接關閉原因*由跟在包含在實現該協議的應用接收到的第一個Close控制幀狀態碼(7.4節)后邊的UTF-8編碼的數據定義。如果Close控制幀中沒有這樣的數據,*WebSocket連接關閉原因*是空字符串。
注意:按照7.1.5節指出的相同的邏輯,兩個端點可以有不一致的*WebSocket連接關閉原因*。
### [](https://github.com/zhangkaitao/websocket-protocol/wiki/7.%E5%85%B3%E9%97%AD%E8%BF%9E%E6%8E%A5#717%E5%A4%B1%E8%B4%A5websocket%E8%BF%9E%E6%8E%A5)7.1.7.失敗WebSocket連接
某些算法和規范要求端點*失敗WebSocket連接*。要做到這一點,客戶端必須*關閉WebSocket連接*,并可以以適當的方式把問題報告給用戶(這將對開發人員非常有用的)。
同樣的,為了做到這一點,服務器必須*關閉WebSocket連接*,并應該記錄下問題。
如果*已建立的WebSocket連接*在端點需要*失敗WebsSocket連接*之前,端點應該在處理*關閉WebSocket連接*之前發送一個帶有適當狀態碼的Close幀(7.4節)。
如果端點認為另一邊不太可能收到并處理關閉幀可以省略發送一個關閉幀,因為錯誤的性質,導致WebSocket連接失敗擺在首要位置。端點必須在被指示為*失敗WebSocket端點*之后不繼續嘗試處理來自遠程端點的數據(包括響應關閉幀)。
除上邊指出的或由應用層指定的(例如,使用WebSocket API的腳本),客戶端應該關閉連接。
## [](https://github.com/zhangkaitao/websocket-protocol/wiki/7.%E5%85%B3%E9%97%AD%E8%BF%9E%E6%8E%A5#72%E5%BC%82%E5%B8%B8%E5%85%B3%E9%97%AD)7.2.異常關閉
### [](https://github.com/zhangkaitao/websocket-protocol/wiki/7.%E5%85%B3%E9%97%AD%E8%BF%9E%E6%8E%A5#721%E5%AE%A2%E6%88%B7%E7%AB%AF%E5%8F%91%E8%B5%B7%E7%9A%84%E5%85%B3%E9%97%AD)7.2.1.客戶端發起的關閉
某些算法,尤其在打開階段握手期間,需要客戶端*失敗WebSocket連接*。為了做到這一點,客戶端必須按照7.1.7節定義的那樣*失敗WebSocket連接*。
如果在任何時候,底層的傳輸層連接意外丟失,客戶端必須*失敗WebSocket連接*。 除上邊指出的或由應用層指定的(例如,使用WebSocket API的腳本),客戶端應該關閉連接。
### [](https://github.com/zhangkaitao/websocket-protocol/wiki/7.%E5%85%B3%E9%97%AD%E8%BF%9E%E6%8E%A5#722%E6%9C%8D%E5%8A%A1%E7%AB%AF%E5%8F%91%E8%B5%B7%E7%9A%84%E5%85%B3%E9%97%AD)7.2.2.服務端發起的關閉
某些算法需要或推薦服務端在打開階段握手期間*中斷WebSocket連接*。為了做到這一點,服務端必須簡單地*關閉WebSocket連接*(7.1.1節)。
### [](https://github.com/zhangkaitao/websocket-protocol/wiki/7.%E5%85%B3%E9%97%AD%E8%BF%9E%E6%8E%A5#723%E4%BB%8E%E5%BC%82%E5%B8%B8%E5%85%B3%E9%97%AD%E4%B8%AD%E6%81%A2%E5%A4%8D)7.2.3.從異常關閉中恢復
異常關閉可能由任何原因引起。這樣的關閉可能是一個瞬時錯誤導致的,在這種情況下重新連接可能導致一個好的連接和一個重新開始的正常操作。這樣的關閉也可能是一個非瞬時問題的導致的,在這種情況下如果每個部署的客戶端遇到異常關閉并立即且持續地的嘗試重新連接,服務端可能會因為大量的客戶端嘗試重新連接遇到的拒絕服務攻擊。這種情況的最終結果可能是服務不能及時的恢復或恢復是更加困難。
為了避免這個,當客戶端遇到本節描述的異常關閉之后嘗試重新連接時,應該使用某種形式的補償。
第一個重新連接嘗試應該延遲一個隨機的時間量。這種隨機延遲的參數的選擇留給客戶端決定;一個可隨機選擇的的值在0到5秒是一個合理的初始延遲,不過客戶端可以選擇不同的間隔由于其選擇一個延遲長度基于實現經驗和特定的應用。
第一次重新連接嘗試失敗,隨后的重新連接嘗試應該延遲遞增的時間量,使用的方法如截斷二進制指數退避算法。
## [](https://github.com/zhangkaitao/websocket-protocol/wiki/7.%E5%85%B3%E9%97%AD%E8%BF%9E%E6%8E%A5#73%E6%AD%A3%E5%B8%B8%E8%BF%9E%E6%8E%A5%E5%85%B3%E9%97%AD)7.3.正常連接關閉
服務端在需要時可能關閉WebSocket連接。客戶端不能隨意關閉WebSocket連接。在這兩種情況下,端點通過如下過程*開始WebSocket關閉握手*初始化一個關閉(7.1.2節)。
## [](https://github.com/zhangkaitao/websocket-protocol/wiki/7.%E5%85%B3%E9%97%AD%E8%BF%9E%E6%8E%A5#74%E7%8A%B6%E6%80%81%E7%A0%81)7.4.狀態碼
當關閉一個已經建立的連接(例如,當在打開階段握手已經完成后發送一個關閉幀),端點可以表明關閉的原因。由端點解釋這個原因,并且端點應該給這個原因采取動作,本規范是沒有定義的。本規范定義了一組預定義的狀態碼,并指定哪些范圍可以被擴展、框架和最終應用使用。狀態碼和任何相關的文本消息是關閉幀的可選的組件。
### [](https://github.com/zhangkaitao/websocket-protocol/wiki/7.%E5%85%B3%E9%97%AD%E8%BF%9E%E6%8E%A5#741%E5%AE%9A%E4%B9%89%E7%9A%84%E7%8A%B6%E6%80%81%E7%A0%81)7.4.1.定義的狀態碼
當發送關閉幀時端點可以使用如下預定義的狀態碼。
* 1000
1000表示正常關閉,意思是建議的連接已經完成了。
* 1001
1001表示端點“離開”(going away),例如服務器關閉或瀏覽器導航到其他頁面。
* 1002
1002表示端點因為協議錯誤而終止連接。
* 1003
1003表示端點由于它收到了不能接收的數據類型(例如,端點僅理解文本數據,但接收到了二進制消息)而終止連接。
* 1004 保留。可能在將來定義其具體的含義。
* 1005
1005是一個保留值,且不能由端點在關閉控制幀中設置此狀態碼。它被指定用在期待一個用于表示沒有狀態碼是實際存在的狀態碼的應用中。
* 1006
1006是一個保留值,且不能由端點在關閉控制幀中設置此狀態碼。它被指定用在期待一個用于表示連接異常關閉的狀態碼的應用中。
* 1007
1007表示端點因為消息中接收到的數據是不符合消息類型而終止連接(比如,文本消息中存在非UTF-8[[RFC3629](http://www.faqs.org/rfcs/rfc3629.html)]數據)。
* 1008
1008表示端點因為接收到的消息違反其策略而終止連接。這是一個當沒有其他合適狀態碼(例如1003或1009)或如果需要隱藏策略的具體細節時能被返回的通用狀態碼。
* 1009
1009表示端點因接收到的消息對它的處理來說太大而終止連接。
* 1010
1010表示端點(客戶端)因為它期望服務器協商一個或多個擴展,但服務器沒有在WebSocket握手響應消息中返回它們而終止連接。 所需要的擴展列表應該出現在關閉幀的/reason/部分。
注意,這個狀態碼不能被服務器端使用,因為它可以失敗WebSocket握手。
* 1011
1011表示服務器端因為遇到了一個不期望的情況使它無法滿足請求而終止連接。
* 1015
1015是一個保留值,且不能由端點在關閉幀中被設置為狀態碼。它被指定用在期待一個用于表示連接由于執行TLS握手失敗而關閉的狀態碼的應用中(比如,服務器證書不能驗證)。
### [](https://github.com/zhangkaitao/websocket-protocol/wiki/7.%E5%85%B3%E9%97%AD%E8%BF%9E%E6%8E%A5#742%E4%BF%9D%E7%95%99%E7%9A%84%E7%8A%B6%E6%80%81%E7%A0%81%E8%8C%83%E5%9B%B4)7.4.2.保留的狀態碼范圍
* 0-999
0-999范圍內的狀態碼不被使用。
* 1000-2999
1000-2999范圍內的狀態碼保留給本協議、其未來的修訂和一個永久的和現成的公共規范中指定的擴展的定義。
* 3000-3999
3000-3999范圍內的狀態碼保留給庫、框架和應用使用。這些狀態碼直接向IANA注冊。本規范未定義這些狀態碼的解釋。
* 4000-4999
4000-4999范圍內的狀態碼保留用于私有使用且因此不能被注冊。這些狀態碼可以被在WebSocket應用之間的先前的協議使用。本規范未定義這些狀態碼的解釋。