跳至主内容

SockJS - WebSocket 模拟

·阅读 7 分钟
Marek Majkowski

WebSocket 技术正在快速发展,但要等到所有浏览器都支持还需要一段时间。在此期间,有大量的项目旨在替代 WebSockets 并为 Web 应用程序启用“实时”功能。但所有这些尝试只解决了通用问题的一部分,而且没有一个单一的解决方案是有效的、可扩展的且不需要特殊部署技巧的。

因此,一个新项目 应运而生SockJS - 另一个 WebSocket 模拟库,但这次做得更好。SockJS 有宏大的目标:

  • 简单的前端和后端 API,尽可能接近 WebSocket API。
  • 文档齐全的扩展和负载均衡技术。
  • 传输协议必须完全支持跨域通信。
  • 在遇到限制性代理时,传输协议必须能优雅地回退。
  • 连接建立应该快速。
  • 客户端不使用 Flash,纯 JavaScript。
  • 客户端 JavaScript 必须经过合理充分的测试。
  • 此外,后端代码应该简单,以便降低为不同语言编写服务器的成本。

简单 API

这听起来可能显而易见,但 WebSocket API 实际上相当不错。这是 Ian Hickson 等人领导的巨大努力的结果。不应忘记,在此之前曾有过不太成功的尝试来 实现类似的功能 - WebSocket API 并非凭空产生。

然而,我还没有看到任何 JavaScript 库试图严格模拟这个 API。早期的 Socket.io 曾尝试过,但现在它已经发展得很远了。

WebSocket 没有定义后端 API,但可以轻松地构想出与客户端 API 具有相似思想和抽象的方案。

部署方案

SockJS 开箱即用地支持跨域通信。您可以,也应该,将 SockJS 服务器隔离并将其托管在与您的主网站不同的域上。这种方法有多种优势,坦率地说,这只是唯一明智的部署策略。

负载均衡方案

单个 SockJS 服务器的容量是有限的。如果您预计单个服务器无法满足您的需求,请查看下面的扩展方案。

为 SockJS 服务器使用多个域

最简单的解决方案是将每个 SockJS 服务器放在不同的域名下,例如 sockjs1.example.comsockjs2.example.com,并允许客户端随机选择一个服务器。

使用支持 WebSocket 的负载均衡器

您可以选择将所有 SockJS 流量托管在一个域下,并使用一个可靠的、支持 WebSocket 的负载均衡器来分配流量。有一个 HAProxy 配置文件的示例,可以作为一个很好的起点。

使用几乎任何负载均衡器

这不是首选解决方案,但在负载均衡器不支持 WebSockets 的环境中,仍然可以运行可扩展的 SockJS。共享主机提供商就是这样的,例如 CloudFoundry。为了加快连接建立速度,可以在客户端和服务器端都禁用 WebSocket 协议。在这种环境中,负载均衡器必须将单个 SockJS 会话的所有请求转发到单个 SockJS 服务器 - 负载均衡器必须支持两种变体中的一种粘性会话(会话亲和性)。

  • 基于前缀的粘性会话。所有发送到 SockJS 的请求都带有会话 ID 前缀。好的负载均衡器可能会将其用作会话亲和性算法的线索(例如 HAProxy 可以做到)。
  • JSESSIONID cookie 粘性会话。默认情况下,SockJS 服务器会设置此 cookie。一些负载均衡器可以识别此 cookie 并启用会话粘性(例如 CloudFoundry 就是这种情况)。

健壮的传输协议

除了原生的 WebSocket,SockJS 还支持几种精心挑选的传输协议,所有这些协议都支持跨域通信。基本思想是,对于每种浏览器,都应该有一种可靠的流式传输和轮询协议。轮询协议必须在具有限制性代理的环境中运行,并支持旧版浏览器。每种浏览器都有三种建立连接的方式:

原生 WebSocket

WebSocket 是最快、最好的传输协议,开箱即用地支持跨域连接。不幸的是,它尚未被广泛的浏览器支持。此外,某些浏览器可能在代理方面存在问题(例如,Firefox 的 WebSocket 实现无法通过大多数代理)。在浏览器供应商就协议和代理处理达成一致之前,还需要一些时间。

流式传输协议

SockJS 支持的流式传输协议基于 http 1.1 分块传输 - 它允许浏览器分多个部分接收单个 http 响应。流式传输协议的一个很好的例子是 EventSource 或通过 XHR(ajax)进行流式传输。从浏览器发送的消息使用另一个 XHR 请求进行发布。

每种浏览器支持的流式传输协议集不同,而且它们通常无法进行跨域通信。幸运的是,SockJS 可以通过使用 iframe 并使用 Html5 PostMessage API 与其进行通信来绕过此限制。这相当复杂,但幸运的是,大多数浏览器都支持(IE7 除外)。

轮询传输

SockJS 支持几种传统的轮询协议,适用于旧版浏览器(包括 IE7)。不幸的是,这些技术相当慢,但对此也无能为力。

在客户端代理不支持 WebSocket 或 http 分块传输(这是流式传输协议所必需的)的情况下,也可以使用轮询传输。

连接建立应该快速

打开 SockJS 连接应该很快,在某些部署场景下,可能需要在用户访问的每个 http 页面上建立 SockJS 连接。

如果浏览器支持,SockJS 首先会尝试打开一个原生的 WebSocket 连接。根据网络和服务器设置,它可能会成功或失败。失败应该很快发生,除非客户端位于一个配置错误的代理后面 - 在这种情况下,超时可能需要长达 5 秒。

在排除 WebSocket 传输后,SockJS 会打开一个 XHR 请求,旨在检查代理是否支持分块传输。遇到不支持 http 分块传输的代理并不罕见。在这种环境中运行流式传输协议将因超时而失败。如果分块传输正常工作,SockJS 会选择浏览器支持的最佳流式传输协议。否则,将使用轮询传输。

所有这些,取决于浏览器,可能需要 3 到 4 次从浏览器到服务器的往返时间,再加上一次 DNS 请求。除非您在使用损坏的代理,或者住在南极洲,否则应该相当快。

这也是 SockJS 避免使用 Flash 传输的原因之一 - 如果端口 843 被阻止,Flash 连接可能需要 至少 3 秒

客户端 JavaScript 必须经过合理充分的测试

SockJS 还很年轻,测试尚未得到充分进行。不过,我们有多个端到端的 QUnit 测试。目前已在几个地方部署:

后端代码应该简单

目前 SockJS-node 实现使用了大约 1200 行 CoffeeScript 代码。其中约 340 行用于 WebSocket 协议,220 行用于简单的 http 抽象,核心 SockJS 逻辑仅使用了约 230 行。

浏览器和服务器之间使用的 SockJS 协议已经相当简单,我们正在努力使其更加清晰。

我们打算至少支持 Node 和 Erlang 服务器,并乐于看到 Python 和 Ruby 的实现。SockJS 旨在支持多种语言。

总结

SockJS 还很年轻,仍有大量工作要做,但我们相信它已足够稳定,可用于实际应用。如果您计划开发实时 Web 应用,不妨尝试一下!(文章也发布在 github pages)

© . This site is unofficial and not affiliated with VMware.