;

浏览器与服务器的消息通信

最近工作中遇到一个场景,商家在商家后台需要实时的获取到有没有新订单,有的话是几个;这个需求类似与日常中使用QQ或者微信时的新信息提醒一样,只要有新信息就需要提醒;商家基本在PC上使用,各式浏览器都有:如 IE系列(7.0,8.0,9.0及以上),chrome内核,firefox等;功能所属的部署在Tomcat 6.0上,如果技术需要可以部署到 Tomcat 7.0上;
我们先做做技术调研,这种浏览器与服务器实时通信的方式有哪些方式。

AJAX轮询

这是我们最自然想到的。 采用常规AJAX轮询的方式,每10s或者30s轮询一次,既可以判断出有有多少个新订单进入,且这种时间间隔对于消息提醒也是可以接受的。这种技术方式实现起来非常简单,目前的机器都是可以机器的,前端浏览器也都支持。
但是这种方式会有非常严重的问题,就是需要不断的向服务器发送消息询问,如果有1w个商家打开了浏览器,采用10s轮询的方式,则服务器则会承担1000 的QPS,这1w个商家可能只有10个有订单通知;这种方式会对服务器造成极大的性能浪费。
还有一个类似的轮询是使用JSONP跨域请求的方式轮询,在实现起来有差别,但基本原理都是相同的,都是客户端不断的向服务器发起请求。

优点
实现简单。
缺点
这是通过模拟服务器发起的通信,不是实时通信,不顾及应用的状态改变而盲目检查更新,导致服务器资源的浪费,且会加重网络负载,拖累服务器。

Comet

Comet是一种用于Web的推送技术,能使服务器实时地将更新的信息传送到客户端,而无须客户端发出请求,目前有两种实现方式:

长轮询(long polling)

长轮询 (long polling) 是在打开一条连接以后保持,等待服务器推送来数据再关闭,可以采用HTTP长轮询和XHR长轮询两种方式。

HTTP 和JSONP方式的长轮询

把 script 标签附加到页面上以让脚本执行。服务器会挂起连接直到有事件发生,接着把脚本内容发送回浏览器,然后重新打开另一个 script 标签来获取下一个事件,从而实现长轮询的模型。

XHR长轮询

这种方式是使用比较多的长轮询模式。
客户端打开一个到服务器端的 AJAX 请求然后等待响应;服务器端需要一些特定的功能来允许请求被挂起,只要一有事件发生,服务器端就会在挂起的请求中送回响应并关闭该请求。客户端 JavaScript 响应处理函数会在处理完服务器返回的信息后,再次发出请求,重新建立连接;如此循环。
现在浏览器已经支持CROS的跨域方式请求,因此HTTP和JSONP的长轮询方式是慢慢被淘汰的一种技术,建议采用XHR长轮询。

长轮询优缺点

优点
客户端很容易实现良好的错误处理系统和超时管理,实现成本与Ajax轮询的方式类似。
缺点
需要服务器端有特殊的功能来临时挂起连接。
当客户端发起的连接较多时,服务器端会长期保持多个连接,具有一定的风险。

iframe

iframe 是很早就存在的一种 HTML 标记, 通过在 HTML 页面里嵌入一个隐蔵帧,然后将这个隐蔵帧的 SRC 属性设为对一个长连接的请求,服务器端就能源源不断地往客户端输入数据。

优点:
这种方式每次数据传送不会关闭连接,连接只会在通信出现错误时,或是连接重建时关闭(一些防火墙常被设置为丢弃过长的连接, 服务器端可以设置一个超时时间, 超时后通知客户端重新建立连接,并关闭原来的连接)。
缺点
IE、Morzilla Firefox 下端的进度栏都会显示加载没有完成,而且 IE 上方的图标会不停的转动,表示加载正在进行。
Google 的天才们使用一个称为“htmlfile”的 ActiveX 解决了在 IE 中的加载显示问题,并将这种方法用到了 gmail+gtalk 产品中。Alex Russell 在 “What else is burried down in the depth's of Google's amazing JavaScript?”文章中介绍了这种方法。Zeitoun 网站提供的 comet-iframe.tar.gz,封装了一个基于 iframe 和 htmlfile 的 JavaScript comet 对象,支持 IE、Mozilla Firefox 浏览器,可以作为参考。
我们常用的网页版的gtalk就是这种实现方式,Google的开发人员使使用一个称为“htmlfile”的 ActiveX 解决了在 IE 中的加载显示问题。

Comet实现框架

CometD

CometD 框架是基于 HTTP 的事件驱动通信解决方案,使用了Bayeux通信协议,提供了一个 Java 服务器部件和一个 Java 客户端部件,还有一个基于 jQuery 和 Dojo 的 JavaScript 客户端库。

Bayeux 通信协议主要是基于 HTTP,提供了客户端与服务器之间的响应性双向异步通信。Bayeux 协议基于通道进行通信,通过该通道从客户端到服务器、从服务器到客户端或从客户端到客户端(但是是通过服务器)路由和发送消息。Bayeux 是一种 “发布- 订阅” 协议。

CometD 与三个传输协议绑定在一起:JSON、JSONP 和 WebSocket。他们都依赖于 Jetty Continuations 和 Jetty WebSocket API。在默认情况下,可以在 Jetty 6、Jetty 7、和 Jetty 8 中以及其他所有支持 Servlet 3.0 Specification 的服务中使用 CometD。
服务器和内部构件

Atmosphere提供了一个通用 API,以便使用许多 Web 服务器(包括 Tomcat、Jetty、GlassFish、Weblogic、Grizzly、JBossWeb、JBoss 和 Resin)的 Comet 和 WebSocket 特性。它支持任何支持 Servlet 3.0 Specification 的 Web 服务器。

Atmosphere 提供了一个 jQuery 客户端库,该库可以使连接设置变得更容易,它能够自动检测可以使用的最佳传输协议(WebSockets 或 CometD)。Atmosphere 的 jQuery 插件的用法与 HTML5 WebSockets API 相似。

Pushlet 使用了观察者模型:客户端发送请求,订阅感兴趣的事件;服务器端为每个客户端分配一个会话 ID 作为标记,事件源会把新产生的事件以多播的方式发送到订阅者的事件队列里。
Pushlet 最后更新于2010年2月5号,之后至今没有再更新。

Cometd 和Atmosphere框架参见示例代码 (https://github.com/brucefengnju/cometdatoms)。

Comet实现要点

不要在同一客户端同时使用超过两个的 HTTP 长连接
HTTP 1.1 规范中规定,客户端不应该与服务器端建立超过两个的 HTTP 连接, 新的连接会被阻塞,在IE浏览器中严格遵守了这种规定。

服务器端的性能和可扩展性
一般 Web 服务器会为每个连接创建一个线程,如果在大型的商业应用中使用 Comet,服务器端需要维护大量并发的长连接。在这种应用背景下,服务器端需要考虑负载均衡和集群技术;或是在服务器端为长连接作一些改进。

在客户和服务器之间保持“心跳”信息
在浏览器与服务器之间维持一个长连接会为通信带来一些不确定性:因为数据传输是随机的,客户端不知道何时服务器才有数据传送。服务器端需要确保当客户端不再工作时,释放为这个客户端分配的资源,防止内存泄漏。因此需要一种机制使双方知道双方都在正常运行。
服务器端在阻塞读时会设置一个时限,超时后阻塞读调用会返回,同时发给客户端没有新数据到达的心跳信息。此时如果客户端已经关闭,服务器往通道写数据会出现异常,服务器端就会及时释放为这个客户端分配的资源。
如果客户端使用的是基于 AJAX 的长轮询方式;服务器端返回数据、关闭连接后,经过某个时限没有收到客户端的再次请求,会认为客户端不能正常工作,会释放为这个客户端分配、维护的资源。
当服务器处理信息出现异常情况,需要发送错误信息通知客户端,同时释放资源、关闭连接。

WebSocket是HTML5开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。WebSocket通讯协议于2011年被IETF定为标准RFC 6455,WebSocketAPI被W3C定为标准。在WebSocket API中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。

浏览器支持

浏览器 版本支持
Chrome 4+
Firefox 4+
IE 10+
Opera 10+
Safari 5+

详情查看 Browser compatibility

实现

WebSocket的实现已经有很多种版本,详细可以查看DEMO

总结

总结下来长轮询不是一个很好的方案,而且对于服务器而言是有风险的;另外支持WebSocket协议的浏览器都比较新,特比是IE需要10以上的版本;而我们的业务是面向于商家端,商家的浏览器版本相对较低,很多对WebSocket都不支持;相对而言Comet的方式比较适合,也有相应的实现框架,实现成本最低;因此最后我们还是决定使用Comet的方式来实现,后面上线运行一段时间之后再来给大家介绍。

References

comet wiki

Comet:基于 HTTP 长连接的“服务器推”技术

反向 Ajax,第 1 部分: Comet 简介
What else is burried down in the depth’s of Google’s amazing JavaScript?
WebSocket 是什么原理?为什么可以实现持久连接
Ajax、Comet、HTML 5 Web Sockets技术比较分析
WebSocket wiki

使用 HTML5 WebSocket 构建实时 Web 应用

The WebSocket Protocol
How can I use HTTP/2 server push?
HTTP/2 Push with experimental Servlet API
HTTP/2 Interoperability and HTTP/2 Push
/google-talk-client-in-javascript