跳到主要内容

SockJS - WebSocket 模拟

·7 分钟阅读
Marek Majkowski

WebSocket 技术正在普及,但所有浏览器都支持它还需要一段时间。与此同时,有许多项目旨在替代 WebSocket,并为 Web 应用程序启用“实时”功能。但是,所有尝试都只解决了通用问题的一部分,并且没有任何单一的解决方案可以工作、可扩展且不需要特殊的部署技巧。

这就是为什么一个新的项目诞生了SockJS - 又一个 WebSocket 模拟库,但这次做得对了。SockJS 有远大的目标

  • 简单的浏览器端和服务器端 API,尽可能接近 WebSocket API。
  • 完善的文档记录了扩展和负载均衡技术。
  • 传输必须完全支持跨域通信。
  • 传输在遇到限制性代理的情况下必须优雅地回退。
  • 连接建立应该很快。
  • 客户端不使用 Flash,仅限 Javascript。
  • 客户端 Javascript 必须经过合理良好的测试。
  • 此外,服务器端代码应该很简单,以便降低为不同语言编写服务器的成本。

简单的 API

这听起来可能很明显,但WebSocket API 实际上相当不错。这是 Ian Hickson 和其他人领导的巨大努力的成果。不应忘记,早期曾有过不太成功的实现类似目标的尝试 - WebSockets API 不是凭空开发的。

然而,我还没有见过任何 Javascript 库试图密切模拟这个 API。早期的 Socket.io 尝试过,但现在已经发展得很远了。

WebSocket 没有定义服务器端 API,但很容易想出一个与客户端具有相似理念和抽象的方案。

部署故事

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

负载均衡故事

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

为 SockJS 服务器使用多个域

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

使用支持 WebSocket 的负载均衡器

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

使用几乎任何负载均衡器

这不是首选解决方案,但即使在负载均衡器不支持 WebSocket 的环境中,也可以运行可扩展的 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 抽象,只有大约 230 行用于核心 SockJS 逻辑。

浏览器和服务器之间使用的 SockJS 协议已经非常简单,我们正在努力使其更加明显。

我们打算至少支持 Node 和 Erlang 服务器,我们也很乐意看到 Python 和 Ruby 实现。SockJS 旨在成为多语言的。

总结

SockJS 还很年轻,还有很多工作要做,但我们相信它对于实际应用程序来说已经足够稳定了。如果您计划进行实时 Web 应用程序开发,请尝试一下!(文章也发表在 github 页面 上)

© . All rights reserved.