2023年11月25日发(作者:)

服务器有新消息主动推送给客户端浏览器

通常情况下,打开⽹页或app去查询或者刷新时,客户端向服务器发出请求然后返回数据,客户端与服务端对应的模式是: 客户端请求--服务端

响应, ⽽在有些情况下,服务端会主动推送⼀些信息到客户端,例如:新闻的订阅,天⽓的提醒等等,那么在这样的模式下,会有些问题值得思考:

1.应⽤服务器如何确定每⼀个应⽤所在的设备

2.服务端把消息推到哪,客户端⼜不像服务器有⼀个固定的地址

服务端主动推送到客户端是怎么⼀个过程?

结合⼀个实际问题分析下:

问题提出: 外卖app, 商家在商家后台需要实时的获取到有没有新订单,有的话是⼏个;这个需求类似与⽇常中使⽤QQ或者微信时的新信息提

醒⼀样,只要有新信息就需要提醒

最近⼯作中遇到⼀个场景,商家在商家后台需要实时的获取到有没有新订单,有的话是⼏个;这个需求类似与⽇常中使⽤QQ或者微信时的

新信息提醒⼀样,只要有新信息就需要提醒;商家基本在PC上使⽤,各式浏览器都有: IE系列(7.08.09.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的跨域⽅式请求,因此HTTPJSONP的长轮询⽅式是慢慢被淘汰的⼀种技术,建议采⽤XHR长轮询。

长轮询优缺点

优点

客户端很容易实现良好的错误处理系统和超时管理,实现成本与Ajax轮询的⽅式类似。

缺点

需要服务器端有特殊的功能来临时挂起连接。当客户端发起的连接较多时,服务器端会长期保持多个连接,具有⼀定的风险。

iframe

iframe 是很早就存在的⼀种 HTML 标记, 通过在 HTML 页⾯⾥嵌⼊⼀个隐蔵帧,然后将这个隐蔵帧的 SRC 属性设为对⼀个长连接的请

求,服务器端就能源源不断地往客户端输⼊数据。

优点:

这种⽅式每次数据传送不会关闭连接,连接只会在通信出现错误时,或是连接重建时关闭(⼀些防⽕墙常被设置为丢弃过长的连接, 服务器

端可以设置⼀个超时时间, 超时后通知客户端重新建⽴连接,并关闭原来的连接)。

缺点

IEMorzilla 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 htmlfile JavaScript comet 对象,⽀持 IEMozilla Firefox 浏览器,可以作为参考。

我们常⽤的⽹页版的gtalk就是这种实现⽅式,Google的开发⼈员使使⽤⼀个称为“htmlfile” ActiveX 解决了在 IE 中的加载显⽰问题。

Comet实现框架

CometD

CometD 框架是基于 HTTP 的事件驱动通信解决⽅案,使⽤了Bayeux通信协议,提供了⼀个 Java 服务器部件和⼀个 Java 客户端部件,还

有⼀个基于 jQuery Dojo JavaScript 客户端库。

Bayeux 通信协议主要是基于 HTTP,提供了客户端与服务器之间的响应性双向异步通信。Bayeux 协议基于通道进⾏通信,通过

该通道从客户端到服务器、从服务器到客户端或从客户端到客户端(但是是通过服务器)路由和发送消息。Bayeux 是⼀种

- 订阅协议。

CometD 与三个传输协议绑定在⼀起:JSONJSONP WebSocket。他们都依赖于 Jetty Continuations Jetty WebSocket API。在默认

情况下,可以在 Jetty 6Jetty 7、和 Jetty 8 中以及其他所有⽀持 Servlet 3.0 Specification 的服务中使⽤ CometD

服务器和内部构件

Atmosphere框架

Atmosphere提供了⼀个通⽤ API,以便使⽤许多 Web 服务器(包括 TomcatJettyGlassFishWeblogicGrizzlyJBossWebJBoss

Resin)的 Comet WebSocket 特性。它⽀持任何⽀持 Servlet 3.0 Specification Web 服务器。

Atmosphere 提供了⼀个 客户端库,该库可以使连接设置变得更容易,它能够⾃动检测可以使⽤的最佳传输协议(WebSockets

CometD)。Atmosphere jQuery 插件的⽤法与 HTML5 WebSockets API 相似。

Pushlet

Pushlet 使⽤了观察者模型:客户端发送请求,订阅感兴趣的事件;服务器端为每个客户端分配⼀个会话 ID 作为标记,事件源会把新产⽣的

事件以多播的⽅式发送到订阅者的事件队列⾥。

Pushlet 最后更新于201025号,之后⾄今没有再更新。

Comet实现要点

不要在同⼀客户端同时使⽤超过两个的 HTTP 长连接

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

服务器端的性能和可扩展性

⼀般 Web 服务器会为每个连接创建⼀个线程,如果在⼤型的商业应⽤中使⽤ Comet,服务器端需要维护⼤量并发的长连接。在这种应⽤背

景下,服务器端需要考虑负载均衡和集群技术;或是在服务器端为长连接作⼀些改进。

在客户和服务器之间保持⼼跳信息

在浏览器与服务器之间维持⼀个长连接会为通信带来⼀些不确定性:因为数据传输是随机的,客户端不知道何时服务器才有数据传送。服务

器端需要确保当客户端不再⼯作时,释放为这个客户端分配的资源,防⽌内存泄漏。因此需要⼀种机制使双⽅知道双⽅都在正常运⾏。

服务器端在阻塞读时会设置⼀个时限,超时后阻塞读调⽤会返回,同时发给客户端没有新数据到达的⼼跳信息。此时如果客户端已经关闭,

服务器往通道写数据会出现异常,服务器端就会及时释放为这个客户端分配的资源。

如果客户端使⽤的是基于 AJAX 的长轮询⽅式;服务器端返回数据、关闭连接后,经过某个时限没有收到客户端的再次请求,会认为客户端

不能正常⼯作,会释放为这个客户端分配、维护的资源。

当服务器处理信息出现异常情况,需要发送错误信息通知客户端,同时释放资源、关闭连接。

websocket

WebSocketHTML5开始提供的⼀种在单个 TCP 连接上进⾏全双⼯通讯的协议。WebSocket通讯协议于2011年被IETF定为标准RFC

6455WebSocketAPIW3C定为标准。在WebSocket API中,浏览器和服务器只需要做⼀个握⼿的动作,然后,浏览器和服务器之间就形

成了⼀条快速通道。两者之间就直接可以数据互相传送。

浏览器⽀持

浏览器版本⽀持

Chrome4+

Firefox4+

IE10+

Opera10+

Safari5+

详情查看 Browser compatibility

实现

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

总结

总结下来长轮询不是⼀个很好的⽅案,⽽且对于服务器⽽⾔是有风险的;另外⽀持WebSocket协议的浏览器都⽐较新,特⽐是IE需要10以上

的版本;⽽我们的业务是⾯向于商家端,商家的浏览器版本相对较低,很多对WebSocket都不⽀持;相对⽽⾔Comet的⽅式⽐较适合,也有

相应的实现框架,实现成本最低;因此最后我们还是决定使⽤Comet的⽅式来实现,后⾯上线运⾏⼀段时间之后再来给⼤家介绍。