服务器推送技术(ServerPush)是最近web技术中最热门的一个流行术语,它的别名叫Comet(彗星)。它是继AJAX之后又一个倍受追捧的Web技术。服务器推送技术与最近的流行与AJAX有着密切的关系。
随着Web技术的流行,越来越多的应用从原有的C/S模式转变为B/S模式,享受着Web技术所带来的各种优势(例如跨平台、免客户端维护、跨越防火墙、扩展性好等)。
基于WEB的实时事件通知方式大致有五种方案:HTTP拉取方式(pull),HTTP流,LongPolling,FlashXMLSocket方式,JavaApplet。
首先说下Comet这个词,Comet这个词是最早由AlexRussell(DojoToolkit的项目Lead)提出的,称基于HTTP长连接、无须在浏览器端安装插件的“服务器推(Push)”技术为“Comet”。
1.HTTP拉取方式(pull)
在这种传统的方法中,客户端以用户可定义的时间间隔去检查服务器上的最新数据。这种拉取方式的频率要足够高才能保证很高的数据精确度,但高频率可能会导致多余的检查,从而导致较高的网络流量。而另一方面,低频率则会导致错过更新的数据。理想地,拉取的时间间隔应该等于服务器状态改变的速度。常见的实现如利用"<metahttp-equiv="refresh"c/>"tag,当然利用xmlHttpRequest定时取也是一种方法。
2.HTTP流(Push机制)
HTTP流有两种形式:
*PageStream:页面上不间断的HTTP连接响应(HTTP1.1KeepAlive).
通过在HTML页面里嵌入一个隐蔵帧(iframe),然后将这个隐蔵帧的SRC属性设为对一个长连接的请求,服务器端就能源源不断地往客户端输入数据。
*ServiceStream:XMLHttpRequest连接中的服务器数据流。
客户端是在XMLHttpRequest的readystate为4(即数据传输结束)时调用回调函数,进行信息处理。当readystate为4时,数据传输结束,连接已经关闭。MozillaFirefox提供了对StreamingAJAX的支持,即readystate为3时(数据仍在传输中),客户端可以读取数据,从而无须关闭连接,就能读取处理服务器端返回的信息。IE在readystate为3时,不能读取服务器返回的数据,目前IE不支持基于StreamingAJAX。
注:使用PageStream(iframe)请求一个长连接有一个很明显的不足之处:IE、MorzillaFirefox下端的进度栏都会显示加载没有完成,而且IE上方的图标会不停的转动,表示加载正在进行。Google的天才们使用一个称为“htmlfile”的ActiveX解决了在IE中的加载显示问题,并将这种方法用到了gmail+gtalk产品中。AlexRussell在“Whatelseisburrieddowninthedepth'sofGoogle'samazingJavascript?”文章中介绍了这种方法。Zeitoun网站提供的comet-iframe.tar.gz,封装了一个基于iframe和htmlfile的JavaScriptcomet对象,支持IE、MozillaFirefox浏览器,可以作为参考。(http://alex.dojotoolkit.org/?p=538)
3.长时间轮询(LongPolling)
也就是所谓的异步轮询(AsynchronousPolling),这种方式是纯服务器端推送方式和客户端拉取方式的混合。它是基于BAYEUX协议(http://svn.xantus.org/shortbus/trunk/bayeux/bayeux.html)的。这个协议遵循基于主题的发布——订阅机制。在订阅了某个频道后,客户端和服务器间的连接会保持打开状态,并保持一段事先定义好的时间(默认为45秒)。如果服务器端没有事件发生,而发生了超时,服务器端就会请求客户端进行异步重新连接。如果有事件发生,服务器端会发送数据到客户端,然后客户端重新连接。
a)服务器端会阻塞请求直到有数据传递或超时才返回。
b)客户端JavaScript响应处理函数会在处理完服务器返回的信息后,再次发出请求,重新建立连接。
c)当客户端处理接收的数据、重新建立连接时,服务器端可能有新的数据到达;这些信息会被服务器端保存直到客户端重新建立连接,客户端会一次把当前服务器端所有的信息取回。
4.FlashXMLSocket(push机制)
这种方案实现的基础是:
a)安装了flash播放器,Flash提供了XMLSocket类(Flash7.0.14以上版本)。
b)JavaScript和Flash的紧密结合:在JavaScript可以直接调用Flash程序提供的接口。
具体实现方法:在HTML页面中内嵌入一个使用了XMLSocket类的Flash程序。JavaScript通过调用此Flash程序提供的套接口与服务器端的套接口进行通信。JavaScript在收到服务器端以XML格式传送的信息后可以很容易地控制HTML页面的内容显示。
关于如何去构建JavaScript与FlashXMLSocket的Flash程序,以及如何在JavaScript里调用Flash提供的接口,我们可以参考AFLAX(AsynchronousFlashandXML)项目提供的SocketDemo以及SocketJS(请参见[http://www.aflax.org/AsynchronousFlashandXML,提供了强大的Flash、Javascript库和很多范例。])。
Javascript与Flash的紧密结合,极大增强了客户端的处理能力。从Flash播放器V7.0.19开始,已经取消了XMLSocket的端口必须大于1023的限制。Linux平台也支持FlashXMLSocket方案。但此方案的缺点在于:
a)客户端必须安装Flash播放器;
b)因为XMLSocket没有HTTP隧道功能,XMLSocket类不能自动穿过防火墙;
c)因为是使用Socket接口,需要设置一个通信端口,防火墙、代理服务器也可能对非HTTP通道端口进行限制;
d)必须使用XML格式作为消息格式,数据冗余增大。
此方案在一些网络聊天室,网络互动游戏中得到广泛使用。
5.JavaApplet(Push机制)
类似于FlashXMLSocket方式。目前已经很少使用,原因极可能是因在手机等移动终端缺少支持。
总结和建议:
如果我们想要高数据一致性和高网络性能,我们就应该选择推送方式。但是,推送会带来一些扩展性问题;服务器应用程序CPU使用率是拉取方式的7倍。根据TUD(http://swerl.tudelft.nl/twiki/pub/Main/TechnicalReports/TUD-SERG-2007-016.pdf)的测试结果,服务器性能会在350-500个用户时趋于饱和。对于更大数量的用户,服务器端需要维护大量并发的长连接。在这种应用背景下,服务器端需要考虑负载均衡和集群技术;或是在服务器端为长连接作一些改进。
使用拉取方式,要想达到完整的数据一致性以及很高的网络性能是很困难的。如果拉取的时间间隔大于数据更新的时间间隔,就会发生一些数据的遗失。而如果小于数据更新的时间间隔,网络性能就会受到影响。拉取方式只有在拉取时间间隔等同于数据更新时间间隔时,才会恰到好处。但是,为了达到那样的目标,我们就需要提前知道准确的数据更新时间间隔。然而,数据更新的时间间隔很少是静态不变并可以预知的。这使得拉取方式只有在数据是根据某种特定模式发布的情况才有用。
控制信息与数据信息使用不同的HTTP连接
使用长连接时,存在一个很常见的场景:客户端网页需要关闭,而服务器端还处在读取数据的堵塞状态,客户端需要及时通知服务器端关闭数据连接。服务器在收到关闭请求后首先要从读取数据的阻塞状态唤醒,然后释放为这个客户端分配的资源,再关闭连接。所以在设计上,我们需要使客户端的控制请求和数据请求使用不同的HTTP连接,才能使控制请求不会被阻塞。
在实现上,如果是基于iframe流方式的长连接,客户端页面需要使用两个iframe,一个是控制帧,用于往服务器端发送控制请求,控制请求能很快收到响应,不会被堵塞;一个是显示帧,用于往服务器端发送长连接请求。如果是基于AJAX的长轮询方式,客户端可以异步地发出一个XMLHttpRequest请求,通知服务器端关闭数据连接。
在客户和服务器之间保持“心跳”信息
在浏览器与服务器之间维持一个长连接会为通信带来一些不确定性:因为数据传输是随机的,客户端不知道何时服务器才有数据传送。服务器端需要确保当客户端不再工作时,释放为这个客户端分配的资源,防止内存泄漏。因此需要一种机制使双方知道大家都在正常运行。在实现上:
a)服务器端在阻塞读时会设置一个时限,超时后阻塞读调用会返回,同时发给客户端没有新数据到达的心跳信息。此时如果客户端已经关闭,服务器往通道写数据会出现异常,服务器端就会及时释放为这个客户端分配的资源。
b)如果客户端使用的是基于AJAX的长轮询方式;服务器端返回数据、关闭连接后,经过某个时限没有收到客户端的再次请求,会认为客户端不能正常工作,会释放为这个客户端分配、维护的资源。
c)当服务器处理信息出现异常情况,需要发送错误信息通知客户端,同时释放资源、关闭连接。
comet【计】:基于HTTP长连接的"服务器推"技术,是一种新的Web应用架构