2009年4月17日星期五

B/S架构中的数据推送技术

B/S架构中服务器向浏览器推送数据在很多场合都有需求,比如实时的监控报警、实时的调度,等等。凡是对实时性要求越高的场景,越是需要服务器及时、准确地向浏览器推送数据。这里我们就讨论一下在B/S架构下,可以实现从服务器向浏览器推送数据的几种技术及其相应的特点。

基于HTTP协议

1. HTTP协议的特点

纯的HTTP协议在本质上是无状态、无连接的,它基于请求/响应的工作模式,使得浏览器在每次发生请求的时候和服务器建立连接,当接收到响应的时候断开连接;在这种情况下,要让服务器主动向浏览器发送数据是不可能的。

技术总是为需求服务的,在很多浏览器需要及时获取服务器数据更新的需要下,技术人员们变通地发明了一些基于HTTP协议的“伪”长连接技术,实现了服务器数据向浏览器的“准”推送。

2. 定时刷新

定时刷新是最粗糙的实现快速感知服务器数据变化的方法,原理就是不停地刷新页面从而显示最新的服务器端数据。定时刷新从技术上大体有两种实现方法:一是通过HTML的META标签设置页面刷新间隔,比如以下的标签会在浏览器中每隔10秒刷新当前页面:
<meta equiv="refresh" content="10" />

这种方式会看到页面有明显的刷新,用户体验会比较差;与此相对应的另外一种方式就是AJAX,在JavaScript脚本中添加一个定时器,每隔一定时间向服务器发送一个AJAX请求,得到响应以后去更新页面的内容。以下JavaScript代码会每隔10秒发送一个AJAX请求去更新当前页面的部分内容:
setInterval(function(){sendAJAXRequest()},10000);

如图 1是定时刷新的模型图:
图 1 定时刷新的模型图

定时刷新这种模式相当于一个下级每隔一段时间打电话给上级,请示当前指示。当然,如果在这个时间段内发生了一些情况,他们之间是没办法沟通的,所以信息并不是“实时”的;另外,刷新间隔到底设置多少合适也是个问题,服务器的负担也比较大。

3. 轮询
轮询也是通过AJAX进行的,它与AJAX定时刷新的区别仅在于接收到AJAX响应以后的工作。定时刷新完全是浏览器主动的,在浏览器和服务器之间存在一定的断开间隔;而轮询这种方式会在AJAX响应中再次发送AJAX请求,也就是说当响应结束,浏览器和服务器之间一旦断开连接的时候,浏览器会再次发送请求建立连接。

如图 2是轮询的模型图:
图 2 轮询的模型图

轮询这种模式相当于一个下级不停给上级打电话请示,上级指示下达以后挂了电话,下级可能去执行指示或者不执行,但是随即下级将再次给上级打电话继续请示。这种模式可以保证数据更新比较实时,但是服务器负担也是一个问题。

顺便提一下,轮询这种模式一般会在浏览器的XMLHttpRequest的readyState为4(响应数据传输结束)的时候调用回调函数,此时连接已经关闭;但是在Firefox中支持Streaming AJAX模式,也就是XMLHttpRequest的readyState为3(响应数据仍在传输)的时候调用回调函数,此时连接尚未关闭。

4. 基于iframe的流模式

iframe可以在当前页面中嵌入一个文档页面,如果把这个页面设为隐藏,并将其src属性设置为一个特殊的请求,数据将会源源不断地从服务器发送到浏览器。

比如以下的JSP页面,它将每隔10秒更新一个数输出到浏览器,不出意外它将会永远执行下去,浏览器和服务器之间的连接也永远不会关闭:
<% int i = 1; try { while (true) { out.print("

"+(i++)+"

");
out.flush();

try {
Thread.sleep(10000);
} catch (InterruptedException e) {}
}
} catch (Exception e) {}
%>


但是这样做有个很显然的不足,由于响应始终没有结束,因此浏览器里面的加载进度条会始终显示加载没有完成。当然,这也并不是没有办法解决,比如http://www.zeitoun.net/articles/comet_and_php/start就提供了IE、Opera、WebKit核心(Chrome/Safari)、Gecko核心(Firefox)等浏览器的解决方案。

如图 3是基于iframe流模式的模型图:
图 3 基于iframe流模式的模型图

基于iframe的流模式相当于一个下级给上级打电话,上级不停发出指示,下级一边接收指示、一边执行。这种模式有着比较好的实时性,比如Gmail就是采用这种模式。

5. 开源框架Pushlets
Pushlets是一个实现了AJAX轮询和iframe流模式的开源框架(Java+JavaScript),对此有兴趣的可以参考:http://www.pushlets.com/,Pushlets采用LGPL许可。

基于消息

1. 基于消息的架构

使用消息可以实现松散耦合的分布式数据通讯。通过消息从服务器向浏览器推送数据一般需要一个消息中间件(Message Oriented Middleware,MOM),服务器将数据推送给消息中间件,消息中间件再将数据以消息的方式推送给浏览器。基于消息的架构有个特别大的优点,那就是不但可以实现服务器向单浏览器、服务器向多浏览器推送数据;还支持浏览器到浏览器之间的数据推送。这将会在预警、调度等场合有非常大的用武之地。

图 4和图 5分别是基于订阅/发布模式和点到点模式的消息传递示意。在B/S架构中,服务器可以发布消息,所有订阅该主题的浏览器都会接受到该消息,这就实现了从服务器向多浏览器的数据推送;浏览器或者服务器也可以向特定的对象发送消息,消息将在一个消息队列中被发送,对方浏览器就可以收到该消息,这就实现了服务器向某特定浏览器或者浏览器之间的数据推送。

2. Java消息服务(JMS)


JMS是一组公开的Java API,它定义了与消息相关的接口和语义,目前JMS已经成为J2EE中的重要组成部分。
图 6 JMS工作原理

如图 6是JMS工作原理。当有客户端连接到JMS服务器的时候,JMS的连接工厂会根据连接类型来创建一个虚拟连接,这个连接会具体负责消息的传递;在这个连接建立完成后,会产生一个会话,会话中保存了消息生产者或消息消费者的信息;消息的消费者会对感兴趣的消息目的地(队列或主题)建立一个监听,消息的生产者则负责把消息发送到这个目的地上。

另外JMS还有一些值得一提的特点,比如支持事务性会话、可以设置消息的持久性、设置消息的优先级、允许消息过期、可以构建长期订阅等。这些特性在各种企业级应用环境下都有可能提高应用的功能或性能。

实现JMS的商业中间件有IBM MQSeries、BEA WebLogic JMS等;开源中间件有OpenJMS、Apache ActiveMQ等。

3. ActiveMQ

ActiveMQ是Apache基金会的著名开源项目,目前Release版本5.2完整支持JMS1.1和J2EE 1.4规范。ActiveMQ采用Apache 2.0许可发布。ActiveMQ的优势在于其支持集群部署、支持多种应用层协议和诸多客户端开发语言等特点。

ActiveMQ主要支持以下协议:
 OpenWire
 REST
 Stomp
 WS Notification
 XMPP
 AMQP

下面这里将使用ActiveMQ和Stomp协议来演示各种方式的数据的推送。

4. Stomp协议

Stomp是一种简单、实现容易的协议,因此支持非常广泛,这里采用Stomp协议的主要原因也是因为其支持的客户端开发语言最多,在各种环境下都有用武之地。这些开发语言主要包括:
 ActionScript 3
 C
 C++
 .Net
 Delphi
 Perl
 PHP
 Python
 Ruby

下面主要用到了ActionScript和.Net语言。

5. 发布/订阅模式的实现

发布/订阅模式适用于广播性质的数据推送。比如在实时监控系统中,当我们的指令中心需要向所有监控目标发送信息的时候,这种模式就比较适合。为了简单起见,这里的服务器端和浏览器端都使用了ActionScript实现。

在浏览器端,我们需要订阅服务器主题。比如所有的接收指令的目标都需要监听“Alarm”频道,那么在浏览器中的代码应该如下:
private function sub():void
{
var ch:ConnectHeaders = new ConnectHeaders();
stomp.connect("localhost", 61613, ch);
stomp.subscribe( "/topic/Alarm" );
}
private function onStompMessage(event:MessageEvent):void
{
var byteArray:ByteArray = event.message.body;
var str:String = byteArray.toString();
}


图 7 发布/订阅模式的数据推送

6. 点对点模式的实现

点对点模式适合类似调度的功能场景。比如在监控系统中,当某个指令需要下达给具体某个目标的时候,就需要点对点模式的数据推送。

实现点对点模式的数据推送需要知道数据发送的目的地,以下代码演示了如何从指令中心“党中央”发送指令到“毒蛇”的过程:
private function sendMsg():void
{
var destination:String = "/queue/毒蛇";
stomp.sendTextMessage(destination, “注意隐蔽”);
}


当然,“毒蛇”需要监听所有发送给他的消息:
private function login():void
{
var ch:ConnectHeaders = new ConnectHeaders();
stomp.connect("localhost", 61613, ch);
stomp.subscribe( "/queue/毒蛇" );
}

图 8 点对点模式的数据推送

7. 实时监控Demo

这里通过消息模式实现了一个实时监控的演示。服务器端是使用.Net Stomp API写的控制台程序,Demo测试使用1000个监控目标,数据发送间隔0.5秒,数据推送到“realmonitor”频道。浏览器端使用Flex,监听“realmonitor”频道。

在服务器端,这个Demo中设置每100个目标信息拼装成一条消息,也就是说每0.5秒会发送10条消息。如果每条消息的信息(坐标和一些属性信息)大约50字节的话,每条消息大概5KB;每秒发送两次,总共大概100KB数据量会被推送。

图 9 Flex中实时监控效果

如图 9是在Flex中实现的实时监控效果。

2 条评论:

菩提老王 说...

总结的太好了,学习ing

匿名 说...

谢谢啊,受益匪浅噢