跳到主要内容

介绍

本文为 WebSocket 协议的第一章,本文翻译的主要内容为针对整个 WebSocket 进行一个简单而又全面的介绍。通过这篇文章我们能够对 WebSocket 有一个整体的大致了解。

1.1 背景

此章节为非规范章节。

在历史上,创建一个客户端和服务端的双向数据 Web 应用(例如 IM 应用和游戏应用)需要向服务端频繁发送不同于一般 HTTP 请求的 HTTP 轮询请求来从服务端上游更新数据。

这个方法有许多的问题:

  • 服务端被迫使用大量的的潜在的 TCP 连接与客户端进行交互:一部分是用来发送数据,而另一部分是用来接收数据。
  • 应用层无线传输协议(HTTP)开销较大,每一个客户端到服务端的消息都有一个 HTTP 头。
  • 客户端脚本必须包含一个发送和接收对应的映射表来进行对应数据处理。

一个简单的解决方案是使用一个简单的 TCP 链接来进行双向数据传输。这就是 WebSocket 提供的能力。结合 WebSocket 的 API,它能够提供一个可以替代 HTTP 轮询的方法来满足 Web 页面和远端服务器的双向数据通信。

相同的技术可以被用到许多的 Web 应用:游戏、股票应用、多人协作应用、与后端服务实时交互的用户接口等。

WebSocket 协议设计的原因是取代已经存在的使用 HTTP 作为传输层的双向通信技术,从而使得已经存在的基础服务(如代理、过滤器、认证服务)能够受益。这种技术是基于效率和可靠性权衡后来进行实现的,而 HTTP 协议最初也不是用来做双向数据通信的。WebSocket 协议尝试实现基于现有的 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

客户端请求的第一行(leading line)遵循了 HTTP 请求行的格式。

服务端的第一行(leading line)遵循了 HTTP 状态行的格式。

HTTP 请求行和状态行的规范定义在RFC2616

在两个协议中,第一行 header 下面是一组无序的 header 字段。这些 header 字段包含的内容在本文的第四节。另外的 header 字段如cookies,也有可能存在。格式和解析头信息被定义在了RFC2616

当客户端和服务端都发送了他们的握手协议,并且当握手已经成功,那么数据传输就开始了。这是一个双方都可以独立发送任意数据的双向通信渠道。

在握手成功以后,客户端和服务端传输的数据来回传输的数据单位,我们在规范中称为消息(messages)。在传输中,一条消息有一个或者多个帧组成。WebSocket 中的消息不需要对应特定网络层中的帧,一条零散的消息可能由中间人合并或者拆分成网络层的帧。

帧有关联的类型。同一条消息的每一帧都包含相同类型的数据。通常来说,它可以是文本数据(UTF-8 编码)、二进制数据(留给应用解析的数据)和控制帧数据(不是用来传输数据,而是用来作为协议层的特定符号,如关闭连接帧)。当前版本的协议定义了 6 种控制帧类型并且预留了 10 个保留类型。

1.3 开始握手

此节为非规范章节。

开始握手为了与基于 HTTP 的服务端软件和中介兼容,因此一个独立的端口既能够同时满足 HTTP 客户端来与服务进行交互,又能够满足 WebSocket 客户端与服务进行交互。最终,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,客户端在握手过程中发送的 header 字段可能是乱序的,所以收到的 header 字段的顺序不同也没有太大影响。

GET 方法的请求 URI(Request-URI)是用于定义 WebSocket 连接的终端,允许同一个 IP 对多个域名提供服务,也允许多个 WebSocket 终端连接同一个服务器。

客户端在每一个握手的Hostheader 里面包含了一个主机域名。所以客户端和服务端都可以校验哪些域名在使用中。

另外的 header 字段是用来确定 WebSocket 协议的选项。这个版本中提供的特定选项是子协议选择(Sec-WebSocket-Protocol)、客户端支持的扩展列表(Sec-WebSocket-Extensions)、Originheader 字段等。请求 header 字段Sec-WebSocket-Protocol可以用来标识哪些子协议(基于 WebSocket 的应用高层协议)是客户端可以支持的。服务端会从中选择零个或者一个支持的协议并且在响应握手中输出它选择的那个协议。

Sec-WebSocket-Protocol: chat

服务端也可以设置 cookie 相关字段来设置 cookie 相关属性,具体文档见RFC6265

1.4 结束握手

此节为非规范章节。

结束握手远比连接握手简单。

任何一端都可以发送一个包含特定关闭握手的控制帧数据(详情见 5.5.1 节)。收到此帧后,另一端在不发送任何数据后会发送一个结束帧作为响应。收到另一端的结束帧后,最开始发送控制帧的端在没有数据需要发送时,就会安全的关闭此连接。

在发送了一个表明连接需要被关闭的控制帧后,这个客户端不会再发送任何的数据;在收到一个表明连接需要被关闭的控制帧后,这个客户端会丢弃此后的所有数据。

这样比两边同时发起握手要更加安全。

这个结束握手的目标是来补充 TCP 结束握手中的一些内容(FIN/ACK),而这是因为 TCP 结束握手在端与端之间并不一定可靠,尤其是有代理和其他的网络中介时会变得不可靠。

在发送关闭帧等待接受另一端的响应关闭帧时,在某些情况下可以避免数据的不必要丢失。例如,在某些平台中,如果一个 socket 在接收队列有数据时被关闭,会发送一个 RST 包,尽管数据还在等待被读取,这也会导致接收到 RST 的一方数据接收失败。

1.5 设计哲学

此节为非规范章节。

WebSocket 协议设计的原理,将框架最小化,对框架的唯一的约束就是使这个协议是基于帧而不是流并且可以支持 Unicode 文本和二进制帧两者中的任意一种。在基于 WebSocket 的应用层中,元数据是应该分层的,就像基于 TCP 的应用层(例如 HTTP)一样。

从概念上来看,WebSocket 层是基于 TCP 实现的,增加了以下的内容:

  • 增加了一个基于浏览器的同源策略模型
  • 增加了一个地址和协议命名机制用以在同一个端口上支持多个服务,在同一个 IP 地址自持多个主机名
  • 在 TCP 协议上分层构建框架机制回到 TCP 使用的 IP 包机制,但是没有长度限制
  • 包含一个设计用于处理有代理和其他网络中介的情况的额外的结束握手协议

除此之外,WebSocket 没有增加任何东西。基本上 WebSocket 的的目标是在约束的条件下向脚本提供尽可能接近原生的 TCP 的 Web 服务。它同时考虑了服务器在进行握手和处理有效的 HTTP 升级请求时,可以和 HTTP 共用一个服务。大家也可以使用其他协议来建立从客户端到服务端的消息通信,但 WebSocket 的协议的目的是为了提供一个相对简单的可以和 HTTP 共存,并且依赖于 HTTP 基础设施(如代理)的协议。这个非常接近 TCP 的协议因为基于安全的基础设施和针对性的能够简单使用和让事情变得更简单的补充(例如消息语义的补充),因此可以安全使用。

这个协议具有可扩展性,未来的版本可能会引入一些新的概念如多路复用。

1.6 安全模型

此节为非规范章节。

当 WebSocket 协议在 web 网页中应用时,WebSocket 协议在 Web 页面与 WebSocket 服务器建立连接时使用基于 web 浏览器的同源策略模型。所以说,当 WebSocket 协议在一个特定的客户端(不是 web 浏览器里面的网页)直接使用时,同源策略模型就不生效了,客户端可以接受任意的源数据。

该协议无法与已经存在的如 SMTP(RFC5421)和 HTTP 协议的服务器建立连接,如果需要的话,HTTP 服务器可以选择支持该协议。该协议还实现了严格约束的握手过程和限制数据不能在握手完成和建立连接之前插入数据进行传输(因此限制了许多被影响的服务器)。

WebSocket 服务器同样无法与其他协议尤其是 HTTP 建立连接。例如,一个 HTML“表单”可能会提交给一个 WebSocket 服务器。WebSocket 服务端只能读取包含特定的由 WebSocket 客户端发送的字段的握手数据。尤其是在编写这个规范时,攻击者不能只使用 HTML 和 JavaScript APIs 的 Web 浏览器来发送以Sec-开头的字段。

1.7 与 TCP 和 HTTP 的关系

此节为非规范章节。

WebSocket 协议是独立的基于 TCP 的协议。他和 HTTP 的唯一关系是建立连接的握手操作的升级请求是基于 HTTP 服务器的。

WebSocket 默认使用 80 端口进行连接,而基于 TLS(RFC2818)的 WebSocket 连接是基于 443 端口的。

1.8 建立连接

此节为非规范章节。

当建立了一个和 HTTP 服务器共享端口的连接时(这种情况很有可能发送在与 80 和 443 端口通信上),这个链接将会给 HTTP 服务器发送一个常规的 GET 请求来进行升级。在一个 IP 地址和一个单一的服务器来应对单一主机名的通信这种相对简单的设置上,基于 WebSocket 协议的系统可以通过一个更加实用的方法来进行部署。在更详细的设置(例如负载均衡和多服务器),与 HTTP 服务器分开的专属的 WebSocket 连接集群可能更加易于管理。在编写这个规范时,我们应该知道在 80 端口和 443 端口建立 WebSocket 连接的成功率是不同的,在 443 端口上面建立的连接很明显更容易成功,尽管这可能随着时间的变化而改变。

1.9 使用 WebSocket 协议的子协议

客户端可以通过在握手阶段中的Sec-WebSocket-protocol字段来请求服务端使用指定的子协议。如果指定了这个字段,服务器需要包含相同的字段,并且从子协议的之中选择一个值作为建立连接的响应。

子协议的名称可以按照第 11.5 节的方法进行注册。为了避免潜在的冲突,推荐使用包含 ASCII 码的域名名称作为子协议名。例如,Example Corporation 创造了在 Web 上通过多个服务器实现的一个聊天子协议(Chat subprotocol),他们可以叫做chat.example.com。如果 Example Organization 创造了他们相对的子协议叫做chat.example.org,这两个子协议可以被服务器同时实现,服务器可以根据客户端来动态的选择使用哪一个子协议。

子协议也可以通过修改名字的方式来向后兼容,例如:将bookings.example.net改为v2.bookings.example.net。WebSocket 客户端能够完全的区分这些子协议。向后兼容的版本控制可以通过复用相同的子协议字符和小心设计的子协议实现来保证这种扩展性。