SockJS 介绍
想要写一篇介绍SockJS的文章,又不知道写什么好,发现官方github项目上这篇文章比较好,于是直接翻译过来。不长不短,正好合适。
该文章发布于2016年,某些论述可能已经过时,这里尝试加以纠正,但有谬误,还请指正。
原文 —— WebSocket emulation done right
SockJS - WebSocket模拟器
WebSocket技术日益发展,但倘若要所有浏览器都支持它,尚需时日(目前主流浏览器基本都支持了)。
与此同时有非常多的项目以取代WebSocket为目标,尝试让web app具备“实时”能力。但所有这些项目仅仅解决了众多问题中的一部分,没有任何单个解决方案能够做到不需要部署上的奇技淫巧就能够正常工作且保有可伸缩性。
这就是SockJS诞生的原因,同其它项目类似,它又是一个WebSocket的模拟库,但不一样的是,它是一个非常有效的解决方案。
SockJS有着雄心勃勃的目标:
- 提供简单的客户端和服务端API,尽量向WebSocket API靠拢
- 提供文档周全的扩展和负载均衡技术
- 传输必须完全支持跨域通信
- 在限制代理的情况下, 传输必须优雅地回退
- 建立连接应该快速
- 客户端不需要Flash,JavaScript就够了
- 客户端的JavaScript必须经过合理的测试
- 为了使用不同的语言编写服务端,服务端代码应该简单
简单的API
这听起来很明显,但WebSocket api实际上相当不错。这是伊恩·希克森和其他人领导的巨大努力的结果。不应该忘记,以前也有一些不太成功的尝试来实现类似的东西,因此WebSockets api并不是凭空开发出来的。
到目前位置,我还没有见过任何JS库对WebSocket进行深度模拟。早起Socket.io有尝试这么做,但目前它已渐行渐远。
WebSocket没有定义服务器端api,但是很容易提出一个与客户端具有类似思想和抽象的方案。
关于发布
SockJS支持开箱即用的跨域通信。你能够并且应该将SockJS服务器独立出来,并放在与你的主网站不同的域名。这种方法有多种优点,坦率地说,它只是唯一合理的部署策略。
关于负载均衡
单个SockJS服务器容量是有限的。如果您预期单个服务器不足以满足您的需求,请查看下面的扩展场景。
使用多个域
最简单的方法就是直接将多个SockJS服务器部署在不同的域名下,并且允许客户端随机挑选一个。
使用具有WebSocket能力的负载均衡器
您可以选择在一个域下托管所有SockJS流量,并使用一个适当的支持WebSocket的负载平衡器来分割流量。有一个示例haproxy配置文件可以作为一个很好的起点。
使用随便什么负载均衡器
这不是一个推荐的方案,但这使得即使在不支持WebSocket的负载均衡器的环境中运行可扩展的SockJS服务称为可能。共享托管提供商就是这样做的——比如CloudFoundry。为了更快地建立连接,可以在客户端和服务器端禁用WebSocket协议。
在这样的环境中,负载平衡器必须将单个SockJS会话的所有请求转发到单个SockJS服务器。负载平衡器必须支持以下两种变体之一的粘性会话(会话关联):
- 基于前缀的粘性会话。所有SockJS的请求都带有session id前缀,好的负载均衡器会使用它作为会话关联算法的线索(比如HAProxy)
- JSESSIONID粘性会话。默认情况下,SockJS服务端会设置cookie。一些负载均衡器能够利用这些cookie(比如CloudFoundry)
健壮的传输协议
除了WebSocket本身,SockJS还支持几个精挑细选的协议,并且它们都支持跨域通信
其基本思想是,每个浏览器都应该有一个像样的流和轮询协议。轮询者必须在具有限制代理的环境中工作,并支持旧浏览器。每个浏览器可以通过三种方式建立连接:
WebSocket方式
WebSocket是最快最好的传输协议,它支持开箱即用的跨域连接。不幸的是它目前并不被所有浏览器支持。同时,一些浏览器可能在使用代理时会有问题。因此在浏览器的支持度和代理问题解决上可能还要花一些时间
流协议
SockJS的流协议是基于Http 1.1的chunking的
- 它允许浏览器接收一个带有多个部分的http响应。很好的流协议的例子是EventSource和基于Ajax的流。从浏览器发出的消息被使用另一个Ajax请求传输
每个浏览器都支持不同的流协议集,它们通常无法进行跨域通信。幸运的是,SockJS能够通过使用iframe并使用html5 postmessage api与之通信来克服这一限制。这相当复杂,但幸运的是大多数浏览器(ie7除外)都支持它。
轮训传输
SockJS对远古浏览器(包括ie7)支持几种旧轮询协议。不幸的是,这些技术相当缓慢,并且没什么优化的空间。
在客户端代理不支持WebSockets或http分块的情况下,也可以使用轮询传输。
快速建立连接
打开SockJS连接应该很快,在某些部署中,可能需要在用户访问的每个http页面上建立SockJS连接。
如果浏览器支持,SockJS将首先尝试打开本机WebSocket连接。根据网络和服务器设置,它可能成功或失败。失败应该发生得很快,除非客户端在一个行为异常的代理后面-在这种情况下,可能需要5秒的超时时间。
WebSocket连接失败后,SockJS打开xhr请求,检查代理是否支持分块。满足不支持HTTP分块的代理并不少见。在这样的环境中运行流协议将失败并超时。
如果分块工作正常,SockJS会选择浏览器支持的最佳流协议。否则使用轮询传输。
所有这些,根据浏览器的不同,可能需要3到4次从浏览器到服务器的往返时间,外加一个dns请求。除非你的代理有问题,或者住在南极洲,不然它应该是相当快的。
这就是SockJS避免使用flash传输的原因之一——如果端口843被阻塞,flash连接至少需要3秒。
简单的服务端
目前,SockJS节点实现在coffeescript中使用了大约1200行代码。WebSocket协议使用大约340个,简单的HTTP抽象使用220个,核心SockJS逻辑仅使用大约230个。
浏览器和服务器之间使用的SockJS协议已经非常简单,我们正在努力使其更加简单。
我们打算至少支持node和erlang服务器,我们也很高兴看到python和ruby的实现。Sockjs是多基因的。
总结
sockjs还很年轻,还有很多工作要做,但我们相信它足够稳定,适合真正的应用程序。如果你打算做实时网络应用,试试吧。