服务器推送实现方案
很多应用譬如监控、即时通信、即时报价系统都需要将后台发生的变化实时传送到客户端而无须客户端不停地刷新、发送请求。此时有两种技术可以将通信引入基于浏览器的应用之中:Bayeux(又称CometD)和WebSockets。
CometD框架是Bayeux协议的实现,可以使得服务器端和客户端在不可靠的网络上进行多通道异步通信。该实现用到了多种语言(JavaScript、Java、Perl等等),但是主要还是基于浏览器的AJAX应用。Bayeux的优势在于,它可以运行在任何支持AJAX的浏览器上,在现有HTTP通信机制下,就能够让浏览器支持异步后台的更新,比如类似于Google邮件的新邮件通知的新信息送达。事实上,同样的协议还可以用于使用其他语言在不可靠的网络上连接设备进行通信(比如移动设备)。
WebSockets是一个标准草案,这项草案由Google、Apple和其他进行HTML 5标准化的WhatWG工作组成员所资助。因此,支持HTML 5的浏览器(Chrome或者Safari)已经开始支持内建的WebSocket协议。
这两个协议的目标都是让基于Web的AJAX应用能通过异步消息或者基于Socket的连接进行通信,而不是在一个现有应用之上再搭建一个自己实现的通信层。这使得在设计应用的时候,可以只关注于组件部分,而把消息传递给通信层去递送。另外,这两个协议都能够建立长连接,事件可以通过长连接异步地递送给应用。这没什么新鲜的:HTTP 1.1就支持连接管道(可以在每个请求之后保持连接,并可以在第一个请求得到处理之后再发送多个请求);而像IMAP等一些协议则支持IDLE命令,把连接置成休眠状态,这样在休眠的连接上就不再有数据传输,但是服务器依然可以随时推送新消息。其实在Bayeux或者WebSockets之前,通过HTTP进行持久通信的机制一般被称为“HTTP推送”。
然而,长连接并不是没有任何问题。一条连接如果长时间没有数据通信的话,会被认为已经死掉,并在接下来的某个时间点被终止。为了解决这个问题,IMAP的IDLE建议客户端每29分钟发送一个IDLE命令来避免断连。而HTTP代理则会决定连接是处于闲置状态并丢弃连接,而不去管客户端和服务器端是否已经保持了一条长连接。
资源限制也是一个问题。通常,浏览器都会限制对单个服务器的并发HTTP连接的数量,以避免对该服务器(或者网络连接)造成过大的压力。浏览器一般会将这个并发连接数量限制在每次2到4个。
Bayeux和WebSockets都试图避免资源限制问题,使用回退机制来实现长轮询(比如Bayeux),或者切换到其他非HTTP协议之上。那么,这些程序库的使用者就不需要再担心浏览器或者基础架构的限制问题。
Erlang之父Joe Armstrong认为,WebSockets将会干掉Comet:
聊天室的典型用例是这样的,你进入了一个聊天室,标识出你的存在,这个标识会一直保持到你显式地退出聊天室。而在Web聊天的情况下,你可以收发聊天消息,直到你关闭浏览器或者转至其他网页。不幸的是,即便是这么简单的用例都无法通过WebSocket实现,因为在该协议中,连接有一个闲置超时。
为了保持存在的状态,聊天应用要发送“连接保持(Keep Alive)”的消息给WebSocket,来避免该连接因为闲置超时而关闭。然而,应用并不知道这个闲置超时究竟是多少,因此它只能随便选一个间隔周期(比如30秒)来发送该消息,这和长轮询要做的事似乎就多少有些类似了。
通过onClose处理、连接保持、消息队列、超时和重试,我们最终实现了一个可以在用户停留在网页上的时候保持其存在状态的聊天室。但是遗憾的是这个聊天室依然还没有完,因为它还需要处理错误和非暂时性故障。
即便如此,现在还是开始cometd之路,毕竟就现在而言,cometd在我们项目中实施WebSocket要方便。
2.实战
本文实现了一个server 定时push一个随机数到client端,此例查询了大量的实例,经过笔者验证,在IE和FireFox下都可以运行。
运行此例,需要下面配置:
1) Tomcat要求支持Comet,必须使用NIO或者APR的方式,因此,修改Tomcat/conf/server.xml
<Connector port="9000" executor="tomcatThreadPool" protocol="org.apache.coyote.http11.Http11NioProtocol"
connectionTimeout="20000" maxThreads="150"
redirectPort="8443" maxKeepAliveRequests="1"/>
2)Tomcat版本要求6.0.16以上,最好使用最新的版本
3) 修改附件中源码里面的web目录下的comet-compatible.jsp文件,把里面涉及IP和端口的url,改成你机器的ip地址和端口
4) 测试运行:http://ip:port/contextName/comet-compatible.jsp
请耐心等数秒钟,随机数开始从server push到client端了
源码见附件,附件的URL如下:
http://dl.javaeye.com/topics/download/ee28df9d-d838-3572-be73-dbf06c11080f