7. 关闭连接
hyr редактировал эту страницу 9 месяцев назад
  1. 关闭连接

7.1. 定义

7.1.1. 关闭WebSocket连接

要_关闭WebSocket连接_,端点关闭底层的TCP连接。端点应该使用一种可以干净关闭TCP连接的方法,如果适用,还应该关闭TLS会话,丢弃可能已接收的任何尾随字节。在必要时,端点可以通过任何可用的方式关闭连接,例如在受到攻击时。

在大多数正常情况下,底层TCP连接应该首先由服务器关闭,这样服务器而不是客户端会保持TIME_WAIT状态(这会阻止客户端在2个最大段生命周期(2MSL)内重新打开连接,而服务器没有相应的影响,因为TIME_WAIT连接在收到具有更高序列号的新SYN时会立即重新打开)。在异常情况下(例如在合理的时间内没有从服务器收到TCP关闭),客户端可以发起TCP关闭。因此,当服务器被指示_关闭WebSocket连接_时,它应该立即发起TCP关闭;当客户端被指示做同样的事情时,它应该等待服务器的TCP关闭。

作为在C语言中使用Berkeley套接字获得干净关闭的一个示例,人们会在套接字上调用shutdown()函数,参数为SHUT_WR,调用recv()直到返回值为0,表示对端也执行了有序关闭,最后在套接字上调用close()。

7.1.2. 开始WebSocket关闭握手

要使用状态码(第7.4节)/code/和可选的关闭原因(第7.1.6节)/reason/_开始WebSocket关闭握手_,端点必须发送一个关闭控制帧,如第5.5.1节所述,其状态码设置为/code/,其关闭原因设置为/reason/。一旦端点既发送又接收到关闭控制帧,该端点应该_关闭WebSocket连接_,如第7.1.1节所定义。

7.1.3. WebSocket关闭握手已开始

一旦发送或接收到关闭控制帧,就可以说_WebSocket关闭握手已开始_,并且WebSocket连接处于CLOSING状态。

7.1.4. WebSocket连接已关闭

当底层TCP连接关闭时,可以说_WebSocket连接已关闭_,并且WebSocket连接处于CLOSED状态。如果在WebSocket关闭握手完成后关闭了TCP连接,则可以说WebSocket连接已经_干净地关闭_。

如果WebSocket连接无法建立,也可以说_WebSocket连接已关闭_,但不是_干净地关闭_。

7.1.5. WebSocket连接关闭代码

如第5.5.1节和第7.4节所定义,关闭控制帧可能包含一个状态码,指示关闭的原因。WebSocket连接的关闭可以由任一端点发起,可能同时发起。_WebSocket连接关闭代码_定义为应用程序实现此协议时收到的第一个关闭控制帧中包含的状态码(第7.4节)。如果此关闭控制帧不包含状态码,_WebSocket连接关闭代码_被认为是1005。如果_WebSocket连接已关闭_且端点未收到关闭控制帧(例如,如果底层传输连接丢失),_WebSocket连接关闭代码_被认为是1006。

注意:两个端点可能不同意_WebSocket连接关闭代码_的值。例如,如果远程端点发送了关闭帧,但本地应用程序尚未从其套接字的接收缓冲区中读取包含关闭帧的数据,并且本地应用程序独立决定关闭连接并发送关闭帧,那么两个端点都将发送并接收关闭帧,并且不会发送更多的关闭帧。每个端点将看到另一端发送的状态码作为_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.1.2节)来启动关闭。

7.4. 状态码

在关闭已建立的连接时(例如,在开放握手完成后发送关闭帧时),端点可以指示关闭的原因。端点对此原因的解释,以及端点在给定此原因时应采取的行动,由本规范未定义。本规范定义了一组预定义的状态码,并指定了哪些范围可以由扩展、框架和终端应用程序使用。状态码和任何相关的文本消息是关闭帧的可选组件。

7.4.1. 定义的状态码

在发送关闭帧时,端点可以使用以下预定义的状态码。

  • 1000 表示正常关闭,意味着建立连接的目的已经实现。
  • 1001 表示端点“即将离开”,例如服务器关闭或浏览器已离开页面。
  • 1002 表示端点因协议错误而终止连接。
  • 1003 表示端点因收到无法接受的数据类型(例如,仅理解文本数据的端点如果收到二进制消息可能会发送此状态码)而终止连接。
  • 1004 保留。将来可能定义具体含义。
  • 1005 是保留值,端点不得在关闭控制帧中将其设置为状态码。它用于应用程序期望状态码表明实际上没有状态码。
  • 1006 是保留值,端点不得在关闭控制帧中将其设置为状态码。它用于应用程序期望状态码表明连接异常关闭,例如,未发送或接收关闭控制帧。
  • 1007 表示端点因在消息中收到与消息类型不一致的数据(例如,文本消息中的非UTF-8 [RFC3629]数据)而终止连接。
  • 1008 表示端点因收到违反其政策的消息而终止连接。这是一个通用状态码,当没有其他更合适的状态码(例如,1003或1009)或需要隐藏有关政策的具体细节时可以返回。
  • 1009 表示端点因收到对其处理能力过大的消息而终止连接。
  • 1010 表示端点(客户端)因期望服务器在WebSocket握手的响应消息中协商一个或多个扩展,但服务器没有返回它们而终止连接。需要的扩展列表应出现在关闭帧的/reason/部分。注意,服务器不使用此状态码,因为它可以失败WebSocket握手。
  • 1011 表示服务器因遇到意外情况而无法完成请求而终止连接。
  • 1015 是保留值,端点不得在关闭控制帧中将其设置为状态码。它用于应用程序期望状态码表明连接因无法执行TLS握手而关闭(例如,无法验证服务器证书)。

7.4.2. 保留的状态码范围

  • 0-999 范围内的状态码未使用。
  • 1000-2999 范围内的状态码保留用于此协议、其未来修订版以及在永久且随时可用的公共规范中指定的扩展的定义。
  • 3000-3999 范围内的状态码保留供库、框架和应用程序使用。这些状态码直接向IANA注册。这些代码的解释由本协议未定义。
  • 4000-4999 范围内的状态码保留供私人使用,因此不能注册。这些代码可以通过WebSocket应用程序之间的事先协议使用。这些代码的解释由本协议未定义。