WebSocket协议详解及应用(七)-WebSocket协议关闭帧

本文介绍WebSocket协议的关闭帧,包括客户端及服务器如何发送并处理关闭帧、关闭帧错误码及错误处理方法。

一、关闭WebSocket连接

要断开WebSocket连接,需要一个端点断开底层的TCP连接。端点需要通过某种方式来完全关闭TCP连接,例如TLS会话,并适当的丢弃未接收完毕的数据。端点也在必要时可以通过一些有效的方式断开连接,如在受到攻击时。
在一般情况下,底层TCP连接应先被服务端断开,以便保持TIME_WAIT状态。这是为了防止其在2个最大分节生命期(1~4分钟,Windows操作系统为4分钟)之内重新打开,否则可能会由于接到一个高序列的SYN包而重新打开连接。在一些异常的情况下(如在一段时间内未收到服务器端TCP关闭帧),客户端可以关闭TCP连接。如果服务器发出关闭指令,则它需要立即关闭连接。而客户端发出关闭指令需要等待服务器发送的TCP关闭帧。

二、关闭握手阶段

关闭握手阶段需要一个状态码和一个可选的关闭原因,端点必须发送一个关闭控制帧,并设置状态码和关闭原因。一旦端点发送并接收了关闭帧,就需要按上节中的方法关闭WebSocket连接。

三、关闭握手阶段开始

当接收或发送关闭帧后,代表关闭握手阶段开始,此时WebSocket连接进入到CLOSING状态。

四、关闭WebSocket连接结束

当底层TCP连接关闭时,代表WebSocket连接已关闭,此时WebSocket连接状态改为CLOSED。如果TCP连接在WebSocket关闭握手结束后断开,则此次WebSocket为一次完整的(cleanly)关闭。
如果WebSocket连接未能建立,它仍叫做连接关闭,但不是完整的。

五、关闭码

关闭帧可以包含一个关闭码和一个关闭原因。关闭帧可以由任何一方发起,也可以双方同时发起。若关闭帧没有指明关闭码,则认为关闭码为1005,如果WebSocket连接断开,而没有任何关闭帧(如底层传输时丢帧),则认为关闭码为1006。
注意:双方发送的关闭码可能不一致。例如,对方发送了一个关闭帧,但本地程序还没有将数据及关闭帧从socket接收缓存中读取出来,然后本地程序决定发送一个关闭帧,双方都会发送并接收到一个关闭帧并不会再次发送关闭帧(即只进行一次收发,即使不是发过关闭帧后收到的帧)。

六、关闭原因

关闭原因是可选的,跟在关闭码后面,为UTF-8编码的数据,并未对其内容做详细的定义。如果没有设置关闭原因,则关闭原因是一个空的字符串。
注意:同关闭码一样,双方发送的关闭原因可能不一致。

七、强制关闭连接

一些情况会引起强制关闭连接,当情况发生时,客户端需要关闭连接并将错误返回给用户(如控制台中报错等),同样,服务器需要关闭连接并将问题记录在日志中。
如果WebSocket连接建立在端点需要强制关闭连接之前,端点需要在处理关闭帧之前发送关闭帧并发送正确的关闭码。当强制关闭连接后,端点不能再次尝试向对方发送任何数据(包括关闭帧)。
除了上述情况或指定的应用层协议(如WebSocket API)外,客户端不应该断开连接。

八、关闭码

1000 正常关闭
1001 端点丢失,如服务器宕机或浏览器切换其他页面
1002 协议错误
1003 数据类型错误(例如端点只能处理文本,但传来了二进制消息)
1004 保留
1005 保留,禁止由端点发送此类型关闭帧,它是用来当端点没有表明关闭码时的默认关闭码。
1006 保留,禁止由端点发送此类型关闭帧,它是用来当端点未发送关闭帧,连接异常断开时使用。
1007 数据内容错误(如在text帧中非utf-8编码的数据)
1008 端点已接收消息,但违反其策略。当没有更好的关闭码(1003或1009)的时候用此关闭码或者不希望显示错误细节。
1009 内容过长
1010 客户端期望服务器协商一个或多个扩展,但这些扩展并未在WebSocket握手响应中返回。
1011 遇到未知情况无法执行请求
1015 保留,禁止由端点发送此类型关闭帧,它会在TLS握手失败(如证书验证失败)时返回。
保留关闭码
0-999 尚未使用
1000-2999 协议保留,用于未来版本、扩展等
3000-3999 为库、框架、应用程序保留,这些状态码可在IANA中注册,这些状态码并未在此协议中实现。
4000-4999 私有保留,不可被注册。用于开发者自定义关闭码。

WebSocket协议详解及应用(六)-WebSocket协议控制帧结构详解

WebSocket控制帧有3种:Close(关闭帧)、Ping以及Pong。

一、控制帧

控制帧是由操作码上的位值置为1来定义的。目前,控制帧的操作码定义了0x08(关闭帧)、0x09(Ping帧)、0x0A(Pong帧)。0x0B-0x0F是为那些将来可能定义而目前尚未定义的控制帧预留的。
控制帧用于WebSocket协议交换状态信息,控制帧可以插在消息片段之间。
注意:所有的控制帧的负载长度务必不大于125字节,并且禁止对控制帧进行分片处理。

二、关闭帧

关闭帧的操作码是0x08。
关闭帧可能包含数据部分(应用数据帧),该部分表明了关闭的原因,例如端点关闭、端点接收帧过大或端点收到的帧不符合预期。如果有数据部分,则数据的前两个字节必须是一个无符号整数(网络字节序),该无符号整数表示了一个状态码,具体定义哪些关闭码将在后面的文章中介绍。在无符号整数后面,可能还有一个UTF-8编码的数据,表示关闭原因,关闭原因由开发者自行定义(可选),并无规范。关闭原因并不一定是对人可读的,但会对调试或传递相关信息起到一定的作用。由于数据不能保证可读,所以客户端不应将其显示给用户(会在关闭事件onclose中)。
客户端发送给服务器的关闭帧必须掩码处理。
应用程序在发送了一个关闭帧后,禁止再发送任何数据(此时处于CLOSING状态)。
如果端点(客户端或服务器)收到了一个关闭帧,并且之前没有发送过关闭帧,则端点必须发送一个关闭帧作为响应。(当端点发送一个关闭帧回应时,通常会显示它收到的状态码。)当端点可以发送关闭响应时应尽快发送关闭响应。一个端点可以延迟发送响应直到它的当前消息发送完毕(例如,已经发送了大多数的消息片段,则端点可能会在发送关闭响应帧前先将剩下的消息帧发送出去)。但不能保证对方在已经发送了关闭帧后还能够继续处理这些数据。
在双方都以发送并接收了关闭帧后,端点需要断掉WebSocket连接并且必须关闭底层的TCP连接。服务器必须立即切断底层TCP连接,客户端最好等待服务器断开连接,但也可以在发送并接收了关闭帧后任何时候断开连接,例如在一段时间内服务器仍没有断开TCP连接。
如果服务器和客户端同时发送了关闭帧,两端都会接收关闭帧,并且都需要断开TCP连接。

三、PING帧

Ping帧的操作码为0x09。
Ping帧可以包含应用数据。
一旦接到了一个Ping帧,端点必须返回一个Pong帧作为响应,除非它收到了一个关闭帧。它应在可以发送时尽快发送Pong帧响应。
端点可以在连接建立后一直到连接关闭前任何时候发送Ping帧。
提示:Ping帧既可用于保持活动状态也可用于验证远端仍可响应数据。

四、PONG帧
Pong帧的操作码为0x0A。
Pong帧必须与Ping帧拥有相同的应用数据部分。
如果端点收到了多个Ping帧,但还没来的及全部回应,可以只回应最后一个Ping帧。
Pong帧可以在未收到Ping帧时就被发送,用作单向心跳包。
不需要对未被请求的Pong帧(对方主动发送的Pong帧)进行回应。