服务器推送技术是最近在项目中有可能要用到的一项技术,所以提前研究了一下。
考虑这样一个场景,在各类WEB应用中,如果有其他用户更改了某位用户正在关注的任何消息,则用户希望得到通知。如果一个 Web 站点显示动态数据,如股价等,那么所有用户都必须立即得到关于变更的通知。
这些场景本身属于一类称为 “服务器推送” 的问题。通常,服务器是中心实体,服务器将首先获得关于所发生的任何更改的通知,服务器负责将此类更改通知所有连接的客户端。但遗憾的是,HTTP 是客户端-服务器通信的标准协议,它是无状态的,而且在某种意义上来说,也是一种单向的协议。HTTP 场景中的所有通信都必须由客户端发起,至服务器结束,然而我们所提到的场景的需求则完全相反。对于服务器推送来说,需要由服务器发起通信,并向客户端发送数据。HTTP 协议并无相关配置,Web 站点应用程序开发人员使用独创的方法来绕过这些问题,
目前来说,服务器推送(PUSH)机制大致分为几种:
1. 短轮询
客户端以固定(或可配置)的时间间隔与服务器联系,查找是否有新的更新可用。在大多数时候,这些轮询纯粹是浪费,因为服务器没有任何更新。这种方法不是没有代价的,它有两大主要问题:
首先,这种方法非常浪费网络资源,
其次,因为轮询有一定的时间间隔,因此客户端不会获得实时的更新。
2. 长轮询
长轮询是用于更新服务器数据的另外一种方法。这种方法的理念就是客户端建立连接,服务器阻塞连接(通过使请求线程在某些条件下处于等待状态),有数据可用时,服务器将通过阻塞的连接发送数据,随后关闭连接。客户端在接收到更新后,立即重新建立连接,服务器重复上述过程,以此实现近于实时的通信。然而,长轮询具有以下缺陷:
一般的浏览器默认允许每台服务器具有两个连接。在这种情况下,一个连接始终是繁忙状态。因而,UI 只有一个连接(也就是说,能力减半)可用于为用户请求提供服务。这可能会导致某些操作的性能降低。
仍然需要打开和关闭 HTTP 连接,如果采用的是非持久连接模式(keepAlive=false),那么这种方法的代价可能极高。
这种方法近于实时,但并非真正的实时。(当然,某些外部因素总是不可控的,比如网络延时,在任何方法中都会存在这些因素。)
3. 流传送
流通道(streaming channel)与长轮询大致相同,差别在于服务器不会关闭响应流。而是特意保持其处于打开状态,使浏览器认为还有更多数据即将到来。但是,流通道也有着自己的缺陷:
最大的问题就是数据刷新(flushing)。
如果发现套接字将打开较长的时间,某些浏览器实现可能会自行决定关闭套接字。在这种情况下,通道需要重新建立。
通常,第一个问题可通过为每个流响应附加垃圾有效载荷来解决,使响应数据足以填满缓冲区。第二个问题可通过 “保持活动” 或按固定间隔 “同步” 消息来欺瞒浏览器,使浏览器认为数据是以较慢的速率传入的。
下面介绍一些常用的实现推送的技术。
1. Comet
Comet 有时也称反向 Ajax 或服务器端推技术(server-side push)。其思想很简单:将数据直接从服务器推到浏览器,而不必等到浏览器请求数据。听起来简单,但是如果熟悉 Web 应用程序,尤其是 HTTP 协议,那么您就会知道,这绝不简单。实现 Comet 风格的 Web 应用程序,同时保证在浏览器和服务器上的可伸缩性,这只是在最近几年才成为可能。
对于 Apache Tomcat,若要使用 Comet,主要需要做两件事。首先,需要对 Tomcat 的配置文件 server.xml 稍作修改。默认情况下启用的是更典型的同步 IO 连接器。现在只需将它切换成异步版本,如下 所示。
<!-- This is the usual Connector, comment it out and add the NIO one -->
<!-- Connector URIEncoding="utf-8" connectionTimeout="20000" port="8084"
protocol="HTTP/1.1" redirectPort="8443"/ -->
<Connector connectionTimeout="20000" port="8080" protocol="org.apache.
coyote.http11.Http11NioProtocol" redirectPort="8443"/>
然后,创建一个实现 org.apache.catalina.CometProcessor 接口的 servlet。
CometProcessor
接口要求实现 event
方法。这是用于 Comet 交互的一个生命周期方法。Tomcat 将使用不同的 CometEvent
实例调用。通过检查 CometEvent
的 eventType
,可以判断正处在生命周期的哪个阶段。当请求第一次传入时,即发生 BEGIN
事件。READ
事件表明数据正在被发送,只有当请求为 POST
时才需要该事件。遇到 END
或 ERROR
事件时,请求终止。
具体的例子,请参照这个地址。
而目前常用的Comet框架有:
CometD:是一个Dojo Fondation的项目,它提供了 Bayeux protocol 的javascript、java、perl、python及其他语言的实现。在CometD的站点里同时提供了Sun,IBM,BEA等公司的Comet的实现的产品的链接。
2. HTML5
HTML5提供了两种符合W3C标准的推送方式:SSE和Web Socket。
先介绍一下SSE(Server-sent-envets),以PHP服务端为例,
客户访问的页面是
sse.htm
<!DOCTYPE html>
<html>
<body>
<script>
var source = new EventSource('source.php');
source.onmessage = function(event){
var text = event.data;
window.webkitNotifications.createNotification('', 'Alert', text).show();
}
</script>
</body>
</html>
服务端推送消息的脚本是
source.php
header("Content-Type: text/event-stream");
header("Cache-Control: no-cache");
mysql_connect("localhost", "user", "pass");
mysql_select_db("eventstream");
$q = mysql_query("select textnotif from notification where read='0'");
$r = mysql_fetch_array($q);
$notif = $r[textnotif];
if($notif != ""){
echo "data: ".$notif.PHP_EOL;
}
SSE实现了从服务器到客户端的单向推送消息的功能,目前浏览器Chrome,Safari都可以支持。具体还有哪些浏览器支持,可以点这里查看。
而WEB Socket提供了一个双向的消息通道。WebSocket 是通过 HTTP 协议的初始握手阶段然后升级到 Web Socket 协议以支持实时数据通信。WebSocket 协议设计了更为轻量级的 Header。
这里可以参考到一个使用WebSocket技术的实例。而目前网上关于WebSocket的内容还是比较多的。
由于WebSocket具有双向沟通的优势,因此可以实现聊天室、游戏、股票交易等等需要双向通讯的应用上。而SSE虽然只能实现从服务器向客户端的单向推送,但是它可以自动重新链接,具备EventID等优势,因此也有用武之地。
而另外一个可作参考的轻量级的服务器推送框架是Pushlets,它提供了从HTTP Push到DHTML,以及Pushlets框架的具体实现实例。
参考: