跳转到主要内容
Chinese, Simplified

由HyBi工作组开发的WebSocket有线协议(RFC 6455)由两个高级组件组成:用于协商连接参数的开放HTTP握手和二进制消息帧机制,以实现低开销、基于消息的文本和二进制数据传输。

WebSocket协议试图在现有HTTP基础架构的上下文中解决现有双向HTTP技术的目标;因此,它被设计为在HTTP端口80和443上工作……然而,这种设计并没有将WebSocket限制为HTTP,而且未来的实现可以在专用端口上使用更简单的握手,而无需重新发明整个协议。

                                                                          ----WebSocket协议RFC 6455

WebSocket协议是一个功能完整的独立协议,可以在浏览器之外使用。尽管如此,它的主要应用程序是作为基于浏览器的应用程序的双向传输。

二进制框架层

客户端和服务器端WebSocket应用程序通过一个面向消息的API进行通信:发送方提供一个任意的UTF-8或二进制有效负载,当整个消息可用时,接收方被通知它的传递。为了实现这一点,WebSocket使用了自定义的二进制帧格式(图17-1),它将每个应用程序消息分解成一个或多个帧,将它们传输到目的地,重新组装它们,并在收到整个消息后通知接收者。

Figure 17-1. WebSocket frame: 2–14 bytes + payload

Figure 17-1. WebSocket frame: 2–14 bytes + payload

Frame

通信的最小单位,每个单位包含一个可变长度的帧头和一个可携带全部或部分应用程序消息的有效负载。

Message

映射到逻辑应用程序消息的完整帧序列。

决定将应用程序消息分割成多个框架是由客户机和服务器框架代码的底层实现做出的。因此,应用程序仍然很高兴地不知道各个WebSocket帧或者帧是如何执行的。话虽如此,理解每个WebSocket帧在连线上是如何表示的要点仍然是有用的:

  • 每个帧(FIN)的第一个比特表示该帧是否为消息的最后一个片段。一条消息可能只包含一帧。
  • 操作码(4位)表示传输帧的类型:用于传输应用程序数据的文本(1)或二进制(2)或用于连接活性检查的控制帧,如连接关闭(8)、ping(9)和pong(10)。
  • 掩码位指示负载是否被掩码(仅针对从客户机发送到服务器的消息)。
  • 有效载荷长度用可变长度字段表示:
    • 如果0-125,那么这就是有效载荷长度。
    • 如果是126,那么下面的两个字节表示一个16位无符号整数,表示帧长度。
    • 如果是127,那么下面的8个字节表示一个64位无符号整数,表示帧长度。
  • 屏蔽键包含一个用于屏蔽有效负载的32位值。
  • 有效负载包含应用程序数据和自定义扩展数据(如果客户端和服务器在建立连接时协商了扩展)。

所有客户端发起的帧的有效负载都使用帧头中指定的值来隐藏:这可以防止恶意脚本在客户端执行缓存中毒攻击,攻击那些可能不理解WebSocket协议的中介。有关这次攻击的详细信息,请参考2011年W2SP上的 "Talking to Yourself for Fun and Profit"

因此,每个服务器发送的WebSocket帧都会产生2-10字节的帧开销。客户端还必须发送一个屏蔽密钥,这将在头文件中增加额外的4个字节,导致开销增加6-14个字节。没有其他元数据可用,例如头字段或关于有效负载的其他信息:所有WebSocket通信都是通过交换帧来执行的,这些帧将有效负载视为应用程序数据的不透明blob。

WebSocket多路复用和堆头阻塞( Head-of-Line Blocking)

WebSocket易受行头阻塞的影响:消息可以被分割成一个或多个帧,但是来自不同消息的帧不能交叉,因为在HTTP/2帧机制中没有等效的“流ID”;参见流、消息和帧)。

因此,一个大消息,即使被分割成多个WebSocket帧,也会阻止与其他消息相关联的帧的传递。如果您的应用程序交付的是对延迟敏感的数据,请注意每个消息的有效负载大小,并考虑将大消息拆分为多个应用程序消息!

核心WebSocket规范中缺乏多路复用也意味着每个WebSocket连接都需要一个专用的TCP连接,这可能成为HTTP/1的一个潜在问题。由于浏览器维护的每个源的连接数量有限而进行的部署;请参阅耗尽客户机和服务器资源。

从好的方面来看,由HyBi工作组开发的“WebSockets多路复用扩展”解决了后一个限制:

有了这个扩展,一个TCP连接可以通过封装带有通道ID标签的帧来提供多个虚拟WebSocket连接……这个多路复用扩展维护独立的逻辑通道,每个逻辑通道提供独立WebSocket连接的完全等价逻辑,包括单独的握手头。

                                                          --------------WebSocket多路复用(草案10)

有了这个扩展,多个WebSocket连接(通道)可以在同一个TCP连接上多路复用。然而,每个单独的通道仍然很容易被线头阻塞!因此,一种可能的解决方案是使用不同的通道或专用的TCP连接并行地多路传输多个消息。

最后,注意前面的扩展仅对HTTP/1是必要的。x连接。虽然目前还没有官方规范可以使用HTTP/2传输WebSocket帧,但是这样做会容易得多:HTTP/2有内置的流多路复用,通过在HTTP/2帧机制中封装WebSocket帧,可以在一个会话中传输多个WebSocket连接。

协议的扩展

WebSocket规范允许协议扩展:WebSocket协议的有线格式和语义可以通过新的操作码和数据字段进行扩展。虽然有些不寻常,但这是一个非常强大的特性,因为它允许客户端和服务器在基本的WebSocket框架层之上实现额外的功能,而不需要任何来自应用程序代码的干预或合作。

有一些WebSocket协议扩展的例子吗?负责WebSocket规范开发的HyBi工作组列出了两个正在开发中的官方扩展:

“WebSockets的多路复用扩展”

这个扩展为单独的逻辑WebSocket连接提供了一种共享底层传输连接的方法。

“WebSocket的压缩扩展”

一个用于创建WebSocket扩展的框架,为WebSocket协议添加压缩功能。

 

如前所述,每个WebSocket连接都需要一个专用的TCP连接,这是低效的。多路复用扩展通过为每个WebSocket帧增加一个额外的“通道ID”来允许多个虚拟的WebSocket通道共享一个TCP连接来解决这个问题。

类似地,基本的WebSocket规范没有为传输数据的压缩提供任何机制或规定:每一帧都携带由应用程序提供的有效负载数据。因此,虽然这对于优化的二进制数据结构来说不是问题,但这可能会导致高字节传输开销,除非应用程序实现了自己的数据压缩和解压缩逻辑。实际上,压缩扩展支持HTTP提供的等效传输编码协商。

要启用一个或多个扩展,客户机必须在初始升级握手时公布它们,服务器必须选择并确认在协商的连接的生命周期中将使用的扩展。作为一个实际的示例,现在让我们仔细看看升级顺序。

WebSocket多路复用和压缩在野外

到2013年中期,WebSocket多路复用还没有被任何流行的浏览器支持。类似地,对压缩的支持也很有限:谷歌Chrome和最新的WebKit浏览器可能会在服务器上发布一个“x-webkit-deflate-frame”扩展。然而,deflate-frame是基于一个过时的修订标准,并将在未来过时。

顾名思义,每帧都是逐帧压缩有效负载内容,这对于可能在多个帧之间分割的大消息不是最优的。因此,压缩扩展的最新版本已经切换到每消息压缩——这是一个好消息。坏消息是每条消息的压缩还处于试验阶段,目前还不能在任何流行的浏览器中使用。

因此,应用程序应该密切关注传输数据的内容类型,并在适用的情况下应用自己的压缩。也就是说,至少要等到本地WebSocket压缩支持在所有流行的浏览器上广泛可用。这对于移动应用程序尤其重要,因为在移动应用程序中,每个不必要的字节都会给用户带来很高的代价。

HTTP升级协商

WebSocket协议提供了很多强大的特性:面向消息的通信、它自己的二进制帧层、子协议协商、可选协议扩展等等。因此,在交换任何消息之前,客户机和服务器必须协商建立连接所需的适当参数。

利用HTTP执行握手提供了几个优点。首先,它使WebSocket与现有的HTTP基础设施兼容:WebSocket服务器可以在端口80和443上运行,这两个端口通常是客户端唯一开放的端口。其次,它允许我们重用和扩展HTTP升级流自定义WebSocket头执行协商:

  • Sec-WebSocket-Version
    • 由客户端发送来指示它想要使用的WebSocket协议的版本(对于RFC6455“13”)。如果服务器不支持客户端版本,则必须使用支持版本列表进行响应。
  • Sec-WebSocket-Key
    • 客户端发送的自动生成的密钥,它充当服务器的“质询”,以证明服务器支持所请求的协议版本。
  • Sec-WebSocket-Accept
    • 包含Sec-WebSocket-Key签名值的服务器响应,证明它理解所请求的协议版本。
  • Sec-WebSocket-Protocol
    • 用于协商应用程序子协议:客户端发布支持协议列表;服务器必须使用单个协议名进行应答。
  • Sec-WebSocket-Extensions
    • 用于协商用于此连接的WebSocket扩展:客户端声明支持的扩展,服务器通过返回相同的头来确认一个或多个扩展。

有了这些,我们现在就有了执行HTTP升级和协商客户端和服务器之间新的WebSocket连接的所有必要的部分:

GET /socket HTTP/1.1
Host: thirdparty.com
Origin: http://example.com
Connection: Upgrade
Upgrade: websocket 
Sec-WebSocket-Version: 13 
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== 
Sec-WebSocket-Protocol: appProtocol, appProtocol-v2 
Sec-WebSocket-Extensions: x-webkit-deflate-message, x-custom-extension
  • 请求执行WebSocket协议升级
  • 客户端使用的WebSocket协议版本
  • 自动生成密钥来验证服务器协议支持
  • 应用程序指定的子协议的可选列表
  • 客户端支持的可选协议扩展列表

就像浏览器中任何其他客户端发起的连接一样,WebSocket请求遵循同源策略:浏览器自动在升级握手中附加源头,远程服务器可以使用CORS来接受或拒绝跨源请求;参见跨源资源共享(CORS)。要完成握手,服务器必须返回一个成功的“交换协议”响应,并确认客户端公布的选择:

HTTP/1.1 101 Switching Protocols 
Upgrade: websocket
Connection: Upgrade
Access-Control-Allow-Origin: http://example.com 
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= 
Sec-WebSocket-Protocol: appProtocol-v2 
Sec-WebSocket-Extensions: x-custom-extension
  • 确认WebSocket升级的响应代码
  • CORS头指示选择进入跨源连接
  • 签名键值证明协议支持
  • 由服务器选择的应用程序子协议
  • 由服务器选择的WebSocket扩展的列表

所有RFC6455-compatible WebSocket服务器使用相同的算法来计算客户挑战的答案:Sec-WebSocket-Key的内容与独特的GUID连接字符串中定义的标准,SHA1哈希计算,生成的字符串是base - 64编码,并返回给客户端。

一次成功的WebSocket握手至少必须包含协议版本和客户端发送的自动生成的质询值,以及来自服务器的101 HTTP响应代码(交换协议)和一个哈希的质询-响应来确认所选协议版本:

  • 客户端必须发送Sec-WebSocket-Version和Sec-WebSocket-Key。
  • 服务器必须通过返回Sec-WebSocket-Accept来确认协议。
  • 客户端可以通过Sec-WebSocket-Protocol发送应用程序子协议列表。
  • 服务器必须选择一个子协议并通过Sec-WebSocket-Protocol返回。如果服务器不支持任何连接,则连接将中止。
  • 客户端可以发送一个协议扩展列表在Sec-WebSocket-Extensions。
  • 服务器可以确认一个或多个选定的扩展通过Sec-WebSocket-Extensions。如果没有提供扩展,则连接在不提供扩展的情况下继续运行。

最后,一旦前面的握手完成,如果握手成功,连接现在就可以用作交换WebSocket消息的双向通信通道。从这里开始,客户端和服务器之间不再有其他明确的HTTP通信,由WebSocket协议接管。

代理、中介和WebSockets

在实践中,由于安全和策略的原因,许多用户拥有一组受限的开放端口——具体来说端口80 (HTTP)和端口443 (HTTPS)。因此,WebSocket协商是通过HTTP升级流执行的,以确保与现有网络策略和基础设施的最佳兼容性。

然而,正如我们前面提到的代理中介,TLS,和新协议在网络上,许多现有HTTP中介可能不理解新WebSocket协议,从而导致各种各样的失败案例:盲目连接升级,意想不到的WebSocket帧缓冲,内容修改协议的不了解,误分类的WebSocket信息流作为妥协的HTTP连接,等等。

WebSocket密钥和接受握手解决了一些问题:它是一个安全策略,可以防止服务器和中介在没有真正理解WebSocket协议的情况下盲目地“升级”连接。然而,尽管这种预防措施解决了显式代理的一些部署问题,但它对于“透明代理”是不够的,“透明代理”可以在没有通知的情况下分析和修改网络上的数据。

解决方法?建立一个安全的端到端隧道。,用WSS !通过在执行HTTP升级握手之前协商一个TLS会话,客户机和服务器建立一个加密隧道,从而解决前面列出的所有问题。对于移动客户端来说尤其如此,它的流量经常要通过各种代理服务,而这些代理服务在WebSocket中可能不能很好地发挥作用。

 

原文:https://hpbn.co/websocket/

本文:http://jiagoushi.pro/node/1111

讨论:请加入知识星球【首席架构师智库】或者小号【jiagoushi_pro】

Tags
 
Article
知识星球
 
微信公众号
 
视频号