精华内容
下载资源
问答
  • websocket RFC 6455
    2018-05-16 13:54:32
    转载地址: https://github.com/wen866595/open-doc/blob/master/rfc/RFC6455-cn.md
    更多相关内容
  • rfc6455的中文翻译版下载,资源来源自:http://www.52im.net/thread-3172-1-1.html
  • rfc6455的英文原版下载,资源来源自:http://www.52im.net/thread-3172-1-1.html
  • NULL 博文链接:https://wen866595.iteye.com/blog/1599209
  • websocketpp+rfc6455

    2019-01-17 09:43:13
    压缩包里包含了: (1)WebSocket++ 0.8.1,一个基于Boost的websocket库。 (2)websocket协议的rfc文档:rfc6455.pdf
  • WebSoket_协议(rfc6455).pdf

    2020-05-03 08:46:12
    这个是WebSocket的协议,也就是 RFC 6455标准,这个是 pdf版的,容易阅读。该标准的官方下载地址:http://www.rfc-editor.org/info/rfc6455
  • WebSocket.Portable WebSocket.Portable是协议的可移植C#实现。 地位 WebSocket.Portable库仍处于pre-alpha状态,仍然缺少许多功能,仍然需要编写许多测试,接口可能会更改。 安装 WebSocket.Portable将通过NuGet...
  • 一个基于WebSocketProtocol协议(rfc6455)的安卓客户端Demo,实现了最基础的Connect/SendMessage/Disconnect三项功能;服务端的实现为(https://github.com/gorilla/websocket);客户端的实现为...
  • 英文版RFC 6455 WebSocket 摘要 WebSocket协议实现在受控环境中运行不受信任代码的一个客户端到一个从该代码已经选择加入通信的远程主机之间的全双工通信。用于这个安全模型是通常由web浏览器使用的基于来源的...

    WebSocket协议中文版

    英文版RFC 6455 WebSocket

    摘要

           WebSocket协议实现在受控环境中运行不受信任代码的一个客户端到一个从该代码已经选择加入通信的远程主机之间的全双工通信。用于这个安全模型是通常由web浏览器使用的基于来源的安全模型。该协议包括一个打开阶段握手、接着是基本消息帧、TCP之上的分层(layered over TCP)。该技术的目标是为需要与服务器全双工通信且不需要依赖打开多个HTTP连接(例如,使用  XMLHttpRequest或者 <iframe>和长查询)的基于浏览器应用提供的一种机制。

     

    本备忘录状态

           这是一个Internet标准跟踪文档。

           本文档是互联网工程任务组(IETF)的一个产物。它代表了IETF社区的共识。它已接受公共审查和已经被互联网工程指导委员会(IESG)认可发布。Interent标准的更多信息可以在RFC 5741的第而节找到。

           本文档的当前状态信息、任何勘误表以及如何提供它的反馈,可于http://www.rfc-editor.org/info/rfc6455获得。

     

    版权声明

           版权所有(C)2011 IETF信托和确认为文档作者的人。保留所有权利。

           本文档遵守BCP 78和涉及IETF文档(http://trustee.ietf.org/license-info)的在本文档发布之日起生效的IETF信托的法律条文。请仔细阅读这些文档,因为他们描述了关于本文档的你的权利和限制。从本文档中提取的代码组件必须包括描述在第四章和简体BSD License文件。e的信托法律条文并提供,不保证描述在简体BSD许可协议中。

     

    目录

       1  引言

         1.1  背景

         1.2  协议概述

         1.3  打开阶段握手

         1.4  关闭阶段握手

         1.5  设计理念

         1.6  安全模型

         1.7  与TCP和HTTP的关系

         1.8  建立连接

         1.9  使用WebSocket协议的子协议

       2  一致性要求

         2.1  术语和其他约定

       3  WebSocket URI

       4  打开阶段握手

         4.1  客户端要求

         4.2  服务器端要求

           4.2.1  读取客户端的打开阶段握手

           4.2.2  发送服务器的打开阶段握手

         4.3  为握手中使用的新的头字段整理的ABNF

         4.4  支持多个版本的WebSocket协议

       5  数据帧

         5.1  概述

         5.2  基本帧协议

         5.3  客户端到服务器掩码

         5.4  分片

         5.5  控制帧

           5.5.1  Close

           5.5.2  Ping

           5.5.3  Pong

         5.6  数据帧

         5.7  示例

         5.8  可拓展性

       6  发送和接收数据

         6.1  发送数据

         6.2  接收数据

       7  关闭连接

         7.1  定义

           7.1.1  关闭WebSocket连接

           7.1.2  启动WebSocket关闭阶段握手

           7.1.3  关闭阶段握手已启动

           7.1.4  WebSocket已关闭

           7.1.5  WebSocket连接关闭代码

           7.1.6  WebSocket连接关闭原因

           7.1.7  失败WebSocket连接

         7.2  异常关闭

           7.2.1  客户端发起的关闭

           7.2.2  服务端发起的关闭

           7.2.3  从异常关闭中恢复

         7.3  正常连接关闭

         7.4  状态码

           7.4.1  定义的状态码

           7.4.2  保留的状态码范围

       8  错误处理

         8.1 处理UTF-8编码数据的错误

       9  拓展

         9.1  协商拓展

         9.2  已知拓展

       10 安全注意事项

         10.1 非浏览器客户端

         10.2 Origin注意事项

         10.3 攻击基础设施(掩码)

         10.4 实现特定限制

         10.5 WebSocket客户端验证

         10.6 连接的保密性和完整性

         10.7 处理无效数据

         10.8 使用SHA-1的WebSocket握手

       11 IANA考虑

         11.1 注册新的URI模式

           11.1.1 注册“ws”模式

           11.1.2 注册“wss”模式

         11.2 注册“WebSocket”HTTP Upgrade关键字

         11.3 注册新的HTTP头字段

           11.3.1 Sec-WebSocket-Key

           11.3.2 Sec-WebSocket-Extensions

           11.3.3 Sec-WebSocket-Accept

           11.3.4 Sec-WebSocket-Protocol

           11.3.5 Sec-WebSocket-Version

         11.4 WebSocket拓展名注册

         11.5 WebSocket子协议名注册

         11.6 WebSocket版本号注册

         11.7 WebSocket关闭代码注册

         11.8 WebSocket操作吗注册

         11.9 WebSocket帧头位注册

       12 其他规范使用WebSocket协议

       13 致谢

       14 参考资料

         14.1 参考标准

         14.2 参考资料

     

     

    引言

     

    1.1  背景

           本节是非规范的。

           过去,创建需要需要在客户端和服务之间的双向通信(例如,即时消息和游戏应用)的web应用,需要一个滥用的HTTP来轮询服务器进行更新但以不同的HTTP调用发生上行通知(RFC6202)。

           这将导致各种各样的问题:

           1、服务器被迫为每个客户端使用一些不同的底层TCP连接:一个用于发送信息到客户端和一个新的用于每个传入消息。

           2、 线路层协议有较高的开销,因为每个客户端-服务器消息都有一个HTTP头信息。

           3、客户端脚本被迫维护一个传出的连接到传入的连接的映射来跟踪回复。

           一个简单的办法是使用单个TCP连接双向传输。这是为什么提供WebSocket协议。与WebSocket API结合[WSAPI],它提供了一个HTTP轮询的替代来进行从web页面到远程服务器的双向通信。

           同样的技术可以用于各种各样的web应用:

           游戏、股票行情、同时编辑的多用户应用、服务器端服务以实时暴露的用户接口等等。

           WebSocket协议被设计来取代现有的使用HTTP作为传输层的双向通信技术,并受益于现有的基础设施(代理、过滤、身份验证)。这样的技术被实现来在效率和可靠性之间权衡,因为HTTP最初的目的不是用于双向通信(参见[RFC6202]的进一步讨论)。WebSocket协议试图在现有的HTTP基础设施上下文中解决现有的双向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

           来自客户端的首行遵照Request-Line格式。来自服务器的首行遵照Status-Line格式。Request-Line和Status-Line在[RFC2616]中定义。

           在这两种情况中一个无序的头字段集合出现在首行之后。这些头字段的意思指定在本文档的第4章。另外的头字段也可能出现,例如cookie[RFC6265]。头的格式和解析定义在[RFC2616]

           一旦客户端和服务器都发送了它们的握手,且如果握手成功,接着开始数据传输部分。这是一个每一段都可以的双向通信信道,彼此独立,随意发生数据。

           一个成功握手之后,客户端和服务器来回第传输数据,在本规范中提到的概念单位为“消息”。在线路上,一个消息是由一个或者多个帧组成。WebSocket的消息并不一定对应于一个特定的网络层帧,可以作为一个可以被一个中间件合并或分解的片段消息。

           一个帧有一个相应的类型。属于相同消息的每一帧包含相同类型的数据。从广义上讲,有文本数据类型(它被解释为UTF-8[RFC3629]文本)、二进制数据类型(它的解释是留给应用)、和控制帧类型(它是不准备包含用于应用的数据,而是协议级的信号,例如应关闭连接的信号)。这个版本的协议定义了六个帧类型并保留10个以备将来使用。

     

    1.3  打开阶段握手

           本节是非规范的。

           打开阶段握手的目的是兼容基于HTTP的服务器软件和中间件,以便单个端口可以用于与服务器交流的HTTP客户端和与服务器交流的WebSocket客户端。最后,WebSocket客户端的握手是一个HTTP Upgrade请求:

            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

           依照[RC2616],握手中的头字段可能由客户端按照任意顺序发送,因此在接收的不同头字段中的顺序是不重要的。

           “Request-URI”的GET方法[RFC2616]用于识别WebSocket连接的端点,即允许从一个IP地址服务的多个域名,也允许由单台服务器的多个WebSocket端点。

           客户端按照[RFC2616]在它的握手的|Host|头字段中包含主机名,以便客户端和服务器都能验证他们同意哪一个正在使用的主机。

           在WebSocket协议中另外的头字段可以用于选择选项。典型的选项在这个版本中可用的是子协议选择器(|Sec-WebSocket-Protocol|)、客户端支持的扩展列表(|Sec-WebSocket-Extensions|)、|Origin|头字段等。|Sec-WebSocket-Protocol|请求头字段可以用来表示客户端接受的子协议(WebSocket协议上的应用级协议层)。服务器选择一个可接受的协议,或不在乎它的握手中回应该值表示它已经选择了那个协议。

            Sec-WebSocket-Protocol: chat

         |Origin| 头字段 [RFC6454]是用于保护防止未授权的被浏览器中使用WebSocket API的脚本跨域使用WebSocket服务器。服务器收到WebSocket连接请求生成的脚本来源。如果服务器不想接受来自此源的连接,它可以选择通过发送一个适当的HTTP错误码拒绝该连接。这个头字段由浏览器客户端发送,对于非浏览器客户端,如果它在这些客户端上下文中有意义,这个头字段可以被发送。

           最后,服务器要证明收到客户端WebSocket握手的信息,以便服务器不接受不是WebSocket连接的连接。这可以防止一个通过使用XMLHttpRequest [XMLHttpRequest]或一个表单提交发送它精心制作的包欺骗WebSocket服务器的攻击者。

           为了证明收到的握手,服务器必须携带两条信息并组合它们并形成一个响应。第一天信息源自客户端握手中的|Sec-WebSocket-Key|头信息:

              Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==

           对于这个头字段,服务器必须携带其值(出现在头字段上,如,减去开头和结尾空格的base64编码 [RFC4648]的版本)并将这个与字符串形成的全局唯一标识符(GUID, [RFC4122]) “258EAFA5-E914-47DA-95CA-C5AB0DC85B11″连接起来,其不太可能被不理解WebSocket协议的网络端点使用。SHA-1散列(160位)[FIPS.180-3],base64编码(参见[RFC4648]第4章)、用于这个的一系列相关事务接着再服务器握手过程中返回。

           具体而言,如果在上面例子中,|Sec-WebSocket-Key|头字段的值为”dGhlIHNhbXBsZSBub25jZQ==”,服务器将连接字符串”258EAFA5-E914-47DA-95CA-C5AB0DC85B11″形成字符串”dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11″。服务器使用SHA-1散列这个,并产生值0xb3 0x7a 0x4f 0x2c 0xc0 0x62 0x4f 0x16 0x90 0xf6 0x46 0x06 0xcf 0x38 0x59 0x45 0xb2 0xbe 0xc4 0xea。这个值接着使用base64编码(参见[RFC4648]第4章),产生值  “s3pPLMBiTxaQ9kYGzzhZRbK+xOo=”。这个值将接着在|Sec-WebSocket-Accept|头字段中回应。

           来自服务器的握手比客户端握手更简单。首行是一个HTTP Status-Line,具有状态码101:

              HTTP/1.1 101 Switching Protocols:

            101以外的任何状态码表示WebSocket握手没有完成且HTTP语义仍适用。头信息遵照该状态码。

           |Connection| 和 |Upgrade|头字段完成HTTP升级。|Sec-WebSocket-Accept|头字段表示服务器是否将接受该连接。如果存在,这个头字段必须包括客户端在|Sec-WebSocket-Key|中现时发送的与预定义的GUID的散列。任何其他值不能被解释为一个服务器可接受的连接。

            HTTP/1.1 101 Switching Protocols

            Upgrade: websocket

            Connection: Upgrade

            Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

           这些字段由WebSocket客户端为脚本页面做检查。如果|Sec-WebSocket-Accept|不能匹配盼望的值、如果头字段缺失或者HTTP状态码不是101,则连接不能建立,且WebSocket帧将不发生。

           可选的字段也可以包含在内。在这合适版本的协议中,主要可选字段是|Sec-WebSocket-Protocol|,其表示服务器选择的子协议。WebSocket客户端验证服务器包含的在WebSocket客户端握手中指定的一个值。声明多个子协议的服务器必须确保它选择第一个,基于客户端握手并指定它在其握手中。

              Sec-WebSocket-Protocol: chat

           服务器也可以设置cookie相关的可选字段为_set_ cookies,描述在[RFC6265]。

     

    1.4  关闭阶段握手

           本节是非规范的。

           关闭阶段握手比打开阶段握手简单得多。

           两个节点中的任一个都能发送一个控制帧与包含一个指定控制序列的数据来开始关闭阶段的握手(详见5.5.1节)。在收到这样一个帧时,另一个节点在响应中发送一个Close帧,如果还没有发送一个。在收到那个控制帧时,第一个节点接着关闭连接,安全地知道没有更多的数据到来。

           发送一个控制帧之后,表示连接将被关闭,一个节点不会发送任何更多的数据;在接收到一个控制帧之后,表示连接将被关闭,一个节点会丢弃收到的任何更多的数据。

           对于两个节点同时地初始化这个握手是安全的。

           关闭阶段握手的目的是完成TCP关闭握手(FIN/ACK),基于TCP关闭阶段握手不总是可靠的端到端,尤其存在拦截代理和中间件。

           通过发送一个Close帧并等待响应中的Close帧,某些情况下可避免数据不必要的丢失。例如,在某些平台上,如果一个socket关闭了,且接收队列中有数据,一个RST包被发送了,这样会导致接受RST的一方的recv()失败,即使有数据等待读取。

     

    1.5  设计理念

           本节是非规范的。

           WebSocket协议应该以最小帧的原则设计(唯一存在的框架是使协议基于帧而不是基于流且支持区分Unicode文本和二进制)。期望通过应用层将元数据分层在WebSocket之上,通用的,通过应用层将元数据分层在TCP之上(例如HTTP)。

           从概念上讲,WebSocket只是TCP之上的一层,执行以下操作。

           1、为浏览器添加一个web基于来源的安全模型

           2、添加一个寻址和协议命名机制来支持在一个IP地址的一个端口的多个主机名的多个服务。

           3、在TCP之上分一个帧机制层已回到TCP基于的IP包机制,但没有长度限制。

           4、包括一个额外的带内(in-band)关闭阶段握手,其被设计来工作在现存的代理和其他中间件。

           除此之外,WebSocket没有添加任何东西。基本上,它的目的是尽可能接近仅暴露原始TCP到脚本,尽可能考虑到Web的约束。它也被设计为它的服务器能与HTTP服务器共享一个端口的这样一种方式,通过持有它的握手是一个有效的HTTP Upgrade请求。一个可以在概念上使用其他协议来建立客户端-服务器消息,但WebSocket的意图是提供一个相对简单的协议,可以与现有的HTTP和部署的HTTP基础设施(例如代理)同时存在,并尽可能接近TCP,且对于使用考虑到安全考虑的这样的基础设施同样是安全的,有针对性的补充以简化使用并保持简单的事情简单(如增加的消息语义)。

           协议的目的是为了可拓展性:未来版本将可能引入额外的概念,如复用(multiplexing)。

     

    1.6  安全模型

           本节是非规范的。

           WebSocket协议使用浏览器使用的来源模型限制web页面可以与WebSocket服务器通信,当WebSocket协议是从一个web页面使用。当然,当WebSocket协议被一个独立的客户端直接使用时(也就是,不是从浏览器中的一个web页面),来源模型不再有用,因为客户端可以提供任意随意的来源字符串。

           该协议的目的是无法与现有的协议,如SMTP [RFC5321]和HTTP建立一个连接,同时允许HTTP服务器来选择支持该协议如果想要。这是通过具有严格的和详尽的握手和通过限制在握手完成之前能被插入到连接的数据(因此限制多少服务器可以被应用)实现的。

           当数据时来自其他协议时,同样的目的是无法建立连接的,尤其发送到一个WebSocket服务器的HTTP,例如,如果一个HTML表单提交到WebSocket服务器可能会发生。这主要通过要求服务器验证它读取的握手来实现,它只能做如果握手包含适当的部分,只能通过一个WebSocket客户端发送。尤其是,在写本规范的时候,|Sec-|开头的字段不能又web浏览器的攻击者设置,仅能使用HTML和JavaScript APIs例如XMLHttpRequest [XMLHttpRequest]。

     

    1.7  TCPHTTP的关系

           本节是非规范的。

           WebSocket协议是一个独立的基于TCP的协议。它与HTTP唯一的关系是它的握手是由HTTP服务器解释为一个Upgrade请求。

           默认情况下,WebSocket协议使用端口80用于常规的WebSocket连接和端口443用于WebSocket连接的在传输层安全(TLS) [RFC2818]之上的隧道化。

     

    1.8  建立连接

           本节是非规范的

           当一个连接到一个HTTP服务器共享的端口时(这种情况是很有可能在传输信息到端口80和443出现),连接将出现在HTTP服务器,是一个正常的具有一个Upgrade提议的GET请求。在相对简单的安装,只用一个IP地址和单台服务器用于所有的数据传输到单个主机名,这可能允许一个切实可行的办法对基于WebSocket协议的系统进行部署。在更复杂的安装(例如,负载均衡和多服务器),一组独立的用于WebSocket连接的主机从HTTP服务器分离出来可能更容易管理。在写该规范的时候,应该指出的是,在端口80和443上的连接有明显不同的成功率,对于在端口443上的连接时明显更有可能成功,尽管这可能会随着时间改变。

     

    1.9  使用WebSocket协议的子协议

           本节是非规范的。

           客户端可能通过包含|Sec-WebSocket-Protocol|字段在它的握手中使用一个特定的子协议请求服务器。如果它被指定,服务器需要在它的响应中包含同样的字段和一个选择的子协议值用于建立连接。

           这些子协议名字应该按照11.5节被注册。为了避免潜在的碰撞,推荐使用包含ASCII版本的子协议发明人的域名的名字。例如,如果Example公司要创建一个Chat子协议,由Web上的很多服务器实现,它们可能命名它为”chat.example.com”。如果Example组织命名它们的竞争子协议为”chat.example.org”,那么两个子协议可能由服务器同时实现,因为服务器根据客户端发送的值动态地选择使用哪一个子协议。

           通过改变子协议的名字,子协议可以以向后不兼容方式版本化,例如,要从”bookings.example.net” 到 “v2.bookings.example.net”。就WebSocket客户端而言,这些子协议被视为是完全不同的。向后兼容的版本可以通过重用相同的子协议字符串实现,但要仔细设置实际的子协议以支持这种可拓展性。

     

    一致性要求

           在本规范中所有的图表、示例和注释是非规范的,以及所有章节明确地标记为非规范的。除此之外,在本规范中的一切是规范的。

           该文档中的关键字必须 “MUST”, 不能”MUST NOT”, 需要”REQUIRED”,应当 “SHALL”, 不得”SHALL NOT”,应该 “SHOULD”, 不应该”SHOULD NOT”, 推荐”RECOMMENDED”, 可能 “MAY”, 和可选的”OPTIONAL” 由 [RFC2119]中的描述解释。

           作为算法的一部分的祈使句中的要求措辞(例如“去掉任何前导空格字符”或“返回false并终止这些步骤”)解释为引入算法中使用的关键字(“MUST”,”SHOULD”,”MAY”等)的意思。

           作为算法或特定的步骤的一致性要求措辞可以以任何形式实现,只要最终结果是相等的。(尤其是,定义在本规范中的算法目的是容易遵循而不必是高性能的)。

     

    2.1  术语和其他约定

           _ASCII_指定义在[ANSI.X3-4.1986]中的字符编码方案。

           此文中提到的UTF-8值和使用 UTF-8标记法格式定义在 STD 63 [RFC3629]。

           关键术语例如命名算法或定义是表示像_this_。

           头字段名字或变量表示像|this|。

           变量值表示像/this/。

           本文档提及的程序_失败WebSocket连接_。该程序定义在7.1.7节。

           _将字符串转换为ASCII小写_意思是替换U+0041到 U+005A(也就是,拉丁文,大写字母A到拉丁文大写字母Z)范围的所有字符为U+0061到 U+007A(也就是,拉丁文,小写字母A到拉丁文小写字母Z)范围对应的字符。

           以一个_ASCII不区分大小写_方式比较两个字符串意思是精确地比较它们,代码点对代码点,除了U+0041 到 U+005A(也就是,拉丁文,大写字母A到拉丁文大写字母Z)范围中的字符,U+0061到 U+007A(也就是,拉丁文,小写字母A到拉丁文小写字母Z)范围对应的字符被认为也匹配。

           用于本文档的术语“URI”定义在[RFC3986]。

           当一个实现需要_发送_作为WebSocket一部分的数据,实现可能任意地推迟实际的传输,例如,缓冲数据为了发送更少的IP包。

           注意,该文档同时使用[RFC5234] 和 [RFC2616]的ABNF变体在不同章节。

     

    3  WebSocket URIs

           本规范定义了两个URI方案,使用定义在RFC 5234 [RFC5234]中的ABNF句法、和术语和由URI规范RFC 3986 [RFC3986]定义的ABNF制品。

              ws-URI = “ws:” “//” host [ “:” port ] path [ “?” query ]

              wss-URI = “wss:” “//” host [ “:” port ] path [ “?” query ]

     

              host = <host, defined in [RFC3986], Section 3.2.2>

              port = <port, defined in [RFC3986], Section 3.2.3>

              path = <path-abempty, defined in [RFC3986], Section 3.3>

              query = <query, defined in [RFC3986], Section 3.4>

           端口组件是可选的;用于“WS”的默认端口试80,而用于“WSS”默认端口时443。

           如果方案组件不区分大写匹配“wss”,URI被称为“安全的”(它是说,“设置了安全标记”)。

           “resource-name”(在4.1节也称为/resource name/)可以通过连接一下来构造:

           1、”/”如果路径组件是空

           2、 “/” if the path component is empty

           3、路径组件

           4、the path component

           5、 “?”如果查询组件是空

           6、”?” if the query component is non-empty

           7、查询组件

           8、 the query component

           片段标识符在WebSocket URI中是无意义的且必须不用在这些URI上。任何URI方法,字符“#”,当不表示片段开始时,必须被转义为%23.

     

    打开阶段握手

     

    4.1  客户单需求

           要_建立WebSocket连接_,客户端打开一个连接并发送一个握手,就像本节中定义的那样。一个连接最初被定义为一个CONNECTING状态。客户端将需要提供一个/host/, /port/,/resource name/, 和  /secure/标记,它们都是在第三章讨论的WebSocket URI的组件,连同一起使用的一个/protocols/ 和 /extensions/列表。此外,如果客户端是一个web浏览器,它提供/origin/。客户端运行在一个受控环境,例如绑定到特定运营商的手机上的浏览器,可以下移(offload)连接管理到网络上的另一个代理。在这种情况下,用于本规范目的的客户端被认为包括手机软件和任何这样的代理。

           当客户端要_建立一个WebSocket连接_,给定一组(/host/, /port/, /resource name/,和/secure/ 标记)、连同一起使用的一个/protocols/ 和 /extensions/列表、和在web浏览器情况下的一个/origin/,它必须打开一个连接、发送一个打开阶段握手、并读取服务器响应中的握手。应如何打开连接的确切要求、在打开阶段握手应发送什么、以及应如何解释服务器响应,在本节如下所述。在下面的文本中,我们将使用第三章中的术语,如定义在那章中的”/host/” 和 “/secure/”标记。

           1. 传入该算法的WebSocket URI组件(/host/, /port/, /resource name/,和 /secure/ 标记)根据指定在第三章的WebSocket URI规范,必须是有效的。如果任何组件是无效的,客户端必须_失败WebSocket连接_并终止这些步骤。

           2. 如果客户已经有一个倒通过主机/host/和端口/port/对标识的远程主机(IP地址)的WebSocket连接,即使远程主机是已知的另一个名字,客户端必须等待直到连接已经建立或由于连接已经失败。必须不超过一个连接处于CONNECTING状态。如果同时多个连接到同一个IP地址,客户端必须序列化它们,以致一次不多于连接在以下步骤中运行。

           如果客户端不能决定远程主机的IP地址(例如,因为所有通信是通过代理服务器本身进行DNS查询),那么客户端必须假定这步的目的是每一个主机名引用一个不同远程主机,且相反,客户端应该限制同时挂起的连接总数为一个适当低的数(例如,客户端可能允许到a.example.com 和 b.example.com同时挂起连接,但如果30个同时连接到同一个请求的主机,那可能是不允许的)。例如,在一个web浏览器上下文中,客户端需要考虑用户已经打开的标签数量,在设置同时挂起的连接数量的限制时。

           注意:这使得它很难仅通过打开大量的WebSocket连接到远程主机为脚本执行一个拒绝服务攻击。当攻击在关闭连接之前被暂停是,服务器可以进一步降低自身的负载,因为这将降低客户端重新连接的速度。

           注意:没有限制一个客户端可以与单个远程主机有的已建立的WebSocket连接数量。服务器可以拒绝接受来自具有大量的现有连接的主机IP地址的连接或当遭受高负载时断开占用资源的连接。

           3. _使用代理_:当有WebSocket协议连接主机/host/和端口/port/时,如果客户端配置使用代理,那么客户端应该连接到代理并要求它打开一个倒由/host/给定主机和/port/给定端口的TCP连接。

           例子:例如,如果客户端所有的信息传输使用一个HTTP代理,那么如果它视图连接到服务器example.com的端口80,它可能会发送以下行到代理服务器:

                  CONNECT example.com:80 HTTP/1.1

                  Host: example.com

           如果还有密码,连接可能看起来像:

                  CONNECT example.com:80 HTTP/1.1

                  Host: example.com

                  Proxy-authorization: Basic ZWRuYW1vZGU6bm9jYXBlcyE=

           如果客户端没有配置使用一个代理,那么应该打开一个直接TCP连接到由/host/给定的主机和/port/给定的端口。

           注意:不暴露明确的UI来为WebSocket连接选择一个独立于其他代理的代理实现,鼓励使用SOCKS5 [RFC1928]代理用于WebSocket连接,如果有的话,或做不到这一点,选择为HTTPS连接配置代理胜过为HTTP连接配置代理。

           为了代理自动配置脚本,传给函数的URI必须从/host/, /port/,/resource name/和/secure/标记来构造,使用第三章给定的WebSocket URI定义。

           注意:WebSocket协议可以在代理自动配置脚本中从模式中识别(“WS”用于未加密的连接和“wss”用于加密的连接)。

           4. 如果连接无法打开,或者因为直接连接失败或者因为任何使用的代理返回一个错误,那么客户端必须_失败WebSocket连接_并终止连接尝试。

           5. 如果/secure/是true,客户端必须在连接之上执行一个TLS握手在打开连接之后和发生握手数据之前[RFC2818]。如果这个失败了(例如,服务器的证书不能被验证),那么客户端必须_失败WebSocket连接_并终止连接。否则,所有在该通道山的进一步通信必须通过加密隧道[RFC5246]。

           客户端必须在TLS握手中使用服务器命名指示拓展[RFC6066]。

     

           一旦一个到服务器的连接(包括通过代理或在TLS加密隧道之上的连接),客户端必须发送一个打开阶段握手到服务器。该握手包括一个HTTP Upgrade请求,连同一个必需的和可选的头字段列表。该握手的要求如下所示:

           1. 握手必须是像[RFC2616]指定的那样的有效的HTTP请求。

           2. 请求方法必须是GET、且HTTP版本必须是至少1.1。

           例如,如果WebSocket URI是”ws://example.com/chat”,发送的第一行应该是”GET /chat HTTP/1.1″。

           3. 请求的”Request-URI”部分必须匹配定义在第三章的(一个相对URI)/resource name/或是一个绝对的http/https URI,当解析时,有一个/resource name/, /host/,和/port/匹配相应的ws/wss URI。

           4. 请求必须包含一个|Host|头字段,其值包含/host/加上可选的”:”后跟/port/(当没有默认端口时)。

           5. 请求必须包含一个|Upgrade|头字段,其值必须包含”websocket”关键字。

           6. 请求必须包含一个|Connection|头字段,其值必须包含”Upgrade”标记。

           7. 请求必须包含一个名字为|Sec-WebSocket-Key|的头字段,这个头字段的值必须是临时组成的一个随机选择的已经base64编码的(参见[RFC4648]第4章)16位的值。临时必须是为每个连接随机选择的。

           注意:作为一个例子,如果随机选择的值是字节序列0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f 0x10,头字段的值必须是”AQIDBAUGBwgJCgsMDQ4PEC==”。

           8. 如果请求来自一个浏览器客户端,请求必须包含一个名字为|Origin| [RFC6454]的头字段。如果连接是来自非浏览器客户端,如果该客户端的语义匹配描述在这的用于浏览器的使用情况时,请求可以包含这个字段。该头字段的值是建立正在运行的连接代码中的环境的origin的ASCII序列化。参考[RFC6454]获取如果构造该头字段的值的详细信息。

           作为一个例子,如果从www.example.com下载的代码试图建立到ww2.example.com的连接,该头字段的值将是”http://www.example.com”。

           9. 请求必须包含一个名字为|Sec-WebSocket-Version|的头字段。该头字段的值必须是13。

           注意:尽管本文档的草案版本(-09, -10, -11,和 -12)发布了(它们多不是编辑上的修改和澄清而不是改变电报协议),值09, 10, 11,和 12不被用作有效的Sec-WebSocket-Version。这些值被保留在IANA注册中心,但并将不会被使用。

           10. 请求可以包含一个名字为|Sec-WebSocket-Protocol|的头字段。如果存在,该值表示一个或者多个逗号分割的客户端要表达的子协议,按优先顺序排列,包含该值的元素必须是非空字符串,且字符在U+0021 到U+007E范围内但不包含定义在[RFC2616]中的分割字符且必须所有是唯一的字符串。用于该头字段值的ABNF是1#token,其构造和规则定义在[RFC2616]给出。

           11. 请求可以包含一个名字为|Sec-WebSocket-Extensions|的头字段。如果存在,该值表示客户端想要表达的协议级的扩展。该头字段的解释和格式描述在第9.1节。

           12. 请求可以包含任意其他头字段,例如,cookies [RFC6265]和/或验证相关的头字段,例如|Authorization头字段 [RFC2616],其根据定义它们的文档处理。

          

           一旦客户端的打开阶段握手已经发送,客户端在发送任何进一步数据之前必须等待自服务器的一个响应。客户端必须验证服务器的响应,如下所示:

           1. 如果收到的服务器的状态码不是101,客户端处理每个HTTP [RFC2616]程序的响应。尤其是,如果收到一个401状态码客户端可能执行身份验证;服务器可能使用一个3XX状态码重定向客户端(但客户端不需要跟随他们)等等。否则,按以下步骤处理。

           2. 如果响应缺少一个|Upgrade|头字段或|Upgrade|头字段包含的值不是一个不区分大小写的ASCII匹配值”websocket”,客户端必须_失败WebSocket连接_。

           3. 如果想要缺少一个|Connection|头字段或者|Connection|头字段不包含一个不区分大小写的ASCII匹配值”Upgrade”符号,客户端必须_失败WebSocket连接_。

           4. 如果想要缺少一个|Sec-WebSocket-Accept|头字段或 |Sec-WebSocket-Accept| 包含一个不是|Sec-WebSocket-Key|(一个字符串,不是base64编码的)与字符串 “258EAFA5-E914-47DA-95CA-C5AB0DC85B11″但忽略任何前导和结尾空格相关联的base64编码的SHA-1值,客户端必须_失败WebSocket连接_。

           5. 如果响应包含一个|Sec-WebSocket-Extensions|头字段且头字段表示使用一个扩展但没有出现在客户端握手中(服务器表示的一个扩展,不是客户端请求的),客户端必须_失败WebSocket连接_。解析该头字段以确定请求了哪些扩展在9.1节讨论。

           6. 如果响应包含一个|Sec-WebSocket-Protocol|头字段且该头字段表示使用一个字协议但没出现在客户端握手中(服务器表示的一个子协议,不是客户端请求的),客户端必须_失败WebSocket连接_。

           如果服务器响应不符合定义在本节和4.2.2节中的服务器握手的要求,客户端必须_失败WebSocket连接_。

           请注意,根据[RFC2616],所有命名在HTTP请求和HTTP响应中的头字段是不区分大小写的。

           如果服务器响应验证了以上提供的,这是说,_WebSocket连接建立了_且WebSocket连接处于OPEN状态。_使用中的扩展_被定义为一个(可能为空)字符串,其值等于服务器握手中提供的|Sec-WebSocket-Extensions|头字段的值或如果在服务器握手中没有该头字段则为null值。_使用中的子协议_被定义为服务器握手中的|Sec-WebSocket-Protocol|头字段的值或如果在服务器握手中没有该头字段则为null值。另外,如果在服务器握手中表示cookie应该被设置(定义在[RFC6265])的任何头字段,这些cookie被称为_在服务器打开阶段握手期间的Cookie设置。

     

    4.2  服务器端要求

           服务器可以下移(offload)连接管理到网络上的其他代理,例如,负载均衡和反向代理。在这样的情况下,用于本规范的目的的服务器被认为是包括服务器端基础设施的所有部分,从开始的设备到终止TCP连接,处理请求和发送响应的服务器的所有方式。

           例如:一个数据中心可能有一个用适当的握手来响应WebSocket请求的服务器,并按照传递连接到另一个服务器来真正处理数据帧。对于本规范的目的,“服务器”是结合了两种计算机。

    4.2.1  读取客户端的打开阶段握手

           当客户端开始一个WebSocket连接,它发送它的打开阶段握手部分。服务器必须至少解析这个握手为了获取必要的信息来生成服务器握手部分。

           客户端打开阶段握手包括以下部分。如果服务器,当读取握手时,发现客户端没有发送一个匹配下面描述的握手(注意,按照[RFC2616],头字段顺序是不重要的),包括但不限制任何违反ABNF语法指定的握手组件,服务器必须停止处理客户端握手并返回一个具有一个适当错误码的(例如400错误的请求)HTTP响应。

           1. 一个HTTP/1.1或更高版本的GET请求,包括一个”Request-URI” [RFC2616]应该被解释为定义在第3幢的/resource name/(或一个包含/resource name/的绝对HTTP/HTTPS URI)。

           2. 一个|Host|头字段包含服务器的权限。

           3. 一个|Upgrade|头字段包含值”websocket”,视为一个不区分大小写的ASCII值。

           4. 一个|Connection|头字段包含符号”Upgrade”,视为一个不区分大小写的ASCII值。

           5. 一个|Sec-WebSocket-Key|头字段,带有一个base64编码的值(参见[RFC4648]第4章),当解码是,长度是16字节。

           6. 一个|Sec-WebSocket-Version|头字段,带有值13.

           7. 可选的,一个|Origin|头字段。该头字段由所有浏览器客户端发送。一个试图缺失此头字段的连接不应该被解释为来自浏览器客户端。

           8. 可选的,一个|Sec-WebSocket-Protocol|头字段,带有表示客户端想要表达的协议的值列表,按优先顺序排列。

           9. 可选的,一个|Sec-WebSocket-Extensions|头字段,带有表示客户端想要表达的扩展的值列表。此头字段的解释在9.1节讨论。

           10. 可选的,其他头字段,例如这些用于发送cookie或请求服务器身份验证的。未知的头字段被忽略,按照[RFC2616]。

     

    4.2.2  发送服务器的打开阶段握手

           当客户端建议一个到服务器的WebSocket连接,服务器必须完成以下步骤来接受该连接并发送服务器的打开阶段握手。

           1. 如果连接发送在一个HTTPS (HTTP-over-TLS)端口上,在连接之上执行一个TLS握手。如果失败了(例如,在扩展的客户端hello “server_name”扩展中的客户端指示的一个主机名,服务器对主机不可用),则关闭该连接;否则,用于该连接的所有进一步的通信必须贯穿加密的隧道[RFC5246]。

           2. 服务器可以执行额外的客户端身份认证,例如,返回401状态码与描述在[RFC2616]中的相关|WWW-Authenticate|头字段。

           3. 服务器可以使用3xx状态码[RFC2616]重定向客户端。注意,此步骤可能连同,之前,或之后的可选的上面描述的身份验证步骤一起发生。

           4. 建立如下信息:

           /origin/

           客户端握手中的|Origin|头字段表示建立连接的脚本的来源。Origin是序列化为ASCII并转换为小写。服务器可以使用这个信息作为决定是否接受传入连接的一部分。如果服务器没有验证origin,它将接受来自任何地方的连接。如果服务器不想接受这个连接,它必须返回一个适当的HTTP错误码(例如,403 Forbidden)并中断描述在本章中的WebSocket握手。更多的详细信息,请参阅第10章。

           /key/

           在客户端握手中的|Sec-WebSocket-Key|头字段包括一个base64编码值,如果解码,长度是16字节。这个(编码的)值用在创建服务器握手时来表示接受连接。服务器没必要使

           /version/

           客户端手中的|Sec-WebSocket-Version|头字段包括客户端试图通信的WebSocket协议的版本。如果该版本没有匹配服务器理解的一个版本,服务器必须中断描述在本节的WebSocket握手并替代返回一个适当的HTTP错误码(例如,426 Upgrade Required)且一个|Sec-WebSocket-Version|头字段表示服务器能理解的版本。

           /resource name/

           由服务器提供的服务的标识符。如果服务器提供多个服务,那么该值应该源自客户端手中的GET方法的”Request-URI”中给定的资源名。如果请求的服务器不可用,服务器必须发生一个适当的HTTP错误码(例如404 NotFound)并中断WebSocket握手。

           /subprotocol/

           或者一个代表服务器准备使用的子协议的单个值或者null。选择的值必须源自客户端握手,从|Sec-WebSocket-Protocol|字段具体选择一个值,服务器将使用它用于这个连接(如果有)。如果客户端握手不包含这样一个头字段或者如果服务器不同意任何客户端请求的子协议,仅接受的值为null。这个字段不存在等价于null值(意思是如果服务器不想同意任何建议的子协议,它必须在它的响应中不发送回一个|Sec-WebSocket-Protocol|头字段)。用于这些目的,空字符串与null值是不一样的,且它不是这个字段合法的值。用于该头字段的值ABNF是(符号),构造定义和规则在[RFC2616]中给出。

           /extensions/

           表示服务器准备使用的协议级别扩展的一个列表(可能为空)。如果服务器支持多个扩展,那么该值必须源自客户端握手,通过从|Sec-WebSocket-Extensions|字段具体地选择一个或多个值。这个字段不存在等价于null值。用于这些目的,空字符串与null值是不一样的。客户未列出的扩展必须不被列出。那些值应该被选择和解释的方法在9.1节讨论。

           5. 如果服务器选择接受传入的连接,它必须以一个有效的表示以下的HTTP响应应答。

           5.1一个按照RFC 2616 [RFC2616]带有101响应吗的Status-Line。这样的响应可能看起来像”HTTP/1.1 101 Switching Protocols”。

           5.2 一个按照RFC 2616 [RFC2616]带有”websocket”的 |Upgrade|头字段。

           5.3 一个带有”Upgrade”的|Connection|头字段。

           5.4 一个|Sec-WebSocket-Accept|头字段,该头字段的值通过连接/key/构造,它定义在4.2.2节第4步,带有字符串”258EAFA5-E914-47DA-95CA-C5AB0DC85B11″,采用SHA-1散列这个连接的值来获取一个20字节的值并base64编码(参考[RFC4648]第4章)这个20字节的散列。

           该头字段的ABNF [RFC2616]定义如下:

               Sec-WebSocket-Accept     = base64-value-non-empty

               base64-value-non-empty = (1*base64-data [ base64-padding ]) |

                                        base64-padding

               base64-data      = 4base64-character

               base64-padding   = (2base64-character “==”) |

                                  (3base64-character “=”)

               base64-character = ALPHA | DIGIT | “+” | “/”

           注意:例如,如果客户端握手中的|Sec-WebSocket-Key|头字段的值是”dGhlIHNhbXBsZSBub25jZQ==”,服务器将追加字符串”258EAFA5-E914-47DA-95CA-C5AB0DC85B11″为字符串”dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-

       C5AB0DC85B11″形式。服务器将采取SHA-1散列这个字符串,并给出值0xb3 0x7a 0x4f 0x2c 0xc0 0x62 0x4f 0x16 0x90 0xf6 0x46 0x06 0xcf 0x38 0x59 0x45 0xb2 0xbe 0xc4 0xea。这个值接着base64编码,给出值”s3pPLMBiTxaQ9kYGzzhZRbK+xOo=”,这将在|Sec-WebSocket-Accept|头字段中被返回。

           5.5 可选的,一个|Sec-WebSocket-Protocol|头字段,带有一个定义在4.2.2节第4不的值/subprotocol/。

           5.6可选的,一个|Sec-WebSocket-Extensions|头字段,带有一个定义在4.2.2节第4步的值/extensions/。如果使用多个扩展,那么可以把所有都列在一个|Sec-WebSocket-Extensions|头字段中或者分配到|Sec-WebSocket-Extensions|头字段的多个实例之间。

           这就完成了服务器握手。如果服务器完成这些步骤且没有中断WebSocket握手,服务器认为WebSocket连接已建立且WebSocket连接处于OPEN状态。此时,服务器可以开始发送(和接收)数据了。

     

    4.3  为握手中使用的新的头字段整理的ABNF

           本节中使用的ABNF语法/规范来自[RFC2616]第2.1节,包括“隐式*LWS规则”。

           注意:以下ABNF约定于本节中。一些规则的名相当于相应的头字段名字。这样的规则表示相应的头字段值,例如Sec-WebSocket-Key ABNF规则描述了 |Sec-WebSocket-Key|头字段值的语法。在名字中带有”-Client”后缀的ABNF规则仅用在由客户端到服务器端发送请求的情况:在名字中带有”-Server”后缀的ABNF规则仅用在由服务器到客户端发生响应的情况。例如,ABNF规则Sec-WebSocket-Protocol-Client描述了客户端到服务器发送的|Sec-WebSocket-Protocol|头字段值的语法。

           以下新的头字段可以在从客户端到服务器握手期间被发送:

          Sec-WebSocket-Key = base64-value-non-empty

          Sec-WebSocket-Extensions = extension-list

          Sec-WebSocket-Protocol-Client = 1#token

          Sec-WebSocket-Version-Client = version

     

          base64-value-non-empty = (1*base64-data [ base64-padding ]) |

                                    base64-padding

          base64-data      = 4base64-character

          base64-padding   = (2base64-character “==”) |

                             (3base64-character “=”)

          base64-character = ALPHA | DIGIT | “+” | “/”

          extension-list = 1#extension

          extension = extension-token *( “;” extension-param )

          extension-token = registered-token

          registered-token = token

          extension-param = token [ “=” (token | quoted-string) ]

               ; When using the quoted-string syntax variant, the value

               ; after quoted-string unescaping MUST conform to the

               ; ‘token’ ABNF.

          NZDIGIT       =  “1” | “2” | “3” | “4” | “5” | “6” |

                           “7” | “8” | “9”

          version = DIGIT | (NZDIGIT DIGIT) |

                    (“1” DIGIT DIGIT) | (“2” DIGIT DIGIT)

                    ; Limited to 0-255 range, with no leading zeros

           以下新的头字段可以在服务器到客户端握手期间被发送:

          Sec-WebSocket-Extensions = extension-list

          Sec-WebSocket-Accept     = base64-value-non-empty

          Sec-WebSocket-Protocol-Server = token

          Sec-WebSocket-Version-Server = 1#version

     

    4.4  支持多个版本的WebSocket协议

           本节提供了在客户端和服务器中支持多个版本的WebSocket协议的一些指导。

           使用WebSocket版本通知能力(|Sec-WebSocket-Version|头字段),客户端可以初始请求它选择的WebSocket协议的版本(这并不一定必须是客户端支持的最新的)。如果服务器支持请求的版本且握手消息是本来有效的,服务器将接受该版本。如果服务器不支持请求的版本,它必须以一个包含所有它将使用的版本的|Sec-WebSocket-Version|头字段(或多个|Sec-WebSocket-Version|头字段)来响应。此时,如果客户端支持一个通知的版本,它可以使用新的版本值重做WebSocket握手。

           以下示例演示了上述的版本协商:

          GET /chat HTTP/1.1

          Host: server.example.com

          Upgrade: websocket

          Connection: Upgrade

          …

          Sec-WebSocket-Version: 25

           服务器的响应可能看起来像如下:

          HTTP/1.1 400 Bad Request

          …

          Sec-WebSocket-Version: 13, 8, 7

           注意服务器最后的响应可能也看起来像:

          HTTP/1.1 400 Bad Request

          …

          Sec-WebSocket-Version: 13

          Sec-WebSocket-Version: 8, 7

           客户端现在可以重做符合版本13的握手:

          GET /chat HTTP/1.1

          Host: server.example.com

          Upgrade: websocket

          Connection: Upgrade

          …

          Sec-WebSocket-Version: 13

     

    5  数据帧

     

    5.1  概述

           在WebSocket协议中,数据使用帧序列来传输。为避免混淆网络中间件(例如拦截代理)和出于安全原因,第10.3节进一步讨论,客户端必须掩码它发送到服务器的所有帧(更多详细信息请参见5.3节)。(注意不管WebSocket协议是否允许在TLS之上,掩码都要做。)当收到一个没有掩码的帧时,服务器必须关闭连接。在这种情况下,服务器可能发送一个定义在7.4.1节的状态码1002(协议错误)的Close帧。服务器必须不掩码发送到客户端的所有帧。如果客户端检测到掩码的帧,它必须关闭连接。在这种情况下,它可能使用定义在7.4.1节的状态码1002(协议错误)。(这些规则可能在未来规范中放宽。)

           基本帧协议定义了带有操作码的帧类型、负载长度、和用于“扩展数据”与“应用数据”以及它们一起定义的“负载数据”的指定位置。某些字节和操作码保留用于未来协议的扩展。

           一个数据帧可以被客户端或者服务器在打开阶段握手完成之后和端点发送Close帧之前的任何时候传输(5.5.1节)。

     

    5.2  基本帧协议

           用于数据传输部分的报文格式是通过本节中详细描述的ABNF来描述。(注意,不像本文档的其他章节,本节中的ABNF是在位bit组上的操作。每一个位组的长度在注释中指出。在编码报文时,最重要的位是在ABNF的最左边。)下图给出了帧的高层次概述。在下图和本节后边指定的ABNF之间冲突的,这个图表时权威的。

          0                   1                   2                   3
          0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
         +-+-+-+-+-------+-+-------------+-------------------------------+
         |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
         |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
         |N|V|V|V|       |S|             |   (if payload len==126/127)   |
         | |1|2|3|       |K|             |                               |
         +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
         |     Extended payload length continued, if payload len == 127  |
         + - - - - - - - - - - - - - - - +-------------------------------+
         |                               |Masking-key, if MASK set to 1  |
         +-------------------------------+-------------------------------+
         | Masking-key (continued)       |          Payload Data         |
         +-------------------------------- - - - - - - - - - - - - - - - +
         :                     Payload Data continued ...                :
         + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
         |                     Payload Data continued ...                |
         +---------------------------------------------------------------+

       FIN:  1 bit

           指示这个是消息的最后片段。第一个片段可能也是最后的片段。

       RSV1, RSV2, RSV3:  1 bit each

           必须是0,除非一个扩展协商为非零值定义含义。如果收到一个非零值且没有协商的扩展定义这个非零值的含义,接收端点必须_失败WebSocket连接_。

       Opcode:  4 bits

           定义了一个“负载数据”的解释。如果收到一个未知的操作码,接收端点必须_失败WebSocket连接_。定义了以下值:

          *  %x0 代表一个继续帧

          *  %x0 denotes a continuation frame

     

          *  %x1 代表一个文本帧

          *  %x1 denotes a text frame

     

          *  %x2 代表一个二进制帧

          *  %x2 denotes a binary frame

     

          *  %x3-7 保留用于未来的非控制帧

          *  %x3-7 are reserved for further non-control frames

     

          *  %x8 代表连接关闭

          *  %x8 denotes a connection close

     

          *  %x9 代表ping

          *  %x9 denotes a ping

     

          *  %xA 代表pong

          *  %xA denotes a pong

     

          *  %xB-F 保留用于未来的控制帧

          *  %xB-F are reserved for further control frames

     

       Mask:  1 bit

           定义是否“负载数据”是掩码的。如果设置为1,一个掩码键出现在masking-key,且这个是用于根据5.3节解掩码(unmask)“负载数据”。从客户端发送到服务器的所有帧有这个位置为1。

       Payload length:  7 bits, 7+16 bits, 或者 7+64 bits

           “负载数据”的长度,以字节为单位:如果0-125,这是负载长度。如果126,之后的两字节解释为一个16位的无符号整数是负载长度。如果127,之后的8字节解释为一个64位的无符号整数(最高有效位必须是0)是负载长度。多字节长度数量以网络字节顺序来表示。注意,在所有的情况下,最小数量的字节必须用于编码长度,例如,一个124字节长的字符串的长度不能被编码为序列126,0,124.负载长度是“扩展数据”长度+“应用数据”长度。“扩展数据”长度可能是零,在这种情况下,负载长度是“应用数据”长度。

       Masking-key:  0 or 4 bytes

           客户端发送到服务器的所有帧通过一个包含在帧中的32位值来掩码。如果mask位设置为1,则该字段存在,如果mask位设置为0,则该字段缺失。详细信息请参见5.3节 客户端到服务器掩码。

       Payload data:  (x+y) bytes

           “负载数据”定义为“扩展数据”连接“应用数据”。

       Extension data:  x bytes

           “扩展数据”是0字节除非已经协商了一个扩展。任何扩展必须指定“扩展数据”的长度,或长度是如何计算的,以及扩展如何使用必须在打开阶段握手期间协商。如果存在,“扩展数据”包含在总负载长度中。

       Application data:  y bytes

    任意的“应用数据”,占用“扩展数据”之后帧的剩余部分。“应用数据”的长度等于负载长度减去“扩展数据”长度。

           基本帧协议是由以下ABNF[RFC5234]正式定义的。重要的是要注意这个数据是二进制表示的,而不是ASCII字符。因此,一个1位长度的字段取值为%x0 / %x1是表示为单个为,其值为0或1,不是以ASCII编码代表字符“0”或“1”的完整字节(8位位组)。4位长度的字段值介于%x0-F之间,是通过4位表示的,不是通过ASCI字符或者这些值的完整字节(8位位组)。[RFC5234]没有指定字符编码:“规则解析为最终值的字符串,有时候被称为字符。在ABNF中,一个字符仅仅是一个非负整数。在某些上下文中,一个值到一个字符集的特定映射(编码)将被指定。”在这里,指定的编码是二进制编码,每一个最终值是编码到指定数量的比特中,每个字段是不同的。

        ws-frame                = frame-fin           ; 1 bit in length

                                  frame-rsv1          ; 1 bit in length

                                  frame-rsv2          ; 1 bit in length

                                  frame-rsv3          ; 1 bit in length

                                  frame-opcode        ; 4 bits in length

                                  frame-masked        ; 1 bit in length

                                  frame-payload-length   ; either 7, 7+16, or 7+64 bits in length

                                  [ frame-masking-key ]  ; 32 bits in length

                                  frame-payload-data     ; n*8 bits in  length, where n >= 0

     

        frame-fin               = %x0 ; more frames of this message follow

                                / %x1 ; final frame of this message

                                      ; 1 bit in length

     

        frame-rsv1              = %x0 / %x1

                                  ; 1 bit in length, MUST be 0 unless

                                  ; negotiated otherwise

     

        frame-rsv2              = %x0 / %x1

                                  ; 1 bit in length, MUST be 0 unless

                                  ; negotiated otherwise

     

        frame-rsv3              = %x0 / %x1

                                  ; 1 bit in length, MUST be 0 unless

                                  ; negotiated otherwise

     

        frame-opcode            = frame-opcode-non-control /

                                  frame-opcode-control /

                                  frame-opcode-cont

     

        frame-opcode-cont       = %x0 ; frame continuation

     

        frame-opcode-non-control= %x1 ; text frame

                                / %x2 ; binary frame

                                / %x3-7

                                ; 4 bits in length,

                                ; reserved for further non-control frames

     

        frame-opcode-control    = %x8 ; connection close

                                / %x9 ; ping

                                / %xA ; pong

                                / %xB-F ; reserved for further control

                                        ; frames

                                        ; 4 bits in length

     

        frame-masked            = %x0

                                ; frame is not masked, no frame-masking-key

                                / %x1

                                ; frame is masked, frame-masking-key present

                                ; 1 bit in length

     

        frame-payload-length    = ( %x00-7D )

                                / ( %x7E frame-payload-length-16 )

                                / ( %x7F frame-payload-length-63 )

                                ; 7, 7+16, or 7+64 bits in length,

                                ; respectively

     

        frame-payload-length-16 = %x0000-FFFF ; 16 bits in length

     

        frame-payload-length-63 = %x0000000000000000-7FFFFFFFFFFFFFFF

                                ; 64 bits in length

     

        frame-masking-key       = 4( %x00-FF )

                                  ; present only if frame-masked is 1

                                  ; 32 bits in length

     

        frame-payload-data      = (frame-masked-extension-data

                                   frame-masked-application-data)

                                ; when frame-masked is 1

                                  / (frame-unmasked-extension-data

                                    frame-unmasked-application-data)

                                ; when frame-masked is 0

     

        frame-masked-extension-data     = *( %x00-FF )

                                ; reserved for future extensibility

                                ; n*8 bits in length, where n >= 0

     

        frame-masked-application-data   = *( %x00-FF )

                                ; n*8 bits in length, where n >= 0

     

        frame-unmasked-extension-data   = *( %x00-FF )

                                ; reserved for future extensibility

                                ; n*8 bits in length, where n >= 0

     

        frame-unmasked-application-data = *( %x00-FF )

                                ; n*8 bits in length, where n >= 0

     

    5.3  客户端到服务器掩码

           一个掩码的帧必须有5.2节定义的字段frame-masked设置为1。

           掩码键完全包含在帧中,5.2节定义的frame-masking-key。它用于掩码定义在相同章节的frame-payload-data中的“负载数据”,其包含“扩展数据”和“应用数据”。

           掩码键是由客户端随机选择的32位值。当准备一个掩码的帧时,客户端必须从允许的32位值集合中选择一个新的掩码键。掩码键需要是不可预测的;因此,掩码键必须来自一个强大的熵源,且用于给定帧的掩码键必须不容易被服务器/代理预测用于后续帧的掩码键。掩码键的不可预测性对防止恶意应用的作者选择出现在报文上的字节是必要的。RFC 4086 [RFC4086]讨论了什么需要一个用于安全敏感应用的合适熵源。

           掩码不影响“负载数据”的长度。变换掩码数据到解掩码数据,或反之亦然,以下算法被应用。相同的算法应用,不管转化的方向,例如,相同的步骤即应用到掩码数据也应用到解掩码数据。

           变换数据的八位位组i是原始数据的八位位组异或i取模4位置的掩码键的八位位组:

         j                   = I MOD 4

         transformed-octet-I = original-octet-I XOR masking-key-octet-j

           负载长度,在帧中以frame-payload-length表示,不包括掩码键的长度。它是“负载数据”的长度,例如,跟在掩码键后边的字节数。

     

    5.4  分片

           分片的主要目的是运行当消息开始但不必缓冲该消息时发送一个未知大小的消息。如果消息不能被分片,那么端点将不得不缓冲整个消息以便在首字节发生之前统计出它的长度。对于分片,服务器或中间件可以选择一个合适大小的缓冲,当缓冲满时,写一个片段到网络。

           第二个分片的用例是用于多路复用,一个逻辑通道上的一个大消息独占输出通道是不可取的,因此多路复用需要可以分割消息为更小的分段来更好的共享输出通道。(注意,多路复用扩展在本文档中没有描述。)

           除非另有扩展规定,帧没有语义含义。一个中间件可能合并并且/或分割帧,如果客户端和服务器没有协商扩展;或如果已协商了一些扩展,但中间件理解所有协商的扩展且知道如何去合并且/或分割在这些扩展中存在的帧。这方面的一个含义是,在没有扩展的情况下,发送者和接收者必须不依赖于特定帧边界的存在。

           以下规则应用到分片:

           1、一个没有分片的消息由单个带有FIN位设置(5.2节)和一个非0操作码的帧组成。

           2、一个分片的消息是单个带有FIN为清零(5.2节)和一个非0操作码的帧组成,跟随零个或多个带有FIN为清零和操作码设置为0的帧,且终止于一个带有FIN为设置且0操作码的帧。一个分片的消息概念上是等价于单个大的消息。其复杂是等价于按顺序串联片段的负载;然而,在存在扩展的情况下,这个可能不适用扩展定义的“扩展数据”存在的解释。例如:“扩展数据”可能仅在首个片段开始处存在且应用到随后的片段,或“扩展数据”可以存在于仅用于到特定片段的每个片段。在没有“扩展数据”的情况下,以下例子展示了分片如何工作。

           例子:对于一个作为三个片段发送的文本消息,第一个片段将有一个0x1操作码和一个FIN位清零,第二个片段将有一个0x0操作码和一个FIN位清零,且第三个片段将有0x0操作码和一个FIN位设置。

           3、控制帧(参见5.5节)可能被注入到一个分片消息的中间。控制帧本身必须不被分割。

           4、消息分片必须按发送者发送顺序交付给收件人。

           5、片段中的一个消息必须不能与片段中的另一个消息交替,除非已协商了一个能解释交替的扩展。

           6、一个端点必须能处理一个分片消息找那个间的控制帧。

           7、一个发送者可以为非控制消息创建任何大小的片段。

           8、 客户端和服务器必须支持接收分片和非分片的消息。

           9、由于控制帧不能被分片,一个中间件必须不尝试改变控制帧的分片。

           10、如果使用了任何保留的位置且这些值的意思对中间件是未知的,一个中间件必须不改变一个消息的分片。

           11、在一个连接的上下文中,已经协商了扩展且中间件不知道协商的扩展的语义,一个中间件必须不改变任何消息的分片。同样,没有看见WebSocket握手(且没有被通知有关它的内容)、导致一个WebSocket连接的一个中间件,必须不改变这个连接的任何消息的分片。

           12、由于这些规则,一个消息的所有分片是相同类型的,以第一个片段的操作码设置。因为控制帧不能被分片,用于一个消息中的所有分片的类型必须是文本、或者二进制、或者一个保留的操作码。

           注意:如果控制帧不能被插入,一个ping延迟,例如,如果跟着一个大消息将是非常长的。因此,要求在分片消息的中间件处理控制帧。

           实现注意:在没有任何扩展时,一个接收者不必按顺序缓冲整个帧来处理它,例如,如果使用了一个流式API,一个帧的一部分能被交付到应用。但是,请注意这个假设可能不适用所有未来的WebSocket扩展。

     

    5.5  控制帧

           控制帧由操作码确定,其中操作码最重要的位是1.当前定义的用于控制帧的操作码包括0x8 (Close), 0x9 (Ping),和0xA (Pong)。操作码0xB-0xF保留用于未来尚未定义的控制帧。

           控制帧用于传达有关WebSocket的状态。控制帧可以插入到分片消息的中间。

           所有的控制帧必须有一个125字节的负载长度或者更少,必须不被分段。

     

    5.5.1  关闭

           关闭帧包含0x8操作码。

           关闭帧可以包含内容体(帧的“应用数据”部分)指示一个关闭的原因,例如端点关闭了、端点收到的帧太大、或端点收到的帧不符合端点期望的格式。如果有内容体,内容体的头两个字节必须是2字节的无符号整数(按网络字节顺序)代表一个在7.4节的/code/值定义的状态码。跟踪2字节的整数,内容体可以包含UTF-8编码的/reason/值,本规范没有定义它的解释。数据不必是人类可读的但可能对调试或传递打开连接的脚本相关的信息是有用的。由于数据不保证人类可读,客户端必须不把它显示给最终的用户。

           客户端发送到服务器的关闭帧必须根据5.3节被掩码。

           在应用发送关闭帧之后,必须不发送任何更多的数据帧。

           如果一个端点接收到一个关闭帧且先前没有发送一个关闭帧,端点必须在响应中发送一个关闭帧。(当在响应中发生关闭帧时,端点通常回送它接收到的状态码)它应该根据实际情况尽快这样做。端点可以延迟发送关闭帧直到它当前消息发送了(例如,如果一个分片消息的大多数已经发送了,端点可以发送剩余的片段在发送一个关闭帧之前)。但是,不保证一个已经发送关闭帧的端点将继续处理数据。

           发送并接收一个关闭消息后,一个端点认为WebSocket连接关闭了且必须关闭底层的TCP连接。服务器必须立即关闭底层TCP连接,客户端应该等待服务器关闭连接但可能在发送和接收一个关闭消息之后的任何时候关闭连接,例如,如果它没有在一个合理的时间周期内接收到服务器的TCP关闭。

           如果客户端和服务器同时都发送了一条关闭消息,两个端点都将发送和接收一个关闭消息且应该认为WebSocket连接关闭了并关闭底层TCP连接。

     

    5.5.2  Ping

           Ping帧包含0x9操作码。

           Ping帧可以包含“应用数据”。

           当接收到一个ping帧时,一个端点必须在响应中发送一个Pong帧,除非它早已接收到一个关闭帧。它应该尽可能快地以Pong帧响应。Pong帧在5.5.3节讨论。

           一个端点可以在连接建立之后并在连接关闭之前的任何时候发送一个Ping帧。

           注意:一个Ping可以充当一个keepalive,也可以作为验证远程端点仍可响应的手段。

     

    5.5.3  Pong

           Pong帧包含一个0xA操作码。

           5.5.2节详细说明了应用Ping和Pong帧的要求。

           一个Pong帧在响应中发送到一个Ping帧必须有在将恢复的Ping帧的消息内容体中发现相同的“应用数据”。

           如果端点接收到一个Ping帧且尚未在响应找那个发送Pong帧到之前的Ping帧,端点可以选择仅为最近处理的Ping帧发送一个Pong帧。

           一个Pong帧可以未经允许请求的发送。这个充当单向的心跳。到未经请求的Pong帧的一个响应式不期望的。

     

    5.6  数据帧

           数据帧(例如,非控制帧)由操作码最高位是0的操作码标识。当前为数据帧定义的操作码包括0x1 (文本), 0x2 (二进制)。  操作码 0x3-0x7保留用于未来尚未定义的非控制帧。

           数据帧携带应用层和/或扩展层数据。操作码决定了数据的解释:

     

           Text

           “负载数据”是编码为UTF-8的文本数据。注意,一个特定的文本帧可能包括部分UTF-8序列;不管怎么样,整个消息必须包含有效的UTF-8。重新组装的消息中无效的UTF-8的处理描述在8.1节。

           Binary

           “负载数据”是随意的二进制数据,其解释仅仅是在应用层。

     

    5.7  示例

           1、未掩码文件消息的单个帧

          *  0x81 0x05 0x48 0x65 0x6c 0x6c 0x6f (contains “Hello”)

           2、掩码的文本消息的单个帧

          *  0x81 0x85 0x37 0xfa 0x21 0x3d 0x7f 0x9f 0x4d 0x51 0x58

             (contains “Hello”)

           3、一个分片的未掩码的文本消息

          *  0x01 0x03 0x48 0x65 0x6c (contains “Hel”)

     

          *  0x80 0x02 0x6c 0x6f (contains “lo”)

           4、未掩码的Ping请求和掩码的Ping响应

          *  0x89 0x05 0x48 0x65 0x6c 0x6c 0x6f (包含内容体“Hello”,但内容体的内容是随意的)

     

          *  0x8a 0x85 0x37 0xfa 0x21 0x3d 0x7f 0x9f 0x4d 0x51 0x58(包含内容体“Hello”,匹配ping的内容体)

           5、单个未掩码帧中的256字节的二进制消息

          *  0x82 0x7E 0x0100 [256字节的二进制数据]

           6、单个未掩码帧中的64KB的二进制消息

          *  0x82 0x7F 0x0000000000010000 [65536字节的二进制数据]

     

    5.8  可拓展性

           协议被设计为允许扩展,这将增加功能到基础协议。端点的一个连接必须在打开阶段握手期间协商使用的任何扩展。本规范提供了用于扩展的操作码0x3 到 0x7 和0xB 到 0xF、“扩展数据”字段、和帧-rsv1、帧-rsv2、和帧-rsv3帧头位。9.1节进一步讨论了扩展协商。以下是一些预期使用的扩展。这个列表是不完整的也是不规范的。

      1、“扩展数据”可以放置在“负载数据”中的“应用数据”之前。

      2、保留的位可以分配给需要的每一个帧。

      3、保留的操作码值能被定义。

      4、如果需要更多的操作码值,保留的位可以分配给操作码字段。

      5、一个保留的位或一个“扩展”操作码可以定义以从“负载数据”中分配额外的位来定义更大的操作码或更多的每位帧位。

     

    6  发送和接收数据

     

    6.1  发送数据

           为了_发送一个WebSocket消息_,其中包括WebSocket连接之上的/data/,端点必须执行以下步骤。

           1. 端点必须确保WebSocket连接处于OPEN状态(比较4.1节和4.2.2节)。如果在任何时候WebSocket连接的状态改变了,端点必须终止以下步骤。

           2. 端点必须封装/data/到定义在5.2节的一个WebSocket帧。如果要发送的数据太大或如果在端点想要开始发生数据时数据作为一个整体不可用,端点可以按照5.2节的定义交替地封装数据到一系列的帧中。

           3. 第一个包含数据的帧的操作码(帧-opcode)必须按照5.2节的定义被设置为适当的值用于接收者解释数据是文本还是二进制数据。

           4. 包含数据的最后帧的FIN位(帧-FIN)必须按照5.2节的定义设置为1。

           5. 如果数据正由客户端发送,帧必须按照5.3节的定义被掩码。

           6. 如果任何扩展(第9章)已经协商用于WebSocket连接,额外的考虑可以按照这些扩展定义来应用。

           7. 已成型的帧必须在底层网络连接之上传输。

     

    6.2  接收数据

           为了接收WebSocket数据,端点监听底层网络连接。传入数据必须按照5.2节的定义解析为WebSocket帧。如果接收到一个控制帧(5.5节),帧必须按照5.5节的定义来处理。当接收到一个数据帧时(5.6节),端点必须注意5.2节由操作码(帧-opcode)定义的数据的/type/。这个真的“应用数据”被定义为消息的/data/。如果帧由一个未分片的消息组成(5.4节),这是说_已经接收到一个WebSocket消息_,其类型为/type/且数据为/data /。如果帧时一个分片消息的一部分,随后数据帧的“应用数据”连接在一起形成/data /.当接收到由FIN位(帧-fin)指示的最后片段时,这是说_已经接收到一个WebSocket消息_,其数据为/data/(由连续片段的“应用数据”组成)且其类型为/type/(分配消息的第一个帧指出)。随后的数据帧必须被解释为属于一个新的WebSocket消息。

           扩展(第9章)可以改变数据如何读的语义,尤其包括什么组成一个消息的边界。扩展,除了在负载中的“应用数据”之前添加“扩展数据”外,也可以修改“应用数据”(例如压缩它)。

           服务器必须按照5.3节的定义为从客户端接收到的数据帧移除掩码。

     

    关闭连接

     

    7.1  定义

     

    7.1.1  关闭WebSocket连接

           为_关闭WebSocket_连接_,端点需关闭底层TCP连接。端点应该使用一个方法完全地关闭TCP连接,以及TLS会话,如果合适,丢弃任何可能已经接收的尾随的字节。当必要时端点可以通过任何可用的手段关闭连接,例如当受到攻击时。

           底层TCP连接,在大多数正常情况下,应该首先被服务器关闭,所以它持有TIME_WAIT状态而不是客户端(因为这会防止它在2个报文最大生存时间(2MSL)内重新打开连接,然而当一个新的带有更高的seq number的SYN时没有对应的服务器影响TIME_WAIT连接被立即重新打开)。在异常情况下(例如在一个合理的时间量后没有接收到服务器的TCP Close)客户端可以发起TCP Close。因此,当服务器被指示_关闭WebSocekt连接_,它应该立即发起一个TCP Close,且当客户端被指示时也这么做时,它应该等待服务器的一个TCP Close。

           例如一个如何使用Berkeley sockets在C中得到完全地关闭的例子,一端会在socket上以SHUT_WR调用shutdown(),调用recv()直到获得一个指示那个节点也已经执行了一个有序关闭的0返回值,且最终在socket上调用close()方法。

    7.1.2  启动WebSocket关闭阶段握手

           为了_启动WebSocket关闭阶段握手_,其带有一个状态码(7.4节)/code/和一个可选的关闭原因(7.1.6节)/reason/,一个端点必须按照5.5.1节的描述发送一个Close控制帧,其状态码设置为/code/且其关闭原因设置为/reason/。一旦一个端点已经发送并接收到一个Close控制帧,哪个端点应该按照7.1.1节的描述_关闭WebSocket连接_。

     

    7.1.3  WebSocket关闭阶段握手已启动

           一旦发送或接收到一个Close控制帧,这就是说,_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节的定义,一个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连接关闭代码_是可能的。

     

    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连接关闭原因_。

     

    7.1.7  失败WebSocket连接

           某些算法和规范要求端点_失败WebSocket 连接_。要做到这一点,客户端必须_关闭WebSocket 连接_,并可以以适当的方式把问题报告给用户(这将对开发人员非常有用的)。同样的,为了做到这一点,服务器必须_关闭WebSocket 连接_,并应该记录下问题。

           如果_已建立的WebSocket连接_在端点需要_失败WebSocket 连接_之前,端点应该在处理_关闭WebSocket 连接_之前发送一个带有适当状态码的Close帧(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  定义的状态码

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

      10001000表示正常关闭,意思是建议的连接已经完成了。

      10011001 表示端点“离开”,例如服务器关闭或浏览器导航到其他页面。

      10021002 表示端点因为协议错误而终止连接。

      10031003 表示端点由于它收到了不能接收的数据类型(例如,端点仅理解文本数据,但接收到了二进制消息)而终止连接。

      1004保留。可能在将来定义某具体的含义。

      10051005 是一个保留值,且不能由端点在关闭控制帧中设置此状态码。它被指定用在期待一个用于表示没有状态码是实际存在的状态码的应用中。

      10061006 是一个保留值,且不能由端点在关闭控制帧中设置此状态码。它被指定用在期待一个用于表示连接异常关闭的状态码的应用中。

      10071007 表示端点因为消息中接收到的数据是不符合消息类型而终止连接(比如,文本消息中存在非UTF-8 [RFC3629]数据)。

      10081008 表示端点因为接收到的消息违反其策略而终止连接。这是一个当没有其它合适状态码(例如1003或1009)或如果需要隐藏策略的具体细节时能被返回的通用状态码。

      10091009 表示端点因接收到的消息对它的处理来说太大而终止连接。

      10101010 表示端点(客户端)因为它期望服务器协商一个或多个扩展,但服务器没有在WebSocket握手响应消息中返回它们而终止连接。所需要的扩展列表应该出现在关闭帧的/reason/部分。注意,这个状态码不能被服务器端使用,因为它可以使WebSocket握手失败。

      10111011 表示服务器端因为遇到了一个不期望的情况使它无法满足请求而终止连接。

      10151015是一个保留值,且不能由端点在关闭帧中被设置为状态码。它被指定用在期待一个用于表示连接由于执行TLS握手失败而关闭的状态码的应用中(比如,服务器证书不能验证)。

     

    7.4.2  保留的状态码范围

      0-9990-999 范围内的状态码不被使用。

      1000-29991000-2999 范围内的状态码保留给本协议、其未来的修订和一个永久的和现成的公共规范中指定的扩展的定义。

      3000-39993000-3999 范围内的状态码保留给库、框架和应用使用。这些状态码直接向IANA注册。本规范未定义这些状态码的解释。

      4000-49994000-4999 范围内的状态码保留用于私有使用且因此不能被注册。这些状态码可以被在WebSocket应用之间的先前的协议使用。本规范未定义这些状态码的解释。

     

    8  错误处理

     

    8.1  处理UTF-8编码数据的错误

           当一个端点解析字节流为UTF-8数据,但发现字节流实际上不是一个有效的UTF-8流,那么端点必须_失败WebSocket连接_。这条规则则应用在打开握手期间和随后的数据交换期间。

     

    扩展

           WebSocket客户端可以请求本规范的扩展,且WebSocket服务器可以接受一些或所有客户端请求的扩展。服务器不必响应不是客户端请求的任何扩展。如果扩展参数包含在客户端和服务器之间的协商中,这些参数必须按照参数应用到的扩展规范来选择。

       WebSocket clients MAY request extensions to this specification, and

     

    9.1  协商扩展

           客户端通过包含一个|Sec-WebSocket- Extensions|头字段请求扩展,其按照正常的HTTP头字段规则(参考[RFC2616],4.2节)并且头字段的值按照以下ABNF定义[RFC2616]。注意本章使用的ABNF语法/规则来源于[RFC2616]包括“隐式的*LWS规范”。如果客户端或服务器在协商阶段接收到的值不符合下边的ABNF,这种畸形数据的接收人必须立即_失败WebSocket连接_。

             Sec-WebSocket-Extensions = extension-list

             extension-list = 1#extension

             extension = extension-token *( “;” extension-param )

             extension-token = registered-token

             registered-token = token

             extension-param = token [ “=” (token | quoted-string) ]

      当使用引用字符串的语法变种是,引用字符串之后的值必须符合token’ ABNF.

           注意,像其他HTTP头字段,这个头字段可以跨多个行分割或组合,因此,以下是等价的:

             Sec-WebSocket-Extensions: foo

             Sec-WebSocket-Extensions: bar; baz=2

           完全等价于

             Sec-WebSocket-Extensions: foo, bar; baz=2

           所有使用的extension-token必须是一个registered token(参考11.4节)。任何给定扩展提供的参数必须被扩展定义。注意,客户端只需要提供使用任何公布的扩展,除非服务器表示它希望使用扩展,否则必须使用它们。

           注意:扩展的的顺序是重要的。在多个扩展间的相互作用可以定义在定义扩展的文档中。在没有这样定义的情况下,解释是它请求中的客户端列出的头字段表示一个它希望使用的头字段的偏好,第一个列出的选项是最优选的。服务器在响应中列出的扩展表示扩展是实际政治用于连接的扩展。扩展应该修改数据和/或组帧,数据的操作顺序应该假定是与打开阶段握手期间服务器响应中列出的扩展顺序是一样的。

           例如,如果有两个扩展”foo” 和 “bar” ,且如果服务器发送的头字段|Sec-WebSocket-Extensions|有值”foo, bar” ,那么数据上的操作将变为 bar(foo(data)),是更改数据本身(如压缩)或更改可能“堆叠”的组帧。

           可接受的扩展头字段(注意:为了可读性,将折叠较长行)的非规范例子:

             Sec-WebSocket-Extensions: deflate-stream

             Sec-WebSocket-Extensions: mux; max-channels=4; flow-control,

              deflate-stream

             Sec-WebSocket-Extensions: private-extension

           服务器通过包含一个容纳了一个或多个扩展的客户端请求的|Sec-WebSocket-Extensions|头字段来接受一个或多个扩展。所有扩展参数的解释,和什么构成一个有效的      

    9.2  已知扩展

           扩展提供了一种机制来实现选择性加入的附加协议特性。本文档没有定义任何扩展。但实现可以使用单独定义的扩展。

     

    10  安全注意事项

           本章描述了一些适用于WebSocket协议的安全注意事项。具体的安全注意事项在本章的子章节描述。

     

    10.1  非浏览器客户端

           WebSocket协议防止恶意的JavaScript运行在一个受信任的应用内部,比如web浏览器,例如,通过检查头字段|Origin|(见下文)。更多细节请参考1.6节。在一个更强大的客户短的情况下,这样的假设不成立。

           虽然该协议的目的是被web页面中的脚本使用,它也可以被主机直接使用。这样的主机按照它们自己的行为行事,因此可以发送伪造的|Origin|头字段,骗过服务器。因此服务器应该小心,假设它们是直接与来自已知源的脚本通信,且必须考虑到它们可能以非预期方式访问。尤其,服务器不应该相信任何输入是有效的。

           例如:如果服务器使用输入作为SQL查询的一部分,所有输入文本在传输到SQL服务器之前应该被转义,以免服务器收到SQl注入攻击。

     

    10.2  Origin注意事项

           服务器不打算处理来自任意web页面的输入,但仅限于网站应该验证|Origin|头是一个它们盼望的源。如果源指示时服务器不可接受的,那么它应该以一个包含HTTP 403 Forbidden的状态码的回复响应WebSocket握手。

           当不受信任方通常是一个执行在受信任的客户端上下文中的JavaScript应用的作者时,|Origin|头字段可以保护攻击的情况。客户端本身可以与服务器联系,并通过|Origin|头字段机制,决定是否提供JavaScript应用的这些通信权限。目的不是为了阻止非浏览器建立连接,而是确保受信的浏览器在潜在的恶意JavaScript控制下不能伪造WebSocket握手。

     

    10.3  攻击基础设施(掩码)

           除了端点是WebSockets攻击的目标之外,web基础设施的其他部分,如代理,也可能是攻击的对象。在本协议正在开发时,进行了一个实验旨在演示一类代理上的攻击,其导致部署在野的缓存代理中毒。一般的攻击形式是在“攻击者”的控制下建立一个到服务器的连接,执行类似于WebSockets协议建立连接的HTTP连接上的UPGRADE,且随后在已经UPGRADE的连接上发送数据,看起来像一个GET请求一个特定的已知资源(在一次攻击中,很可能会像广泛部署的用于跟踪点击或一个广告服务网络资源的脚本)。远程服务器响应的东西看起来像是到伪造的GET请求的一个响应,且这个响应将被一个非零百分比的部署的中间件缓存,因此使缓存中了毒了。这种攻击的静效应将是如果说服用户去访问攻击者控制的网站,攻击者可能使用户和其他晚于相同缓存的用户的缓存中毒且在其他源运行恶意脚本,影响网络安全模型。

           为避免这种部署的中间件上的攻击,前置不兼容HTTP的和帧一起的应用提供的数据是不够的,因为无法详尽地发现和测试每一个不符合中间件的不跳过这样的非HTTP帧和错误地假装帧负载。因此,采用的防御是掩码所有从客户端到服务端发送的数据,使远程脚本(攻击者)无法控制数据如何在电线上发送,从而无法构造一个可能被中间件误解释的作为一个HTTP请求的消息。

           客户端必须为每一帧选择一个新的掩码密钥,使用一个不能被提供数据的终端应用预测。例如,每次掩码可以从一个强加密的随机数生成器获取。如果使用相同的密钥或存在一个可预测的模式用于选择下一个密钥,当掩码后,攻击者可以发送一个消息,可能作为一个HTTP请求出现(通过交换消息,攻击者希望观察线上并用下一个将被使用的掩码密钥掩码它,当客户端应用它时掩码密钥将有效的解码数据)。

    一旦从客户端传输一个帧已经开始,帧的负载(应用提供的数据)必须不能被应用修改也是必要的。

           否则,攻击者可能发送一个已知初始数据(如都是0)的长帧,一收到数据的第一部分后就开始计算使用的掩码密钥,当掩码后,接着修改尚未发送的作为一个请求出现的帧中的数据(这本质上是和前面段落描述的使用一个已知的或可预测的掩码密钥是相同的问题)。

           如果需要发送额外的数据或要发送的数据以某种方式修改了,新的修改了的数据必须在一个新的帧中发送,那么需要一个新的掩码密钥。总之,一旦开始传输一个帧,对于远程脚本(应用)来说,内容必须不能是可修改的。

           威胁模型用来保护客户端发送的在一个请求出现的数据。因此,需要掩码的信道是从客户端到服务器的数据。服务器到客户端的数据可以作出看起来像一个响应,但为了完成这个请求,客户端也必须有能力去伪造一个请求。因此,没必要在两个方向上掩码数据(从客户端到服务器的数据没有掩码)。

           尽管掩码提供了保护,对于客户端和服务器没有掩码的这种类型的中毒攻击,非兼容的HTTP代理将依然是脆弱的。

     

    10.4  实现特定限制

           实现已经实现-和/或特定平台的有关帧大小或总消息大小的限制,从多个帧重新组装后,必须保证它们自己不超过这些限制。(例如,一个恶意终端无论是通过单一的大帧(例如,2**60大小)还是通过发送一个长流的分片消息的一部分的小帧,可以设法耗尽它的对等体端点(Peer,即要攻击的那一方)的内存或安装一个拒绝服务攻击)。这样的实现应该对帧大小和从多个帧重组后的总消息大小加以限制。

     

    10.5  WebSocket客户端验证

           本规范没有规定任何特定的方式在WebSocket握手期间服务器可以验证客户端。WebSocket服务器可以使用任何客户端对普通HTTP服务器可用的验证机制,如Cookie,HTTP验证,或者TLS验证。

     

    10.6  连接的保密性和完整性

           连接的保密性和完整性是通过运行在TLS (wss URIs)上的WebSocket协议提供的。WebSocket实现必须支持TLS并应该在它们的对等端点通信时使用它。

           对于使用TLS的连接,TLS提供的受益量在很大程度上取决于在TLS握手期间协商的算法强度。例如,一些TLS加密机制不提供连接的保密性。为了实现合理级别的包含,客户端应该仅适用强TLS算法。Web安全上下文:用户接口指南[W3C.REC-wsc-ui-20100812]讨论了什么构成强TLS算法。[RFC5246]的附录 A.5 和附录D.3中提供了额外的指导。

     

    10.7  处理无效数据

           传入的数据必须始终由客户端和服务器验证。如果,在任何时候,一个端点不理解它的数据或违反了一些端点确定的安全输入标准,或当端点看到一个打开阶段握手没有符合它期望的值(例如,在客户端请求中不正确的路径或源),端点可以终止TCP连接。如果在WebSocket握手成功后接收到了无效数据,端点应该在进行_关闭WebSocket连接_之前发送一个带有适当状态码(7.4节)的关闭帧。使用一个带有适当状态码的关闭帧能帮助诊断问题。如果在WebSocket握手期间发送了无效的数据,服务器应该返回一个适当的HTTP[RFC2616]状态码。使用错误的编码发送文本数据是通常出现的一类安全问题。本协议规定一个Text数据类型(而不是Binary或其他类型)的消息包含UTF-8编码的数据。虽然仍指定了长度,且实现本协议的应用应该使用长度来决定帧从哪真正的结束,但以一个不当的编码发送数据仍可能打破建立在本协议之上的应用的假设,导致从误解释数据到丢失数据或潜在的安全漏洞。

     

    10.8  使用SHA-1WebSocket握手

           本文档中描述的WebSocket 握手不依赖于任何SHA-1安全特性,例如碰撞性或抗第二前像攻击(如同[RFC4270]中的描述)。

     

    11  IANA考虑

     

    11.1  注册新的URI模式

     

    11.1.1  注册“ws”模式

      一个|ws| URI标识一个WebSocket服务器和资源名称。

      URI模式名称:ws

      状态: 永久的

      URI模式语法:使用ABNF [RFC5234]语法和URI规范[RFC3986]的ABNF终结符:

                 “ws:” “//” authority path-abempty [ “?” query ]

        <path-abempty> 和 <query> [RFC3986]组件形成的资源名发生给服务器来确定服务期望的类型。其他组件的含义描述在[RFC3986]。

      URI模式语义:这个模式的作用仅是使用WebSocket协议打开一个连接。

      编码考虑:上边定义的语法不包括host组件中的字符,必须按照[RFC3987]从Unicode转换为ASCII或其替换。为了模式标准化的目的,国际化域名(IDN)形式的host组件和它们转换的域名代码被认为是等价的(参考[RFC3987]5.3.3节)。

           上边定义的语法不包括其他组件中的字符,必须按照定义在URI [RFC3986]和国际化资源标识符(IRI) [RFC3986]规范从Unicode编码转换为ASCII,通过首先编码字符为UTF-8,接着使用它们百分数编码的形式替换相应的字节。

    应用/协议使用这个URI模式命名:WebSocket协议

      互操作性考虑:使用WebSocket需要使用HTTP版本1.1或更高

      安全考虑:参考“安全考虑”章节

      联系方式: HYBI WG hybi@ietf.org

      作者/变更管理员: IETF iesg@ietf.org

      参考资源:RFC 6455

     

    11.1.2  注册“wss”模式

      一个|wss| URI标识一个WebSocket服务器和资源名称,并标明在受TLS保护的连接之上的通信(包括标准的TLS的好吃,比如数据保密性和完整性和端点认证)。

      URI模式名称:wss

      状态:永久的

      URI模式语法:使用ABNF [RFC5234]语法和URI规范[RFC3986]的ABNF终结符:

                 “wss:” “//” authority path-abempty [ “?” query ]

             <path-abempty> 和 <query>[RFC3986]组件形成的资源名发生给服务器来确定服务期望的类型。其他组件的含义描述在[RFC3986]。

      URI模式语义:这个模式的作用仅是使用WebSocket协议打开一个使用TLS的连接。

      编码考虑:上边定义的语法不包括host组件中的字符,必须按照[RFC3987]从Unicode转换为ASCII或其替换。为了模式标准化的目的,国际化域名(IDN)形式的hsot组件和它们转换的域名代码被认为是等价的(参考[RFC3987]5.3.3节)。

           上边定义的语法不包括其他组件中的字符,必须按照定义在URI [RFC3986]和国际化资源标识符(IRI)[RFC3987]规范从Unicode编码转换为ASCII,通过首先编码字符为UTF-8,接着使用它们百分数编码的形式替换相应的字节。

      应用/协议使用这个URI模式命名:WebSocket协议

      互操作性考虑:使用WebSocket需要使用HTTP版本1.1或更高。

      安全考虑:参考“安全考虑”章节

      联系方式: HYBI WG <hybi@ietf.org>

      作者/变更管理员: IETF <iesg@ietf.org>

      参考资源: RFC 6455

     

    11.2  注册“WebSocket”HTTPUpgrade关键字

      本节按照RFC 2817 [RFC2817]定义了在HTTP Upgrade符号注册中心注册一个关键字。

      符号名称:WebSocket

      作者/变更管理员:IETF <iesg@ietf.org>

      联系方式:HYBI <hybi@ietf.org>

      参考资源: RFC 6455

     

    11.3  注册新的HTTP头字段

     

    11.3.1  Sec-WebSocket-Key

      本节描述了在永久消息头字段命名注册中心[RFC3864]中注册一个头字段。

      头字段名:Sec-WebSocket-Key

      使用协议:http

      状态:标准

      作者/变更管理员:IETF

      参考资源:RFC 6455

      相关信息:该头字段仅用于WebSocket打开阶段握手。

           |Sec-WebSocket-Key|头字段用于WebSocket打开阶段握手。它从客户端发送到服务器,提供部分信息用于服务器检验它收到了一个有效的WebSocket握手。这有助于确保服务器不接收正被滥用来发送数据给毫不知情的WebSocket服务器的非WebSocket客户端的连接(例如HTTP客户端)。

           |Sec-WebSocket-Key|头字段在一个HTTP请求中不能出现多于一个。

     

    11.3.2  Sec-WebSocket-Extensions

      本节描述了在永久消息头字段命名注册中心[RFC3864]中注册一个头字段。

      头字段名:Sec-WebSocket-Extensions

      使用协议:http

      状态:标准

      作者/变更管理员:IETF

      参考资源:RFC 6455

      相关信息:该头字段仅用于WebSocket打开阶段握手。

           |Sec-WebSocket-Extensions|头字段用于WebSocket打开阶段握手。它最初是从客户端发送到服务器,随后从服务器端发送到客户端,用来达成在整个连接阶段的一组协议级扩展。

           |Sec-WebSocket-Extensions|头字段在HTTP请求中可以出现多次(逻辑上等价于单个|Sec-WebSocket-Extensions|头字段包含的所有值)。但是,|Sec-WebSocket-Extensions|头字段在一个HTTP响应中必须不出现多于一次。

     

    11.3.3  Sec-WebSocket-Accept

      本节描述了在永久消息头字段命名注册中心[RFC3864]中注册一个头字段。

      头字段名:Sec-WebSocket-Accept

      使用协议:http

      状态:标准

      作者/变更管理员:IETF

      规范文档:  RFC 6455

      相关信息:该头字段仅用于WebSocket打开阶段握手。

           |Sec-WebSocket-Accept|头字段用于WebSocket打开阶段握手。它从服务器发送到客户端来确定服务器愿意启动WebSocket连接。

           |Sec-WebSocket-Accept|头在一个HTTP响应中必须不出现多于一次。

     

    11.3.4  Sec-WebSocket-Protocol

      本节描述了在永久消息头字段命名注册中心[RFC3864]中注册一个头字段。

      头字段名:Sec-WebSocket-Protocol

      使用协议:http

      状态:标准

      作者/变更管理员:IETF

      规范文档:RFC 6455

      相关信息:该头字段仅用于WebSocket打开阶段握手。

           |Sec-WebSocket-Protocol|头字段用于WebSocket打开阶段握手。它从客户端发送到服务器端,并从服务器端发回到客户端来确定连接的子协议。这使脚本可以选择一个子协议和确定服务器同一服务子协议。

           |Sec-WebSocket-Protocol|头字段在一个HTTP请求中可以出现多次(逻辑上等价于|Sec-WebSocket-Protocol|头字段包含的所有值)。但是,|Sec-WebSocket-Protocol|头字段在一个HTTP响应必须不出现多于一次。

     

    11.3.5. Sec-WebSocket-Version

      本节描述了在永久消息头字段命名注册中心[RFC3864]中注册一个头字段。

      头字段名:Sec-WebSocket-Version

      使用协议:http

      状态:标准

      作者/变更管理员:IETF

      规范文档:RFC 6455

      相关信息:该头字段仅用于WebSocket打开阶段握手。

           |Sec-WebSocket-Version|头字段用于WebSocket打开阶段握手。它从客户端发送到服务器端来指定连接的协议版本。这能使服务器正确解释打开阶段握手和发送数据的随后数据。如果服务器不能以安全的方式解释数据则关闭连接。当从客户端接收到不匹配服务器端理解的版本是,WebSocket握手错误,|Sec-WebSocket-Version|头字段也从服务器端发送到客户端。在这种情况下,头字段包括服务器端支持的协议版本。

           注意:如果没有期望更高版本号,必然是向下兼容低版本号。

           |Sec-WebSocket-Version|头字段在一个HTTP响应中可以出现多次(逻辑行等价于单个|Sec-WebSocket-Version|透过自动包含的所有值)。但是,|Sec-WebSocket-Version|头字段在HTTP请求中必须不出现多于一次。

     

    11.4  WebSocket扩展名注册

      本规范根据RFC 5226 [RFC5226]陈述的原则,创建一个新的IANA注册用于与WebSocket协议一起使用的WebSocket扩展名。

      作为本注册的一部分,IANA维护以下信息:

      扩展标识符:扩展标识符,将被用在注册到本规范11.3.2节的|Sec-WebSocket-Extensions|头字段。其值必须符合定义在本规范9.1节的扩展-符号要求。

      扩展通用名称:拓展名称,通常称为扩展。

      拓展定义:在扩展用于的WebSocket协议中定义了文档参考。

      已知的不兼容扩展:与此扩展是不兼容的一个扩展标识符列表。

           WebSocket扩展名受制于“先来先服务”的IANA注册策略[RFC5226]。

           在此注册中心没有初始值。

     

    11.5  WebSocket子协议名注册

      本规范根据RFC 5226 [RFC5226]陈述的原则,创建了一个新的IANA注册用于与WebSocket协议一曲使用的WebSocket子协议名。

      作为本注册的一部分,IANA维护以下信息:

      子协议标识符:子协议标识符,将被用在注册到本规范11.3.4节的  |Sec-WebSocket-Protocol|头字段。其值必须符合定义在本规范4.1节给出的第10条的符号要求–也就是,其值必须是RFC 2616 [RFC2616]定义的一个符号。

      子协议通用名称:子协议名称,通常称为子协议

      子协议定义:在子协议用于定义了WebSocket协议的文档参考

      WebSocket子协议名受制于“先来先服务”的IANA注册策略[RFC5226]。

     

    11.6  WebSocket Version Number Registry  WebSocket版本号注册

      本规范依据RFC 5226 [RFC5226]陈述的原则,创建了一个新的IANA注册用于与WebSocket协议一起使用的WebSocket版本号。

      作为注册的一部分,IANA维护以下信息:

      版本号: 用于|Sec-WebSocket-Version|的版本号指定在本规范4.1节。其值必须是一个在0到255(包括)之间的非负整数。

      参考:RFC请求一个新的版本号或带版本号的草案名称(见下文)。

      状态:“临时的”或“标准的”。参考下面的说明。

           一个版本号被临时指定为“临时的”或“标准的”。

            “标准的”版本号是记录在一个RFC中并用来识别一个主要的、稳定的WebSocket协议版本,例如本RFC定义的版本。“标准的”版本号受制于“IETF评审“IANA注册策略。[RFC5226].

           “Interim”的版本号记录在一个Internet草案中用并用于帮助实现这识别和与部署的WebSocket版本互操作,例如在公布这个RFC之前指定的版本。“临时的”版本号受制于“专家评审”IANA注册策略[RFC5226],HYBI工作组主席(或,如果工作组关闭了,IETF应用区域的区域董事)将是初始的指定专家。

           IANA已经添加如下的初始值到注册中心:

     +--------+-----------------------------------------+----------+
       |Version |                Reference                |  Status  |
       | Number |                                         |          |
       +--------+-----------------------------------------+----------+
       | 0      + draft-ietf-hybi-thewebsocketprotocol-00 | Interim  |
       +--------+-----------------------------------------+----------+
       | 1      + draft-ietf-hybi-thewebsocketprotocol-01 | Interim  |
       +--------+-----------------------------------------+----------+
       | 2      + draft-ietf-hybi-thewebsocketprotocol-02 | Interim  |
       +--------+-----------------------------------------+----------+
       | 3      + draft-ietf-hybi-thewebsocketprotocol-03 | Interim  |
       +--------+-----------------------------------------+----------+
       | 4      + draft-ietf-hybi-thewebsocketprotocol-04 | Interim  |
       +--------+-----------------------------------------+----------+
       | 5      + draft-ietf-hybi-thewebsocketprotocol-05 | Interim  |
       +--------+-----------------------------------------+----------+
       | 6      + draft-ietf-hybi-thewebsocketprotocol-06 | Interim  |
       +--------+-----------------------------------------+----------+
       | 7      + draft-ietf-hybi-thewebsocketprotocol-07 | Interim  |
       +--------+-----------------------------------------+----------+
       | 8      + draft-ietf-hybi-thewebsocketprotocol-08 | Interim  |
       +--------+-----------------------------------------+----------+
       | 9      +                Reserved                 |          |
       +--------+-----------------------------------------+----------+
       | 10     +                Reserved                 |          |
       +--------+-----------------------------------------+----------+
       | 11     +                Reserved                 |          |
       +--------+-----------------------------------------+----------+
       | 12     +                Reserved                 |          |
       +--------+-----------------------------------------+----------+
       | 13     +                RFC 6455                 | Standard |
       +--------+-----------------------------------------+----------+

     

    11.7. WebSocket关闭代码注册

           本规范依据RFC 5226 [RFC5226]陈述的原则,创建了一个新的IANA注册用于WebSocket关闭代码。

           作为本注册的一部分,IANA维护以下信息:

      状态码:状态码表示一个按照本文档7.4节的WebSocket连接关闭的原因。状态时一个在1000到4999(包括)之间的一个整数数字。

      含义:状态码的含义。每一个状态码都必须有唯一的含义。

      联系方式:保留状态代码实体的联系方式。

      参考:稳定的文档要求状态码并定义它们的含义。在1000-2999范围内的状态码是必须的且推荐的状态码在3000-3999范围内。

           WebSocket关闭代码根据它们的范围受不同的注册要求。本协议请求使用的状态码和其后续版本或拓展受制于“标准功能”、“规定要求”(这意味着“指定专家”)或“IESG审查”IANA注册策略中的任何一个,且应该允许在1000-2999范围内。库、框架和应用请求使用的状态码受制于“先来先服务”IANA注册策略且应该允许在3000-3999范围内。4000-4999范围的状态码被指定用于私有使用。请求应该指出他们要求的状态码是用于WebSocket协议(或未来版本的协议)、扩展,或库/框架/应用。

           IANA已经添加如下初始值到注册中心:

        |Status Code | Meaning         | Contact       | Reference |
        -+------------+-----------------+---------------+-----------|
         | 1000       | Normal Closure  | hybi@ietf.org | RFC 6455  |
        -+------------+-----------------+---------------+-----------|
         | 1001       | Going Away      | hybi@ietf.org | RFC 6455  |
        -+------------+-----------------+---------------+-----------|
         | 1002       | Protocol error  | hybi@ietf.org | RFC 6455  |
        -+------------+-----------------+---------------+-----------|
         | 1003       | Unsupported Data| hybi@ietf.org | RFC 6455  |
        -+------------+-----------------+---------------+-----------|
         | 1004       | ---Reserved---- | hybi@ietf.org | RFC 6455  |
        -+------------+-----------------+---------------+-----------|
         | 1005       | No Status Rcvd  | hybi@ietf.org | RFC 6455  |
        -+------------+-----------------+---------------+-----------|
         | 1006       | Abnormal Closure| hybi@ietf.org | RFC 6455  |
        -+------------+-----------------+---------------+-----------|
         | 1007       | Invalid frame   | hybi@ietf.org | RFC 6455  |
         |            | payload data    |               |           |
        -+------------+-----------------+---------------+-----------|
         | 1008       | Policy Violation| hybi@ietf.org | RFC 6455  |
        -+------------+-----------------+---------------+-----------|
         | 1009       | Message Too Big | hybi@ietf.org | RFC 6455  |
        -+------------+-----------------+---------------+-----------|
         | 1010       | Mandatory Ext.  | hybi@ietf.org | RFC 6455  |
        -+------------+-----------------+---------------+-----------|
         | 1011       | Internal Server | hybi@ietf.org | RFC 6455  |
         |            | Error           |               |           |
        -+------------+-----------------+---------------+-----------|
         | 1015       | TLS handshake   | hybi@ietf.org | RFC 6455  |
        -+------------+-----------------+---------------+-----------|

     

    11.8  WebSocket操作码注册

           本规范根据RFC 5226 [RFC5226]陈述的原则,创建一个新的IANA注册用于WebSocket操作码。

           作为本注册的一部分,IANA维护以下信息:

      操作码:操作码表示WebSocket帧的帧类型,定义在5.2节。操作码是一个0到15(包括)之间的数字。

      含义:状态码值的含义。

      参考:规范要求的操作码。

           WebSocket状态码受制于“标准功能”IANA注册策略[RFC5226]。

           IANA已经添加了如下的初始值到注册中心:

         |Opcode  | Meaning                             | Reference |
        -+--------+-------------------------------------+-----------|
         | 0      | Continuation Frame                  | RFC 6455  |
        -+--------+-------------------------------------+-----------|
         | 1      | Text Frame                          | RFC 6455  |
        -+--------+-------------------------------------+-----------|
         | 2      | Binary Frame                        | RFC 6455  |
        -+--------+-------------------------------------+-----------|
         | 8      | Connection Close Frame              | RFC 6455  |
        -+--------+-------------------------------------+-----------|
         | 9      | Ping Frame                          | RFC 6455  |
        -+--------+-------------------------------------+-----------|
         | 10     | Pong Frame                          | RFC 6455  |
        -+--------+-------------------------------------+-----------|

     

    11.9  WebSocket帧头位注册

           本规范依据RFC 5226 [RFC5226] 陈述的原则,创建了一个新的IANA注册用于WebSocket 帧头位。此注册控制的位分别标记为5.2节的RSV1,RSV2和 RSV3。

           这些位被保留用于未来版本或本规范的扩展。

           WebSocket帧头位分配受制于“标准功能”IANA注册策略[RFC5226]。

     

    12  其他规范使用WebSocket协议

           WebSocket协议的目的是被另一个规范使用来提供一个通用机制来动态作者-定义内容,例如,在一个规范中定义一个脚本API。

           这样的规范首先需要_建议一个WebSocket连接_,该算法是:

           1、目的地,包含一个/host/和一个 /port/。

           2、一个 /resource name/,运行在一个host和port标识多个服务。

           3、一个 /secure/标记,如果连接时加密的则为true,否则为false。

           4、一个源[RFC6454]的ASCII序列化,负责连接。

           5、可选的,一个字符串标识一个协议,层叠在WebSocket连接之上。

           /host/、 /port/、 /resource name/和 /secure/标记通常从一个URI中使用该步骤解析一个WebSocket URI组件获得。如果没有指定一个WebSocket,则这些步骤失败。

           如果在任何时候连接将被关闭,那么规范需要使用_关闭WebSocket 连接_算法(7.1.1节)。

           7.1.4节定义了什么时候_ WebSocket 连接关闭_。

           当打开一个连接,规范将需要处理什么时候_已经接收了一个WebSocket 消息_的情况(6.2节)。

           要发送一些数据/data/到一个打开的连接,规范需要_发送一个WebSocket 消息_(6.1节)

     

    13  致谢

      略

     

    14参考资料

      略

     

      The WebSocket Protocol [RFC6455],张开涛[译],部分内容有修改。

    展开全文
  • Rust-WebSocket 是一个用 Rust 编写的 WebSocket (RFC6455 ) 库。Rust-WebSocket 提供了一个处理 WebSocket 连接(客户端和服务器)的框架。该库目前处于实验状态,但提供普通和安全 WebSocket 的功能、支持分段的...
  • Internet Engineering Task Force (IETF),WebSocket协议,RFC6455,英文原版,国际标准,December 2011
  • 该项目为PHP提供了6个新功能,旨在与websocket(RFC6455)对等方进行交互。 ws_get_frame_info()-获取有关即将到来的websockets框架的信息ws_read_frame()-从客户端或服务器读取websockets框架的数据ws_send_...
  • websocket_rfc6455.pdf

    2021-12-09 18:42:28
    websocket官方文档
  • 其他的字段也可以使用,比如cookies(RFC6265),头部字段的定义和解析在rfc2616中定义。 如果客户端和服务器成功地完成了握手阶段,那么连接进入数据传输阶段。这是一个双向的连接,连接两段都可以随意发送数据。 ...

    简介

    Websocket协议能在受控的环境内实现浏览器与服务器之间的双向通讯(浏览器中的应用可能是不可靠的,但是仍然可以与服务器建立websocket连接),使得浏览器应用与服务器进行双向通讯时不必同时打开多个HTTP连接(使用XMLHttpRequest 、iframe 或者长轮询实现双向通讯时经常会打开多个连接)。Websocket位于TCP之上(位于应用层),主要包括握手过程、数据传输两个主要部分。

    1.介绍

    1.1.背景

    本部分为非权威描述

    历史上,web应用(即时通讯或者游戏)为了实现与服务器的双向通讯,一般会建立一个发送消息的http连接与一个接收消息的http连接。 这样会导致几个问题:

    1. 服务器与每个客户端维持多个连接。

    2. 网络会产生过多的负载,因为每一个http消息都有头部信息。

    3. 客户端需要把发送消息的连接与接收消息的连接建立映射。

    Websocket协议设计的目标是使用一个连接实现客户端与服务器之间的通讯以此替代http长轮训。Websocket基于http实现双向通讯可以从当前基础设施(代理、过滤、认证)中获得更多支持。 Websocket基于http协议的80和443端口工作,即使这样会增加协议的复杂度。 当然,我们在设计Websocket时没有限制必须基于http协议,未来也许会单独开一个端口,然后使用更简单的握手过程从而替换掉底层依赖的http协议。

    1.2.协议概览

    本部分为非权威描述

    协议主要分为两部分:握手(handshake)和传输(data transfer)。

    客户端发起的握手协议如下:

            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
    

    客户端发送的首行遵循Request-Line格式。 服务器响应的首行遵循Status-Line格式。这两种格式都在rfc2616中定义。

    Request-Line或者Status-Line之后都跟随着一组无序的头部字段。这些字段的具体意义,在本文第四章有讲述。其他的字段也可以使用,比如cookies(RFC6265),头部字段的定义和解析在rfc2616中定义。 如果客户端和服务器成功地完成了握手阶段,那么连接进入数据传输阶段。这是一个双向的连接,连接两段都可以随意发送数据。

    握手成功之后,客户端和服务器可以相互发送数据,我们把相互发送的数据单位称作消息(message)。实际传输过程中,一个消息可能包含多个frame(帧)。

    每个帧都有特定类型。同属于一个消息的数据帧拥有相同的数据类型。数据类型包括文本(UTF-8编码的字符)、二进制(具体解析方式由程序定义)、控制(用作控制作用,比如管理连接的打开和关闭)。这个版本的协议定义了六种数据类型,并且预留十种类型。

    1.3.开始握手

    本部分为非权威描述
    握手阶段主要用来兼容http服务器或者中间件,这样同一个端口就可以使用websocket协议和http协议。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]兼容,头部字段可以以任何顺序排列。

    Get方法中的Request-URI用来指定可以处理websocket请求的服务器接口,这样可以在一个ip下部署多台服务器,也可以在一台服务器中部署多个应用服务器(根据Request-URI进行路由)。

    客户端在发送的请求头部添加|Host|字段,指定要连接的主机名字。

    其他的字段用来指定协议提供的其他选项。常见的选项有子协议(|Sec-WebSocket-Protocol|)、子协议扩展(|Sec-WebSocket-Extensions|)、|Origin|字段等。 |Sec-WebSocket-Protocol|列出了客户端支持的子协议列表,服务器选择其中一个协议并返回给客户端。

    Sec-WebSocket-Protocol: chat

    |Origin|字段方便服务器识别未授权的浏览器应用发送的websocket连接建立请求。服务器可以通过这个字段获取客户端的Origin信息,如果服务器不接受来自这个Origin的连接建立请求,可以拒绝客户端的请求。|Origin|字段的值是由浏览器设置的,非浏览器环境的客户端可以根据当时环境设置合理的值。

    为了证明服务器收到了来自客户端发送的握手信息,服务器需要使用两个字符串生成一个响应信息。第一个信息来自客户端握手消息中的|Sec-WebSocket-Key|字段:

    Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==

    服务器把Sec-WebSocket-Key字段的值与GUID “258EAFA5-E914-47DA-95CA-C5AB0DC85B11”(如果网络节点中没有运行websocket协议的话会很难理解这个字符串的含义)连接生成一个字符串,然后进行SHA-1运算,最后进行base64编码,生成的数据作为服务器的响应。

    比如:客户端发送的字段|Sec-WebSocket-Key|中包含的值为dGhlIHNhbXBsZSBub25jZQ==,服务器把这个值与258EAFA5-E914-47DA-95CA-C5AB0DC85B11进行连接,然后生成dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11字符串,进行SHA-1运算,生成0xb3 0x7a 0x4f 0x2c 0xc0 0x62 0x4f 0x16 0x90 0xf6 0x46 0x06 0xcf 0x38 0x59 0x45 0xb2 0xbe 0xc4 0xea,最后进行base64编码生成s3pPLMBiTxaQ9kYGzzhZRbK+xOo=,然后放入服务器的|Sec-WebSocket-Accept|字段中并响应客户端请求。

    服务器返回的握手信息很简单,第一行为http状态行,状态为101:

    HTTP/1.1 101 Switching Protocols

    其他状态码表示握手还没完成,状态码的含义依然遵循http定义。

    |Connection|和|Upgrade|表示握手过程完成。|Sec-WebSocket-Accept|表示服务器是否接收这个连接,如果有这个字段,这个字段的值必须为客户端提供的|Sec-WebSocket-Key|字段的值与预先定义好的GUID值进行哈希,在进行base64编码。任何其他的值都表明服务器没有接受客户端发起的请求。

       HTTP/1.1 101 Switching Protocols
       Upgrade: websocket
       Connection: Upgrade
       Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
    

    这些字段会在客户端进行校验,如果|Sec-WebSocket-Accept|的值与客户端期望的值不一致、没有这个字段或者HTTP状态码不为101,那么连接不会被建立并且websocket数据帧不会发送。

    |Sec-WebSocket-Protocol|表示服务器选择的子协议。客户端校验服务器返回的值是否为客户端。

    Sec-WebSocket-Protocol: chat

    1.4.挥手过程

    本部分为非权威描述

    挥手过程要比打开过程简单的多。 任何一端都可以发送一个Close帧来开始挥手过程,Close帧可能带有部分数据(比如描述关闭的原因以及状态码)。任何一端收到一个Close帧,如果之前没有回复过的话,需要发送Close帧。主动关闭的一端在收到对端返回的响应后,在确定没有数据需要继续接收之后,开始关闭底层连接(shutdown)。

    在发送Close帧之后不应该发送任何数据帧,在收到对端发送过来的Close帧后,对于后续的数据,接收端不予处理。

    两端可以同时发送挥手控制帧。

    挥手控制帧用来关闭两端之间的tcp连接,因为有时两端之间并不是直接相连,中间有可能会有代理或者其他中间设备。

    发送一个挥手控制帧然后等待响应可以防止某些情况下丢失数据。比如在某些软件平台,如果socket一端在接收队列里还有未处理的数据,但是关闭了连接,这时会向对端发送一个rst消息,对端在收到rst消息后会让在recv()监听的线程收到函数返回的错误信息,即使当时接收队列里还有数据(因为两端都没有成功处理消息,所以两端需要对类似的rst错误进行处理)。

    1.5.设计哲学

    本部分为非权威描述

    Websocket协议应该尽量减少使用帧相关的概念(只有在描述协议是基于帧的而不是基于流的时候与用来区分文本帧和二进制帧的时候会涉及到帧)。应用层在websocket层之上,所发送的数据都会经过websocket这一层去传递,这一点与http使用tcp去发送数据大致相同。

    概念上讲,websocket就是基于tcp的协议,拥有以下功能:

    1. 为浏览器添加基于origin的安全模型
    2. 增加寻址和命名服务,可以实现多个服务(http和websocket)监听同一个端口,同时也可以实现一个ip有多个主机名字。
    3. 在tcp之上建立分帧功能,类似tcp基于的ip协议,但是websocket的帧没有长度限制。
    4. 增加挥手过程,用来解决链路中的代理或者其他中间系统。

    除了上面列出的功能外,websocket没有增加其他功能。考虑到web浏览器的限制,尽可能的只把原生的tcp接口暴露给脚本去调用。如果客户端发送过来的是合法的http升级请求,那么websocket服务器可以与http服务器共享一个端口。有人可能会使用其他协议来实现客户端和服务器之间的消息通讯,但是websocket协议的设计初衷就是提供一个相对简单的协议来与http协议或者http基础设施(代理)共存,websocket提供的长连接使得经过这些基础设施时与直接使用tcp一样安全,并且通过增加一些附属功能来简化使用的方式。

    协议具有扩展性,未来版本可能会新增其他的功能(概念),比如多路复用。

    1.6.安全模型

    本部分为非权威描述

    Websocket协议使用与web浏览器一样的安全模型(origin-based)来控制应用可以与哪些服务器建立连接。如果在一个专用的客户端中使用websocket协议,这个安全模型就显得没有必要了,因为客户端可以提供任何可能的origin值。

    运行[SMTP]与HTTP协议的服务器不会与websocket客户端建立连接,但是如果HTTP服务器支持升级到websocket协议则可以建立连接。为了保证协议的正确性,在握手没有完成之前不可以发送应用数据。

    如果websocket服务器接收到了其他协议的(主要指http协议数据)数据,那么整个连接会被关闭。Websocket在握手阶段会使用专用的头部字段,服务器可以通过验证这些专用的头部字段来保证握手过程的合法性,在这个规范编写的时候,网络攻击者不会在网页应用(html和js)中使用XMLHttpRequest发送带有|Sec-|前缀的头部字段。

    1.7.与TCP和HTTP的关系

    本部分为非权威描述

    Websocket是基于TCP独立设计的协议。与HTTP的唯一关系是,websocket的握手协议是通过HTTP的协议升级实现的。

    默认情况下,普通websocket连接使用80端口,安全的websocket连接使用443端口,基于TLS安全层。

    1.8.建立一个连接

    本部分为非权威描述

    向一个既支持websocket协议又支持http协议的服务器发送websocket请求时,应该使用传统GET请求,并且带有Upgrade头部字段。在简单的部署场景中,一台服务器可以同时支持websocket与http协议。在一些复杂的场景中(多机部署以及负载均衡部署),websocket服务器和http服务器分开部署易于管理。在编写规范的时候,80端口和443端口的连接成功率不一样,443的成功率要高点,这个可能随着时间的变化会有所变化。

    1.9.使用子协议

    本部分为非权威描述

    客户端可以在握手请求中添加|Sec-WebSocket-Protocol|字段要求服务器从中选择一个支持的子协议。服务器在握手响应中需要包含这个字段并且选择支持的子协议。

    子协议按照[Section 11.5]规定注册名字。为了避免名字冲突,协议名字应该包含子协议指定方的主机名字,并且主机名字是ascii编码格式的。举个例子,比如Example公司打算创建一个chat子协议,这个协议可能会被网络上的很多服务器使用,那么这个子协议的名字可以命名chat.example.com。如果Example组织也创建了自己的子协议名称,命名为chat.example.org,那么多个服务器可以同时实现,并且在握手过程中从客户端提供的子协议列表中选择一个。

    子协议为了实现向后兼容可以更改名称,比如bookings.example.net改成v2.bookings.example.net。这些协议可以被客户端轻松的分辨出来。重用相同的子协议名字也可以实现向后兼容,但是这么做的时候需要认真设计子协议(比如通过其他扩展字段实现版本化来支持向后兼容)。

    2.规范要求

    如果有的图表、示例、注释在这个文档中标记为非规范的,表示非标准规范,没有显式标记的都是规范标准。

    文本中的关键字,“MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL"都在[RFC2119]中进行记录。 在描述算法的文字中,那些祈使句一般可以解释成"MUST”, “SHOULD”, “MAY"等(比如"strip any leading space characters” or “return false and abort these steps”)。

    规范如果被表述为某些算法或者某些规定的步骤,那么他们的实现方式可能是多样性的,但是如果他们的结果是一样的,那就是可以接受的(特别的,本规范里规定的算法都很简单,并且方便实现。)。

    2.1.术语和其他约定

    _ASCII_表示[ANSI.X3-4.1986]中描述的字符编码。

    本文档使用定义在[RFC3629]中的UTF-8字符编码。

    关键字和算法命名与定义都用_this_表示。

    头部字段和变量使用|this|这种格式。

    变量值使用/this/。

    [Section 7.1.7]中描述的流程定义为_Fail the WebSocketConnection_。

    关键字_Converting a string to ASCII lowercase_ 是指把U+0041 到 U+005A区间的字符替换成U+0061 到 U+007A区间的字符。

    _ASCII case-insensitive_表示比较两个字符串是大小写不敏感的,字母A-Z与a-z之间相应的字母认为是相同的(A与a是相同的)。

    URI的意思与[RFC3986]定义的一样。

    当websocket实现被要求_send_发送一个数据时,具体实现可以按照需要在某个时间去真正发送数据(数据可能事先在buffer中缓存)。

    本文在不同的章节都使用使用[RFC5234]和[RFC2616]中的[ABNF]扩展语法规则。

    3.WebSocket URIs


    本规范使用了两种URI框架,使用了[RFC 5234]中定义的ABNF语法与[RFC 3986 ]中定义的单词和其他的一些术语。

        ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ]
        wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ]
        
        host = <host, defined in [RFC3986], Section 3.2.2>
        port = <port, defined in [RFC3986], Section 3.2.3>
        path = <path-abempty, defined in [RFC3986], Section 3.3>
        query = <query, defined in [RFC3986], Section 3.4>
    

    端口组件是可选的,ws默认是80端口,wss默认端口是443。

    如果端口组件是wss(大小写不敏感)的话,那么这个连接是安全连接。

    资源名字可以由一下几部分组成:

    • 如果path为空,使用"/"。
    • path组件。
    • 如果query组件不为空使用?。
    • query组件。

    段落标识符在websocket uri中没有意义,在所有的URI框架中,如果#不表示段落的开始,那么应该使用%23进行转义。

    4.开始握手

    4.1.客户端要求

    客户端与服务器成功建立socket连接之后会发送websocket握手信息。Websocket在开始阶段处于CONNECTING状态。客户端需要提供/host/, /port/, /resource name/和 /secure/,具体含义在第三章中有描述,除了这些参数之外有可能会提供/protocols/ 和 /extensions/列表,如果客户端是浏览器,还需要添加/origin/。

    便携式设备中的浏览器访问网络时可能会通过某些代理软件,所以,本规范的客户端包括了便携式设备中的浏览器软件和代理软件。

    使用(/host/, /port/, /resource name/和 /secure/ )、/protocols/ 、 /extensions/、/origin/(如果客户端是浏览器)来与服务器建立连接,然后发送握手请求,等待服务器的响应。具体的如何打开连接,如何发送握手请求,服务器如何对握手请求进行回应,都会在下文进行描述。后续我们会使用第三章中规定的单词进行讨论(比如/host/与/secure/标识符)。

    1. 传入的/host/, /port/, /resource name/和/secure/ 标志位必须符合第三章中的规定,如果不符合,客户端必须马上_Fail the WebSocket Connection_,然后退出这个流程。
      如果客户端与服务器(ip为1.1.1.1)正在建立连接,即使服务器使用了其他的服务器名字(/host/),客户端必须等待这个连接建立完成或者关闭连接,保证只有一个连接处于CONNECTING状态。如果客户端有多个连接同时连接一个服务器(同一个IP),那么客户端必须串行化这些连接操作,保证同一时刻只有一个连接执行如下步骤。

    2. 如果客户端没办法判断服务器的ip应该假设每个主机域名都对应一个ip地址,并且客户端应该限制处于connecting状态的连接数量(比如客户端允许分别与a.example.com、b.example.com服务器建立连接)。浏览器通过限制用户同时打开的tab数量来防止客户端发送大量连接建立请求,以便防止DDOS攻击。服务器遭受DDOS攻击时会首先暂停接收新的连接建立请求,然后慢慢关闭部分已创建的连接,这样可以防止因为关闭连接过多而导致的客户端大量重连操作。

      注意:客户端和服务器之间可以建立的websocket连接(connected)数量没有限制,如果一个客户端建立了太多连接或者连接占用太多服务器资源,服务器可以主动关闭这些连接。

    3. Proxy Usage:如果客户端使用了代理,那么客户端与代理建立连接后应该通知代理与/host/和/port/指定的服务器建立tcp连接。

      假如客户端使用了代理来处理http请求,在与example.com:80端口通讯时,可以向代理发送如下指令:

         CONNECT example.com:80 HTTP/1.1
         Host:example.com
      
         如果有密码字段,则可以发送如下执行:
      
         CONNECTexample.com:80 HTTP/1.1
         Host:example.com
      
         Proxy-authorization: Basic ZWRuYW1vZGU6bm9jYXBlcyE=
      
      

      如果客户端没有设置代理,那么客户端可以直接与指定的 /host/ 和/port/建立连接。

      注意:如果没有UI界面来为Websocket连接选择代理,那么建议使用SOCKs5来代理Websocket连接,如果不能选择使用SOCKs5代理,建议使用HTTPs代理而不是HTTP代理。

    4. 如果不能打开连接,不管是由于直连或者通过代理连接,客户端都应该关闭相应的连接并且不再尝试建立连接。

    5. 如果设置了/secure/标识符,客户端必须在连接上发送TLS握手请求[RFC2818],然后再发送websocket握手信息。如果TLS握手失败(服务器证书不合格),客户端应该关闭连接。如果成功的话,之后所有的数据都应该在安全的TLS通道上发送。

      客户端在TLS握手过程中必须使用Server Name Indication extension[RFC6066]相关扩展。

    连接建立成功之后,客户端向服务器发送websocket握手消息。握手消息中包括一个HTTP Upgrade以及一系列必须的或者可选的头部字段。具体的要求信息如下:

    1. 握手消息必须是一个合法的HTTP消息(rfc2616)。
    2. 请求方法必须为GET,并且http版本必须为1.1以上(包括1.1)。如果websocket的uri为ws://example.com/chat,那么请求行为GET /chat HTTP/1.1。
    3. 请求地址(URI)(Request-URI)必须满足[Section 3]中定义的规则(相对地址)或者绝对地址(http/https),通过解析后有/resource name/、 /host/和 /port/ ,并且满足相应的ws/wss URI。
    4. 请求必须包括|Host|字段并且字段的值为/host/加上相应的/port/(不使用默认端口的情况下)。
    5. 必须包括|Upgrade|字段并且值必须包含websocket关键字。
    6. 必须包括|Connection|字段,并且值为Upgrade。
    7. 必须包括|Sec-WebSocket-Key|字段,值为随机选择的没有任何意义的16字节数据,并且经过base64编码。每个连接的编码都必须不一样。
      举个例子:如果选择的值为0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f 0x10,那么经过base64编码之后AQIDBAUGBwgJCgsMDQ4PEC==。
    8. 如果请求是从浏览器发送的,那么必须包括|Origin|字段。如果是从非浏览器发送的请求并且符合当前描述的使用场景,也可以发送这个字段。这个字段的值为运行前端应用的主机名字并且是经过ascii编码的。[RFC6454]通过了解更多的赋值规则。
      举个例子,前端应用从www.example.com地址下载的,应用与ww2.example.com服务器建立连接时,那么这个值为http://www.example.com。
    9. 必须包括|Sec-WebSocket-Version|字段,并且值为13。
      注意:尽管这个协议已经有多个版本的草稿(-09、-10、-11和-12),但是他们仍然不会被当做Sec-WebSocket-Version的值使用。这些值注册在IANA中,但是不会被使用。
    10. 请求可能包括|Sec-WebSocket-Protocol|字段。如果有这个字段,字段值为客户端希望使用的子协议列表,子协议按照期望程度进行排序。子协议的名字必须是唯一的并且由U+0021到U+007E之前的字符组成,不包括分隔符。这个字段值的命名规范使用ABNF中的1#token,这个规则在[RFC2616]中有描述。
    11. 请求可能包含|Sec-WebSocket-Extensions|字段。这个字段表示客户端希望使用的协议扩展。具体的协议扩展格式在[Section 9.1]中有描述。
    12. 可能还包括其他字段,比如cookies[RFC6265]、授权相关的|Authorization|[RFC2616],这些字段的解析规则都要参考定义他们的文档。

    一旦客户端发送了握手请求,客户端必须等待服务器的响应,在获取响应前不应该发送任何数据。客户端也应该按照如下规则检验服务器的响应信息。

    1. 如果状态码不是101,那么客户端应该按照[RFC2616]中规定的过程处理响应的数据。如果收到401状态码,客户端需要进行认证过程。服务器可能通过3xx状态码要求客户端进行重定向(客户端可以不进行重定向操作)。其他情况按照如下规则处理。
    2. 如果响应头部没有|Upgrade|字段或者字段的值在大小写不敏感的情况下与websocket不匹配,那么客户端可以直接_Fail the WebSocket Connection_。
    3. 如果响应头部没有|Connection|字段,并且字段值没有包含大小写不敏感的“Upgrade”,那么客户端应该_Fail the WebSocket Connection_。
    4. 如果响应头部没有|Sec-WebSocket-Accept|字段或者|Sec-WebSocket-Accept|包含的值不是|Sec-WebSocket-Key|(没有经过base64加密的)与258EAFA5-E914-47DA-95CA-C5AB0DC85B11连接并且经过base64-encoded SHA-1的值(出去前后的空白字符),客户端必须_Fail the WebSocket Connection_。
    5. 如果包括|Sec-WebSocket-Extensions|字段,并且值并不是客户端提供的,客户端必须_Fail the WebSocket Connection_。
    6. 如果包括|Sec-WebSocket-Protocol|字段,并且值并不是客户端提供的,客户端必须_Fail the WebSocket Connection_。


    如果服务器响应不符合[Section 4.2.2]规定的要求,客户端必须_Fail the WebSocket Connection_。

    根据[RFC2616]中的规定,http中的所有请求字段和响应字段都是大小写不敏感的。

    如果服务器响应通过上述验证规则,那么websocket连接就进入打开状态。当前使用的协议扩展是服务器响应头部返回的|Sec-WebSocket-Extensions|字段的值,如果响应中没有指定协议扩展,那么当前连接就没有使用协议扩展。正在使用的子协议也是使用服务器返回的|Sec-WebSocket-Protocol|字段中的值,如果响应中没有指定子协议,那么当前连接就没有使用子协议。除此之外,如果在握手阶段服务器要求设置cookies[RFC6265],那么cookies就是在握手阶段设置的cookies。

    4.2.服务端要求

    服务器可能使用网络代理管理连接(比如负载均衡服务器或者反向代理)。在这种场景中,协议指定的服务器包括了服务端的所有基础设施,从接受tcp连接的设施到处理客户端请求的设施。

    4.2.1.读取客户端的握手请求

    客户端的握手请求包含如下几部分。如果服务器在读取客户端的握手请求时发现客户端没有发送协议指定的字段,并且字段的名字或者值不符合ABNF规定的,服务器可以直接不处理这个握手请求,并且返回错误码(400 Bad Request)。

    1. HTTP协议版本为1.1或者更高,并且为Get请求,包括一个Request-URI[RFC2616]地址,这个URI应该被解析成/resource name/。如果是绝对地址,这个URI至少应该包括/resource name/。
    2. |Host|头部字段。
    3. |Upgrade|字段值为websocket并且大小写不敏感。
    4. |Connection|字段值为Upgrade并且大小写不敏感。
    5. |Sec-WebSocket-Key|字段,值为[Section 4 of RFC4648]中规定的格式,经过base64反解码可以得到16个字节的数据。
    6. |Sec-WebSocket-Version|值为13.
    7. |Origin|字段,所有的浏览器都应该有这个字段,如果请求没有这个字段,则发送请求的客户端不应该被当做浏览器对待。
    8. 可选的 |Sec-WebSocket-Protocol|字段列出了客户端期望使用的协议,根据期望程度进行排序。
    9. 可选的|Sec-WebSocket-Extensions|字段列出了客户端期望使用的协议扩展。这个字段的解析在[Section 9.1]中进行了描述。
    10. 其他可选的字段比如认证或者cookies。其他没有在[RFC2616]里面描述的字段都忽略。

    4.2.2.发送服务器的握手响应

    当客户端与服务器建立websocket连接时,服务器必须执行如下流程来接收这个连接,并且返回响应。

    1. 如果连接的端口是443端口,那么服务器必须执行一个TLS握手过程。如果TLS握手失败(客户端在扩展字段中指定的host地址和服务器的地址不一致),服务器关闭这个连接。如果成功,所有后续的通讯都要在这个加密通道中传输[RFC5246]。

    2. 服务器可以执行一些认证,比如,返回401状态码同时带有|WWW-Authenticate|字段,这个字段在[RFC2616]中有描述。

    3. 服务器可能通过3xx[RFC2616]状态码要求客户端跳转到指定位置。这一步可能在认证之前也可能之后执行。

    4. 证实如下信息:

      /origin/表示程序从哪里下载的。origin的值为ASCII字符并且是小写的。服务器可能使用这个值来判断是否接收这个连接。如果服务器不检查这个字段的值,那么服务器会接收来自任何客户端的连接请求。如果服务器不打算接收这个连接可以返回一个错误码(比如403拒绝)。更多相关信息可以查看[Section 10]。

      |Sec-WebSocket-Key| 这个头部字段是客户端在握手过程中发送的,经过base64加密的,如果解密的话会获得16个字节的数据。服务器在生成握手响应的时候会使用这个数据来表示接收了客户端的握手请求。

      |Sec-WebSocket-Version|表示客户端希望使用的websocket版本号。如果服务器没有客户端请求使用的协议版本号,那么服务器应该返回一个错误码(比如426)并且使用|Sec-WebSocket-Version|表示服务器可以使用的协议版本号。

      /resource name/表示服务器提供的某种服务。如果服务器提供多种服务,那么应该从客户端的握手信息(Request-URI[RFC2616])中提取到具体的值来确定使用哪些服务。如果服务器没有对应的服务,那么应该返回一个HTTP错误码(比如404 Not Found)。

      /subprotocol/表示服务器打算使用的一个子协议。这个子协议的值必须是从客户端握手请求中提供的子协议列表中选择的,在|Sec-WebSocket-Protocol|字段中指定。如果客户端没有使用这个字段或者服务器没有选择子协议的话,那么这个连接不使用任何子协议。没有这个字段与有这个字段但是值为空(null)的效果是一样的,如果服务器不选择任何一个子协议的话,握手响应中不能有这个字段(|Sec-WebSocket-Protocol|)。空字符串与null的值是不一样的,空字符串不是一个合法的取值。值的格式准寻ABNF规则,在[RFC2616]中有描述相关规则。

      /extensions/列出了服务器打算使用的协议扩展(有可能也没有列出),这些扩展值必须从客户端握手请求中的|Sec-WebSocket-Extensions|字段值中选择出来的。没有|Sec-WebSocket-Extensions|这个字段与空值(null)是一样的效果。空字符串不是合法的值。具体的选择规则以及解析规则在[Section 9.1]中有描述。

    5. 如果服务器打算接收客户端发送的连接请求,必须按照如下流程做出响应。

      1. HTTP/1.1 101 Switching Protocols。
      2. |Upgrade|:websocket。
      3. |Connection|:"Upgrade"。
      4. |Sec-WebSocket-Accept|字段,取值为上面第四步里描述的/key/值与258EAFA5-E914-47DA-95CA-C5AB0DC85B11串联起来,进行sha-1运算最终获得20个字节,再最后进行base64编码。
         ABNF定义的值为:
            Sec-WebSocket-Accept    = base64-value-non-empty
            base64-value-non-empty 	= (1*base64-data [ base64-padding ]) |base64-padding
            base64-data      		= 4base64-character
            base64-padding   		= (2base64-character "==") |(3base64-character "=")
            base64-character 		= ALPHA | DIGIT | "+" | "/"
      比如:客户端在|Sec-WebSocket-Key|字段中提供了“dGhlIHNhbXBsZSBub25jZQ==”这个值,服务器把这个值与“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”进行拼接获得“dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11”之后进行SHA-1运算获得
      0xb3 0x7a 0x4f 0x2c 0xc0 0x62 0x4f 0x16 0x90 0xf6 0x46 0x06 0xcf 0x38 0x59 0x45 0xb2 0xbe 0xc4 0xea,
      最后进行base64编码获得“s3pPLMBiTxaQ9kYGzzhZRbK+xOo=”,把这个值作为|Sec-WebSocket-Accept|字段的值返回。
      |Sec-WebSocket-Protocol|值为上面中定义的/subprotocol/。
      5.可选的|Sec-WebSocket-Protocol|字段取值为上面第四步中定义的/subprotocol/。
      6.可选的|Sec-WebSocket-Extensions|字段取值为上面第四步中定义的/extensions/,如果有多个扩展,可以再这个字段中列出来,也可以使用多个|Sec-WebSocket-Extensions|字段。
      
      

    这样就算完成了握手过程。如果服务器没有关闭连接,那么websocket连接便进入OPEN状态,之后的数据就可以在这个连接上进行传递。

    4.3.在握手阶段使用的并且符合ABNF规则的新增字段

    本部分使用了在 Section 2.1 of[RFC2616]描述的ABNF语法规则,同时也包括隐式的 *LWS规则。 如下描述的ABNF规则在本段落使用。规则描述了对应字段值的格式。比如Sec-WebSocket-Key描述了|Sec-WebSocket-Key|值的规则。带有-Client后缀的规则描述了客户端请求中的字段值的规则。带有-Server后缀的规则描述了服务器响应中的字段值的规则。比如,Sec-WebSocket-Protocol-Client描述了客户端发送的|Sec-WebSocket-Protocol|字段值的规则。如下字段是客户端发送到服务器的字段值的规则:

          Sec-WebSocket-Key = base64-value-non-empty
          Sec-WebSocket-Extensions = extension-list
          Sec-WebSocket-Protocol-Client = 1#token
          Sec-WebSocket-Version-Client = version
    
          base64-value-non-empty = (1*base64-data [ base64-padding ]) |
                                    base64-padding
          base64-data      = 4base64-character
          base64-padding   = (2base64-character "==") |
                             (3base64-character "=")
          base64-character = ALPHA | DIGIT | "+" | "/"
          extension-list = 1#extension
          extension = extension-token *( ";" extension-param )
          extension-token = registered-token
          registered-token = token
                extension-param = token [ "=" (token | quoted-string) ]
               ; When using the quoted-string syntax variant, the value
               ; after quoted-string unescaping MUST conform to the
               ; 'token' ABNF.
          NZDIGIT       =  "1" | "2" | "3" | "4" | "5" | "6" |
                           "7" | "8" | "9"
          version = DIGIT | (NZDIGIT DIGIT) |
                    ("1" DIGIT DIGIT) | ("2" DIGIT DIGIT)
                    ; Limited to 0-255 range, with no leading zeros
    

    如下字段描述了服务器返回的字段的值的格式:

          Sec-WebSocket-Extensions = extension-list
          Sec-WebSocket-Accept     = base64-value-non-empty
          Sec-WebSocket-Protocol-Server = token
          Sec-WebSocket-Version-Server = 1#version
    

    4.4.支持多版本的websocket协议

    本部分给出了实现客户端和服务器之间支持多个版本的指导意见。

    客户端通过|Sec-WebSocket-Version|字段声明它希望使用的协议版本。如果服务器支持客户端提供的版本并且客户端提供的其他字段也是合法的,服务器会接收这个版本。如果服务器不支持客户端提供的协议版本,服务器必须返回一个|Sec-WebSocket-Version|(或者多个字段)字段来声明它所支持的协议。如果客户端支持其中一个协议,可以继续使用上述过程把支持的协议版本发送到服务器。

    以下示例描述了协议版本的协商过程:

          GET /chat HTTP/1.1
          Host: server.example.com
          Upgrade: websocket
          Connection: Upgrade
          ...
          Sec-WebSocket-Version: 25
    

    服务器的响应信息如下:

          HTTP/1.1 400 Bad Request
          ...
          Sec-WebSocket-Version: 13, 8, 7
    

    服务器也可能返回如下:

          HTTP/1.1 400 Bad Request
          ...
          Sec-WebSocket-Version: 13
          Sec-WebSocket-Version: 8, 7
    

    客户端再次发送握手请求:

          GET /chat HTTP/1.1
          Host: server.example.com
          Upgrade: websocket
          Connection: Upgrade
          ...
          Sec-WebSocket-Version: 13
    

    5.数据分帧

    5.1.总览

    在websocket协议中,数据是通过一系列帧进行传递的。为了防止网络攻击(Websocekt为网络安全带来哪些挑战?),客户端在发送数据时必须对数据帧进行掩码(数据帧进行掩码与是否运行在TLS安全层上无关)。服务器如果收到没有掩码的数据帧,需要立即关闭这个连接。在这种情况下,服务器可能发送一个关闭帧,然后状态码为1002(Section 7.4.1)。服务器不用对数据帧进行掩码。客户端如果收到掩码的数据,必须关闭这个连接。在这种情况下客户端可能需要返回1002(Section 7.4.1)状态码。这些规则限制可能在协议的未来版本中变得宽松。

    帧协议定义了表示类型的opcode字段、数据长度(payload length)字段、表示扩展数据(designated locations for “Extension data”)和应用数据(Application data)位置的字段,扩展数据和应用数据共同组成了数据部分(Payload data)。其他数据位和opcode值预留给未来扩展使用。客户端和服务器建立连接后如果没有关闭连接的话,可以双向发送数据帧。

    5.2.协议的基本分帧功能

    数据传输的格式通过ABNF规则描述[RFC5234]。注意,不像其他章节的ABNF规范一样,本章的ABNF主要是用来描述位组,位组的长度在注释中标注了。当编码时,最高位为最左边的数据位。数据格式的总体描述如下图。如果同一个规则在下图和下面使用的ABNF规则里都描述了,以图为准。

          0                   1                   2                   3
          0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
         +-+-+-+-+-------+-+-------------+-------------------------------+
         |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
         |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
         |N|V|V|V|       |S|             |   (if payload len==126/127)   |
         | |1|2|3|       |K|             |                               |
         +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
         |     Extended payload length continued, if payload len == 127  |
         + - - - - - - - - - - - - - - - +-------------------------------+
         |                               |Masking-key, if MASK set to 1  |
         +-------------------------------+-------------------------------+
         | Masking-key (continued)       |          Payload Data         |
         +-------------------------------- - - - - - - - - - - - - - - - +
         :                     Payload Data continued ...                :
         + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
         |                     Payload Data continued ...                |
         +---------------------------------------------------------------+
    
    • FIN: 1 bit

      表示是否为最后一帧。第一个数据帧可能同时也是最后一个帧。

    • RSV1, RSV2, RSV3: 每个1位

      如果协商过程中没有确定意义的话,每个值都为0。如果收到一个非0的设置,但是在协商过程中又没有规定具体的意义,接收端应该关闭这个连接。

    • Opcode 四位<

      定义Payload data的类型,如果收到一个无法理解的值,接收方应该立即关闭这个连接。具体的定义如下:

          *  %x0 表示后续还有数据帧
          *  %x1 表示文本帧
          *  %x2 表示二进制帧
          *  %x3-7 预留
          *  %x8 表示连接关闭
          *  %x9 ping
          *  %xA pong
          *  %xB-F 预留
    
    • Mask: 1 bit

      标记Payload data是否经过掩码。如果设置为1,masking-key字段的值用来编码,也可以用来解码。所有从客户端发送到服务器的数据都必须进行掩码(mask标记必须为1)。

    • Payload length: 7 bits, 7+16 bits, or 7+64 bits

    Payload data长度,以字节为单位,如果是0-125表示Payload data的字节数。如果是126,后续的2个字节为无符号数,表示Payload data的长度。如果是127,后面的8个字节表示为无符号数,表示Payload data的长度。多字节数据按照网络字节序(大端)处理。注意:在所有场景中,最少的字节数表示Payload data的长度,比如124个字节长度的字符串不能表示为126, 0, 124。payload length表示Extension data数据的长度和Application data的长度总和。Extension data的长度有可能为0,这时候payload length的长度为Application data(应用数据)的长度。

    • Masking-key: 0 or 4 bytes

      所有从客户端发送到服务器的数据帧都需要与一个32位长的key进行掩码。这个32位长的key随数据帧一起发送。如果MASK设置为1,key有值。详细的编码过程参照Section 5.3

    • Payload data: (x+y) bytes

    Payload data是由Extension data" 与 "Application data"组成。

    • Extension data: x bytes

    如果在协商阶段没有规定扩展数据的话,这个长度为0。如果协商阶段说明了扩展数据,扩展数据的长度必须标明,如果没有标明长度,也要在协商的过程中说明如何计算扩展数据长度并且也同时要说明如何使用这些扩展数据。

    • Application data: y bytes

    应用数据紧随扩展数据之后,应用数据的长度为Payload length减去扩展数据的长度。

    基本的分帧操作由下面的ABNF进行描述。注意,这里的数据都是二进制数据,不是ASCII字符。比如 %x0 / %x1代表一位数据,值为0或者1,不是一个ASCII字符中的0或者1(占用一个字节)。占四位的字段%x0-F,表示四个位,并不是四个ASCII字符。在ABNF中,一个字符只是表示一个非负的整数。在某些场景中会指定某些编码值与某些字符集(ASCII)进行映射。每个字段的值都用固定位数的二进制值表示,不同字段的值的长度可能不一样。

     ws-frame                   = frame-fin           ; 1 bit in length
                                  frame-rsv1          ; 1 bit in length
                                  frame-rsv2          ; 1 bit in length
                                  frame-rsv3          ; 1 bit in length
                                  frame-opcode        ; 4 bits in length
                                  frame-masked        ; 1 bit in length
                                  frame-payload-length   ; either 7, 7+16,
                                                         ; or 7+64 bits in
                                                         ; length
                                  [ frame-masking-key ]  ; 32 bits in length
                                  frame-payload-data     ; n*8 bits in
                                                         ; length, where
                                                         ; n >= 0
    
        frame-fin               = %x0 ; more frames of this message follow
                                / %x1 ; final frame of this message
                                      ; 1 bit in length
    
        frame-rsv1              = %x0 / %x1
                                  ; 1 bit in length, MUST be 0 unless
                                  ; negotiated otherwise
    
        frame-rsv2              = %x0 / %x1
                                  ; 1 bit in length, MUST be 0 unless
                                  ; negotiated otherwise
    
        frame-rsv3              = %x0 / %x1
                                  ; 1 bit in length, MUST be 0 unless
                                  ; negotiated otherwise
    
        frame-opcode            = frame-opcode-non-control /
                                  frame-opcode-control /
                                  frame-opcode-cont
    
        frame-opcode-cont       = %x0 ; frame continuation
    
        frame-opcode-non-control= %x1 ; text frame
                                / %x2 ; binary frame
                                / %x3-7
                                ; 4 bits in length,
                                ; reserved for further non-control frames
    
        frame-opcode-control    = %x8 ; connection close
                                / %x9 ; ping
                                / %xA ; pong
                                / %xB-F ; reserved for further control
                                        ; frames
                                        ; 4 bits in length
                                        
            frame-masked            = %x0
                                ; frame is not masked, no frame-masking-key
                                / %x1
                                ; frame is masked, frame-masking-key present
                                ; 1 bit in length
    
        frame-payload-length    = ( %x00-7D )
                                / ( %x7E frame-payload-length-16 )
                                / ( %x7F frame-payload-length-63 )
                                ; 7, 7+16, or 7+64 bits in length,
                                ; respectively
    
        frame-payload-length-16 = %x0000-FFFF ; 16 bits in length
    
        frame-payload-length-63 = %x0000000000000000-7FFFFFFFFFFFFFFF
                                ; 64 bits in length
    
        frame-masking-key       = 4( %x00-FF )
                                  ; present only if frame-masked is 1
                                  ; 32 bits in length
    
        frame-payload-data      = (frame-masked-extension-data
                                   frame-masked-application-data)
                                ; when frame-masked is 1
                                  / (frame-unmasked-extension-data
                                    frame-unmasked-application-data)
                                ; when frame-masked is 0
    
        frame-masked-extension-data     = *( %x00-FF )
                                ; reserved for future extensibility
                                ; n*8 bits in length, where n >= 0
    
        frame-masked-application-data   = *( %x00-FF )
                                ; n*8 bits in length, where n >= 0
    
        frame-unmasked-extension-data   = *( %x00-FF )
                                ; reserved for future extensibility
                                ; n*8 bits in length, where n >= 0
    
        frame-unmasked-application-data = *( %x00-FF )
                                ; n*8 bits in length, where n >= 0
    

    5.3.客户端的掩码操作

    经过掩码的数据帧中的frame-masked标志必须置为1。掩码key会放在frame-masking-key字段随着数据帧一起发送。

    Key的值是客户端随机选择的32位的数据。当进行掩码时,客户端需要获取一个新的32位长度的值。这个key必须是不确定的,并且不能让服务器或者代理通过当前的key来猜测到下一个key的值。Key的不确定性可以防止恶意用户构造恶意请求。RFC 4086描述了对于安全比较敏感的应用如何产生比较安全的掩码key。

    掩码操作不影响Payload data数据的长度。掩码或者解码获取原数据都可以遵循如下规则。

    i表示数据帧的第i个字节,4表示4个字节的mask key。

       j                   = i MOD 4
       transformed-octet-i = original-octet-i XOR masking-key-octet-j
    
    

    数据帧中frame-payload-length字段表明的数据长度不包括masking key。

    5.4.分帧

    分帧的目的就是发送未知长度的消息。如果不能分帧,发送端需要缓存整个消息。如果有了分帧,发送端或者中间设备可以随意设置缓存,在缓存满的时候,把数据包装成一个数据帧发送出去。

    分帧的第二个作用就是多路复用,如果一个大消息发送时没有分帧的话,会一直占用逻辑通道,这样会影响其他使用通道的应用。

    除非设置了扩展标志,否则数据帧没有其他的特殊处理逻辑。客户端和服务器之间如果没有协商某些扩展信息的话,中间设备可以随意组合和拆分数据帧,如果客户端和服务器之间进行了扩展信息的协商,而且中间设备也了解这些扩展信息,中间设备也可以自由的组合与拆分这些数据帧。

    分帧的规则如下:

    • 未分帧的消息只包含一个数据帧,数据帧的FIN标志会被设置并且opcode值不为0.

    • 如果消息经过了分帧,第一个分帧的FIN被设置为0,opcode不为0,后续跟着0或者多个数据帧,这些数据帧的FIN为0,并且opcode为0,最后的数据帧FIN标记设置为1,opcode也为0。分帧的数据帧概念上是一个整体的消息,消息的长度就是这些数据帧长度的总和,如果存在扩展数据的话,这个规则可能不会成立,因为扩展数据的位置与解析由扩展信息决定。比如,有的扩展数据可能只在第一个分帧数据帧中,也有可能存在后面的具有扩展标志的数据帧中。先不考虑扩展数据,数据帧的具体操作流程如下:

      比如,对于一个文本数据,第一个数据帧的opcode设置为1,FIN标志位0,第二个数据帧的opcode为0,FIN为0,第三个数据帧的opcode为0,FIN为1。

    • 控制帧可以放在分帧的消息中间。控制帧不能继续分帧。

    • 数据段接收方收到的顺序和发送方发送的顺序必须一致。

    • 两个消息的分帧不能交叉,除非在握手协商过程中说明了如何解析这种交叉分帧。

    • 终端可以处理位于中间的控制帧。

    • 发送方可以发送任意大小的数据分帧。

    • 客户端和服务器必须可以处理经过分帧的和未经过分帧的消息。

    • 因为控制帧不能被分帧,所以中间设备必须不能对控制帧进行分帧。

    • 中间设备如果不能理解预留字段的具体含义不能对数据帧进行进一步操作。

    • 在一个连接中,如果消息的扩展信息在握手协商中确定了具体的意义,但是中间设备不了解这些扩展信息的具体意义,所以不能改变这些消息的分帧信息。同样的,如果中间设备不了解websocekt的握手过程,同样不能更改消息的分帧操作。

    • 根据上述规则,一个消息的所有分帧类型都必须一样,分帧类型只在第一个分帧进行设置。因为控制帧不能分帧,所以经过分帧的消息类型都是文本、二进制或者其他的自定义类型。

    注意:如果控制帧不能插入数据段中间,那么在一个非常大的消息后面发送一个ping消息会有很大的延迟。

    协议实现注意:如果没有特殊规定的话,接收方不必等到数据接收完成再处理,比如在流处理应用中,可以把部分数据交由应用处理。这种要求可能随着不同的websocket版本的不同而不同。

    5.5.控制帧

    opcode字段的最高位为1表示控制帧。当前定义的控制帧包括:0x8 (Close), 0x9 (Ping), and 0xA (Pong)。值在0xB-0xF中间的表示预留值。

    控制帧用来交流websocket连接状态。控制帧可以放在数据帧中间。

    所有的控制帧payload的长度必须为125字节或者比125少,并且都不能分帧。

    5.5.1.Close控制帧

    opcode标志为0x8。

    Close帧可能包含body(帧的数据部分),这个body可能描述了关闭连接的原因,比如服务器宕机、收到一个很大的数据帧、或者收到一个不能识别的数据格式的帧。如果有数据部分,那么前两个字节(网络字节序)为无符号的整数,这个整数代表状态码(Section 7.4中有描述)。随后跟着UTF-8编码的文本。这个文本不一定必须是可以直接阅读,也有可能是与调试有关的数据。因为这个数据不一定方便人类阅读,所以客户端不能把这个数据直接展示给终端用户。

    从客户端发送到服务器的Close帧必须按照Section 5.3规则进行掩码。

    应用在发送Close帧之后不能再发送任何应用数据。

    如果一个终端收到一个close帧,但是之前没有发送过close帧,这个终端应该发送一个close帧(在响应close帧时,终端应该把收到的close帧中的状态值放在自己发送的响应的close帧中)。这个响应的发送时机视具体情况而定。一个终端可能在发送完自己的数据后才会发送close帧。

    发送完close帧之后并且也收到对端发送的close帧,终端可以关闭底层的TCP连接。服务器必须马上关闭TCP连接,客户端可以等待服务器关闭TCP连接,也可以在任何时候关闭TCP连接。

    如果客户端和服务器同时发送了Close帧,两端都发送并且收到close帧可以认为WebSocket关闭了,并且关闭底层TCP连接。

    5.5.2.Ping控制帧

    Ping帧的opcode为0x9。

    可能包含Application data。

    收到Ping帧后,需要立马发送一个Pong帧作为响应,如果已经收到Close帧的话,就不用发送Pong帧。发送Pong的时机视具体情况而定。Pong在下一节描述。

    终端可以在连接建立后到关闭前的任何时刻发送Ping帧。

    注意:Ping可以用来判断连接是否存活。

    5.5.3.Pong控制帧

    Ping帧的opcode为0xA。

    对于Ping控制帧的要求同样适用于Pong控制帧。

    Pong帧中的应用数据应该与Ping中的应用数据一样。

    如果终端还没来得及响应之前的Ping帧,又收到一个Ping帧,它可以选择响应最近的一个Ping帧。

    一个Pong帧可能在没有接收到Ping帧的情况下发送,这种一般出现在单向心跳的情况下。Pong帧不需要响应。

    5.6. 数据帧

    opcode字段的最高位为0表示数据帧。目前表示数据帧的为 0x1 (Text), 0x2 (Binary)。opcodes为0x3-0x7为预留值,表示非控制帧。

    数据帧包含应用数据或者扩展数据。opcode的值表示数据的解释方式。

    Text

    数据被编码成UTF-8字符。一个数据帧可能包含一个字符的部分UTF-8编码序列,但是整个消息的编码必须正确。包含非法的UTF-8编码序列的消息处理方法在8.1小节描述。

    Binary

    应用数据可以是任意的二进制数据,这些数据的具体含义由应用层负责解释。

    5.7. 例子

    • 包含一个没有掩码的数据帧的消息,

      1. 0x81 0x05 0x48 0x65 0x6c 0x6c 0x6f (contains “Hello”)
    • 包含一个掩码的数据帧的消息,

      1. 0x81 0x85 0x37 0xfa 0x21 0x3d 0x7f 0x9f 0x4d 0x51 0x58(contains “Hello”)
    • 分段的未掩码的文本消息,

      1. 0x01 0x03 0x48 0x65 0x6c (contains “Hel”),
      2. 0x80 0x02 0x6c 0x6f (contains “lo”)
    • 未掩码的Ping请求和掩码的Ping响应,

      1. 0x89 0x05 0x48 0x65 0x6c 0x6c 0x6f (contains a body of “Hello”,but the contents of the body are arbitrary),
      2. 0x8a 0x85 0x37 0xfa 0x21 0x3d 0x7f 0x9f 0x4d 0x51 0x58 (contains a body of “Hello”, matching the body of the ping)
    • 一个包含256字节的二进制的消息未掩码的数据帧。

      1. 0x82 0x7E 0x0100 [256 bytes of binary data]
    • 一个包含64k字节的二进制的消息未掩码的数据帧。

      1. 0x82 0x7F 0x0000000000010000 [65536 bytes of binary data]

    5.8. 扩展

    协议支持扩展以实现对基础功能的增强。客户端和服务器在握手阶段必须协商扩展数据的具体含义。协议中的opcode的值在 0x3到0x7,0xB 到 0xF表示扩展,同时还提供了"Extension data" 字段、以及frame-rsv1,frame-rsv2,frame-rsv3这三个扩展位。扩展的协商过程在9.1章节进行讨论。

    下面是一个可能的扩展用法,这些用法不是完备的并且也不是规范的。

    • 在Payload data中,Extension data可能在Application data之前。
    • 每个帧的预留位可以独立定义。
    • 预留操作码的值可以定义。
    • 如果需要更多的操作码值,可以使用预留位(Reserved bits)进行补充。
    • 可以使用Payload data对操作码或者预留位进行扩展。

    6. 发送和接收数据

    6.1. 发送数据

    在websocket连接上发送消息必须执行如下流程:

    1. 发送端必须确保webcosket连接处于打开状态。如果任何时刻连接状态发生变化,发送端可以不执行如下流程。
    2. 发送端必须把数据包装到websocket帧中(帧格式在5.2章节中有介绍)。如果发送的数据很多或者在发送时不知道数据大小,发送端可以按照5.4章节介绍的过程对数据进行分帧。
    3. 第一个帧必须设置text或者binary类型,这样接收方就可以根据类型来解析数据。
    4. 最后一个发送的帧必须设置Fin为1.
    5. 如果是客户端发送的数据,每个帧必须设置Mask为1.
    6. 如果建立连接时协商了扩展点,应该对每个扩展点都进行详细的考虑。
    7. 生成的帧必须通过网络连接(tcp)进行发送。

    6.2. 接收数据

    接收端接收到的字节必须解析成5.2章节定义的数据格式。必须按照5.5章节定义的方式处理控制帧。对于数据帧,接收方必须解析数据帧的数据类型(5.2)。如果收到一个没有分帧的数据,那么就可以确认收到一个类型为/type/和数据为/data/的消息。如果收到一个分帧的数据,那么应用数据就是所有后续分帧的/data/组合。当最后一个Fin设置为1的数据帧到达后,那么一个完整的应用数据就接收完毕了,后续的帧就属于新的消息。

    扩展设置可以改变数据解析的方式,可能包括消息边界。扩展设置除了可以在应用数据前添加扩展数据外,还有可能会对应用数据进行压缩。

    正如5.3章节中描述的一样,服务器收到客户端发送的数据帧时,必须解码。

    7.关闭连接

    7.1.定义

    7.1.1.关闭websocket连接

    可以通过关闭底层TCP连接来关闭websocket连接。关闭TCP连接要干脆利落,同时也要关闭相关的TLS会话,如果还有未来得及处理的数据,也需要丢弃这些数据(可能在接收缓存中)。当受到网络攻击时,主机可以通过任何可以使用的方法来关闭连接。

    在大部分场景下,底层的TCP连接都是由服务器关闭,这样服务器会保持TIME_WAIT状态一段时间(如果是客户端首先关闭连接,客户端需要保持2MSL时间才能重新打开连接),但是这样对服务器没有什么影响,只要SYN带有更大的seq,服务器可以重新打开这个连接。在非正常情况下(在一段时间后客户端没有服务器发送的Close请求),客户端可以首先发起关闭TCP连接请求。同样地,当服务器被要求关闭websocket连接的时候,服务器应该立马发起连接关闭请求,当客户端被要求关闭连接时,应该等待服务器发送的关闭连接请求。

    当使用C编程语言时,在关闭伯克利socket时,我们可以调用shutdown()方法,并且附带SHUT_WR这个参数,之后调用recv()方法,并且等待获取一个值为0的字节,来表示对端也调用了shutdown()方法,最后调用socket的close方法来关闭连接。

    7.1.2.发起Websocket关闭握手

    关闭websocket连接时需要在Close控制帧中指定一个code值和原因。当一端既发送了Close控制帧,也收到一个Close控制帧,可以按照7.1.1章节介绍的规则关闭TCP连接。

    7.1.3.Websocket关闭握手已经开始

    只要发送或者收到一个Close控制帧,标志着Websocket关闭握手已经开始,并且Websocket连接已经进入CLOSING状态。

    7.1.4.Websocket连接已经关闭

    当底层的TCP连接已经关闭,表明Webcosket连接已经关闭,并且进入了CLOSED状态。当TCP连接在Websocket关闭流程完成后关闭,可以说Webcosket连接被优雅地关闭了。

    当Webcosket连接不能建立,也可以说Webcosket连接被关闭了,只不过不是优雅地关闭(_ The WebSocket Connection is Closed_, but not _cleanly _)。

    7.1.5.Websocket连接关闭状态码

    正如5.5.1和7.4章节介绍一样,一个Close控制帧可能会包含一个状态码用来表明关闭连接的理由。Websocket关闭请求可以由任何一端发送,也有可能是同时发送。_The WebSocket Connection Close Code_定义为第一个Close控制帧中包含的并且在7.4章节中定义的状态码。当Close 控制帧没有状态码,_The WebSocket Connection Close Code_被认为是1005.当Websocket连接被关闭了,但是没有收到Close控制帧(底层TCP连接直接关闭),_The WebSocket Connection Close Code_被认为是1006.

    注意:连接的两端可能存在_The WebSocket Connection Close Code_数值不一致的情况。比如,远端发送了一个Close控制帧,但是本地没有读取TCP中的数据,也就是没有读取远端发送的Close控制帧信息,本地应用打算关闭连接并且发送了一个Close 控制帧,这样的话,两端都发送并且收到一个Close控制帧,并且后续不会再发送Close控制帧。两端都看到了对面发送的_The WebSocket Connection Close Code_。这样的话,两端可能看到不同的_The WebSocket Connection Close Code_,这种情况是在两端几乎同时开启关闭握手时出现。

    7.1.6.Websocket连接关闭原因

    在5.5.1和7.4章节讲到,一个Close控制帧可能包含一个状态码用来指示关闭的原因,同时也能包含一个UTF-8编码的数据,这个数据的具体解释方式由接收方的应用处理。_The WebSocket Connection Close Reason_定义为附加在状态码之后的UTF-8编码的数据,这个数据包含在第一个Close控制帧中。如果Close控制帧中没有包含这个数据,那么_The WebSocket Connection Close Reason_被认为是空字符串。

    注意:和7.1.5章节描述的一样,两端接收到的关闭原因可能不一样。

    7.1.7.Websocket连接失败

    某些算法或者规范要求终端可以_Fail the WebSocket Connection_。为了实现这个功能,客户端必须_Close the WebSocket Connection_并且向用户以合适的方式上报错误。同样的,服务器也应该_Close the WebSocket Connection_并且把错误日志打印出来。

    如果_The WebSocket Connection is Established_在_Fail the WebSocket Connection_之前执行,终端应该_Fail the WebSocket Connection_这个连接并且向对面发送一个带有状态码的Close控制帧,然后再执行_Close the WebSocket Connection_。如果一个终端已经了解到对面不会处理任何websocket消息了,那么这个终端有可能就不会再发送Close帧,因为Websocket在建立连接的时候有可能就没有建立成功。当终端被命令_Fail the WebSocket Connection_时,它不能继续处理任何从对端发送过来的数据(包括对端发送过来的Close帧)。

    除了上述情况或者应用主动关闭Websocket连接,其他情况下不应该关闭websocket连接。

    7.2. 异常关闭

    7.2.1. 客户端发起的关闭

    某些算法,尤其在建立连接的握手阶段,要求客户端可以_Fail the WebSocket Connection_。为了实现这个功能,客户端必须按照7.1.7章节描述的过程去执行相应的步骤。

    在任何时刻如果底层的TCP连接丢失了,客户端必须_Fail the WebSocket Connection_。

    除了上述情况或者应用主动关闭Websocket连接,其他情况下不应该关闭websocket连接。

    7.2.2. 服务端发起的关闭

    某些算法,尤其在建立连接的握手阶段,要求服务端可以_Fail the WebSocket Connection_。为了实现这个功能,客户端必须按照7.1.7章节描述的过程去执行相应的步骤。

    7.2.3. 从异常关闭中恢复

    很多情况都会导致连接异常关闭。常见的是底层链路的连接错误,这种情况下可以重新建立连接。还有其他非链路错误,客户端可能非正常关闭了连接,但是又立刻或者持续地进行重连,服务器可能会经历类似于拒绝式服务攻击,因为很多客户端会尝试进行连接。最后可能会导致服务器无法在短时间内进行恢复,或者恢复过程变得很困难。

    为了防止这种情况,客户端在遇到连接非正常关闭的情况下应该采用某种回退机制。

    在经过某个随机时间后再进行重连操作。具体的随机算法由客户端去决定,0到5秒可能是一个不错的选择,不过客户端依然可以根据经验或者具体情况去选择重连的回退时间。

    如果第一次重连失败,第二次重连时间应该适当增长,比如采用截断二进制指数退避算法(truncated binary exponential backoff)。

    7.3. 正常关闭

    服务器可以根据情况关闭webcosket连接。客户端不应该随意关闭连接。不管谁关闭连接,都应该遵守7.1.2描述的过程去_Start the WebSocket Closing Handshake_。

    7.4. 状态码

    终端在关闭已经建立的websocket连接时应该指定关闭的理由。当前规范没有定义接收到关闭理由时应该进行什么样的操作。Close帧可以选择是否记录状态码和相关文本。

    7.4.1. 预留状态码

    终端在发送Close帧时可以使用以下预留的状态码。

    • 1000

      1000表示正常关闭连接。

    • 1001

      1001表示终端已经“going away”,比如服务器宕机或者浏览器跳转到其他页面。

    • 1002

      1002表示终端由于协议错误终止了连接。

    • 1003

      1003表示终端因为接收到了不能处理的数据类型,所以打算关闭连接(比如终端只理解text数据,但是收到了binary类型的消息)。

    • 1004

      预留。

    • 1005

      1005是一个预留值,表示终端期望收到状态码但是没有收到,不能放在Close帧中。

    • 1006

      预留值,用来表示连接被异常关闭(没有发送或者收到Close帧),不能放在Close帧中。

    • 1007

      1007表示终端关闭了连接,因为发现收到的数据内容和实际的消息类型不匹配。(比如非UTF-8编码的数据放在了text消息中)

    • 1008

      1008表示收到一个不符合规则的消息,并打算关闭连接。这是一个比较通用的状态码,当没有更合适的状态码或者希望隐藏一些具体的细节的时候可以选择使用。

    • 1009

      1009表示收到一个比较大的不能处理的消息。

    • 1010

      用来关闭连接,因为在握手阶段,客户端希望服务器使用多个扩展,但是服务器没有返回相应的扩展信息。客户端在发送Close帧时把希望使用的扩展列表放在/reason/(关闭原因)中。服务器不需要发送这个状态码,因为服务器可以直接执行_ Fail the WebSocket Connection _。

    • 1011

      表示服务器遇到某些不能完成的请求。

    • 1015

      表示不能进行TLS握手的时候发送(比如服务器的证书不能得到验证),是一个预留状态码,不能在Close帧中使用。

    7.4.2.预留状态码范围

    • 0-999

      还没有被使用。

    • 1000-2999

      这个范围的状态码被设计为协议预留,在协议未来的扩展或者校订版本中会用到这个范围的状态码。

    • 3000-3999

      这个范围的状态码预留给类库、框架、应用使用(基础应用比如apache http这样的应用或者一些开源框架)。这些状态码在IANA中直接注册。本协议没有规定具体含义。

    • 4000-4999

      这个范围的状态码没有在IANA中注册可以随意使用,具体的意义由websocket应用程序规定。

    8.错误处理

    8.1.处理关于UTF-8编码的数据异常

    如果按照UTF-8格式不能成功解析字节流,终端必须_ Fail the WebSocket Connection _。握手或者后续的传递数据阶段都适用于这个规则。

    9.扩展

    Websocket客户端可能要求使用协议扩展。服务器可能接受部分或者所有来自客户端的扩展请求。如果客户端没有请求相关的扩展,服务器一定不能响应。如果在客户端和服务器协商阶段指定了部分扩展参数,那么参数的使用必须准守相关的扩展规则。

    9.1.协商扩展

    客户端使用扩展时可以在请求头中使用|Sec-WebSocket-Extensions| 字段,请求头的name和value规则遵循http请求头的相关规则。本章节使用ABNF规则来定义请求头。如果客户端或者服务器收到不符合ABNF规则的value,可以立刻_ Fail the WebSocket Connection _。

           Sec-WebSocket-Extensions = extension-list
             extension-list = 1#extension
             extension = extension-token *( ";" extension-param )
             extension-token = registered-token
             registered-token = token
             extension-param = token [ "=" (token | quoted-string) ]
                 ;When using the quoted-string syntax variant, the value
                 ;after quoted-string unescaping MUST conform to the
                 ;'token' ABNF.
    

    和其他HTTP头部字段一样,一个header值可以被拆分或者组装成多行。所以如下实例是一样的。

          Sec-WebSocket-Extensions: foo
          Sec-WebSocket-Extensions: bar; baz=2
    
    

    等于

          Sec-WebSocket-Extensions: foo, bar; baz=2
    
    

    使用的extension-token必须是已经注册的(在11.4章节介绍)。必须使用与扩展相关的参数。如果服务器没有确认某些扩展,客户端不能使用这些扩展的功能。

    扩展项的排列顺序是有特殊意义的。扩展项之间的交互顺序都在定义他们的文档中有相关描述。如果没有相关文档进行定义,在客户端请求头部中列出的扩展项的顺序,就是它期望的顺序。服务器返回的扩展项的顺序是实际使用的顺序。扩展项处理数据的顺序就是按照他们在服务器握手响应头部中出现的顺序。

    如果服务器返回的头部信息中|Sec-WebSocket-Extensions|中有“foo”和“bar”两个扩展项,那么对于数据的处理过程就是 bar(foo(data))。如果数据是分帧接收的话,那么把这个帧组装后再进行处理。

    服务器一个不规范的扩展头部实例:

             Sec-WebSocket-Extensions: deflate-stream
             Sec-WebSocket-Extensions: mux; max-channels=4; flow-control,
              deflate-stream
             Sec-WebSocket-Extensions: private-extension
    
    

    服务器通过|Sec-WebSocket-Extensions|头包含一个或者多个客户端发送过来的扩展项。服务器返回的扩展参数,以及返回什么样的值,都由具体的扩展项定义。

    9.2.已知扩展

    扩展可以为协议提供一些新的功能。本文档不描述任何扩展相关的信息。协议的实现可能会描述使用的相关扩展项.

    10.安全方面的考虑

    本章节讨论Websocket协议关于安全方面的点。特殊方面的安全点在后续的章节有讲述。

    10.1.非浏览器客户端

    Websocket在受信任的应用里运行的时候(比如浏览器)可以防止恶意JavaScript脚本的运行,比如检查|Origin|字段。查看1.6章节来了解更详细的内容。这个假设在其他类型的客户端中不成立。

    然而这个协议本身就是为了让在网页中的脚本语言使用的,当然也可以被其他主机应用使用,所以这些主机可能随意的发送|Origin|,这样有可能会迷惑服务器。服务器应该谨慎的去与客户端交流,不能单纯的认为对面就是已知主机上的脚本。所以,服务器不能认为客户端发送的都是合法的。

    举例:如果服务器使用前端传过来的sql进行数据库查询,所有的sql文本应该经过转义再发送到数据库,以免服务器遭受sql注入攻击。

    10.2.Origin注意事项

    服务器如果不是接收所有主机的请求的话,应该检查请求头中|Origin|的值。如果服务器不接受|Origin|主机的请求,应该在握手阶段返回一个http 403相应。

    |Origin|可以防止恶意代码在受保护的应用中运行时发起的某些攻击。客户端可以通过|Origin|机制来确定是否需要为脚本程序授予通讯权限。这种机制不是防止非浏览器应用去建立Websocket连接,而是防止恶意的JavaScript发送一个虚拟的Websocket握手请求。

    10.3.基础设施的攻击(Masking,掩码)

    Websocket除了会攻击终端设备以外,还会攻击网络基础设施,比如代理服务器。

    在协议的过程中,有一项实验用来模拟一类代理服务器攻击,这类攻击会污染一部分透明代理类(基于ip做转发的,不是普通的http代理服务器,比如带有http缓存功能的网关或者网桥设备,可以参考Talking to Yourself for Fun and Profit 中文版)。

    为了防止中间代理服务器被攻击,现在对每一个客户端发送的数据进行掩码操作,这样攻击者就不能伪造http请求来攻击中间代理服务器了。

    客户端必须为每个数据帧选择一个新的掩码key,不能预测下一个key的算法最安全。如果客户端使用了一个可以判断下一个key的算法,那么攻击者可能会使用某个数据,在与mask key进行掩码后刚好就是http请求,这样同样会对中间代理服务器进行缓存投毒。

    如果数据帧已经在发送过程中,那么就不能再对数据进行修改。否则,攻击者的脚本可能在开始发送的时候会写一串0的数据,然后边发送边修改数据帧的值,比如改成http请求。

    我们假定的攻击模型是客户端在发送数据时发送一个http请求,所以需要掩码的就是客户端发送到服务器的数据。服务器到客户端的数据可以看成是一个响应,所以没有必要对服务器的响应数据进行掩码。

    10.4.实现方面的一些限制

    应该对消息的大小进行限制,来避免攻击者会发送一个很大的消息或者分段的数据帧组合后出现很大的消息(2^60),这样容易使对面的服务器内存耗尽或者出现拒绝服务。

    10.5.客户端认证

    本协议不描述关于服务器如何认证客户端的相关规则。websocket服务器可以使用当前http服务器普遍使用的认证方式,比如cookies、http 认证、或者tls认证。

    10.6.连接的保密性和完整性

    连接的保密性和完整性通过使用TLS协议来实现。websocket协议的实现必须支持TLS。
    使用TLS的连接,保密程度完全取决于在TLS握手阶段协商的加密算法是否够强。为了达到期望的安全程度,客户端应该使用更强的TLS算法。Web Security Context: User Interface Guidelines描述相关算法。

    10.7.处理无效数据

    客户端和服务器必须对接收到的数据进行检查。当终端接收到不能理解的或者接收到的数据违反了终端制定的安全准则再或者握手过程中遇到了之前没有交流过的某些值,终端可以直接关闭TCP连接。如果在握手成功之后,收到了无效的数据,终端应该发送一个Close帧和一个状态码,之后再执行_ Close the WebSocket Connection _流程。Close帧中写入状态码可以方便排查问题。如果在握手阶段接收到了无效数据,服务器可以返回Http相关状态码。

    在发送文本数据的时候使用了错误的编码可能会引发一些安全方面的问题。本协议中的文本都是UTF-8编码。尽管协议指定了数据的长度,并且应用按照这个长度去读取消息,但是发送没有正确编码的数据仍然可能会导致数据丢失或者一些潜在的安全问题。

    10.8.握手阶段使用SHA-1

    这里描述的握手过程不依赖SHA-1相关的安全属性。

    翻译自:https://tools.ietf.org/html/rfc6455

    展开全文
  • gwsocket:快速,独立,与语言无关的WebSocket服务器符合RFC6455
  • 调用了openssl,里面内置hash函数,比如md5,...另外附带RFC 6455 websocket的实现(包含http代理) 但是(bio)socket部分实现有bug想要正常使用,需要自己重写ssl socket(bio) 解析部分有bug,只能学习和研究使用
  • WebSocket:Qt5(C ++)的轻量级RFC 6455(Web套接字)实现
  • 易语言-[RFC 6455] WebSocket Client 模块纯源码实现以及分析
  • rfc6455-websocket

    2021-08-27 22:31:36
    协议(rfc6455): ws80 wss443 握手和数据传输 1.握手:通过http/https,兼容http 请求 GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIH

    一.场景:

    web应用定时拉取http数据

    1.每次可能重新创建(keep-alive)

    2.http带多余的请求头(浏览器)

    3.单向请求

    二.协议(rfc6455):

    ws80 wss443

    握手和数据传输

    1.握手:通过http/https,兼容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

    Sec-WebSocket-Version: 13

    响应

    HTTP/1.1 101 Switching Protocols

    Upgrade: websocket

    Connection: Upgrade

    Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

    Sec-WebSocket-Protocol: chat

    2.数据传输:

    数据帧(opcode)

    text/二进制/连续帧

    控制帧

    ping pong close

    三.实现:

    org.java-websocket:Java-WebSocket:1.5.1

    WebSocketClient类回调

    onOpen()

    onMessage(ByteBuffer bytes)

    onClose()

    onError(ex)

    写数据

    webSocketClient.send(ByteBuffer bytes)

    四.遇到问题

    1.本身内部ping和pong机制以及超时都有实现默认超时1m

    2.需要支持多端

    五.参看文档

    https://datatracker.ietf.org/doc/rfc6455/?include_text=1

    https://github.com/TooTallNate/Java-WebSocket

    展开全文
  • The WebSocket Protocol enables two-way communication between a client running untrusted code in a controlled environment to a remote host that has opted-in to communications from that code....
  • WebSocket:Java SE 7 + Android轻量级WebSocket客户端服务器软件包(RFC 6455
  • 网络套接字 软件包websocket为指定的WebSocket协议实现了客户端和服务器。 特征 升级HTTP / Conn TLS 开始吧 安装 go get github.com/hslam/websocket 进口 import "github.... m := mux .... m .... conn , err :=
  • 节点的WebSocket客户端和服务器实现 概述 这是Node的WebSocket协议版本8和13的(大部分)纯JavaScript实现。 在“ test / scripts”文件夹中,有一些示例客户端和服务器应用程序实现了各种互操作性测试协议。...
  • RFC 6455 11.1.1 "ws"结构的注册 一个ws URI定义了一个WebSocket服务器和资源名称。 URI结构名称 ws 状态 永久的 URI结构语法 使用ABNF的语法和URI说明书中定义的字符: "ws:" "//" ...
  • rfc6455 WebSockets

    2018-01-30 16:34:00
    https://tools.ietf.org/html/rfc6455 转载于:https://www.cnblogs.com/cheungxiongwei/p/8385719.html

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,470
精华内容 988
关键字:

rfc6455