近日,一直在研究服务器推技术,在网上看了很多资料,也下载了很多小例子,总体来说,学到了一些东西,有些心得分享下:
在各类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 连接器。现在只需将它切换成异步版本,如下 所示。
4 | < Connector connectionTimeout = "20000" port = "8080" protocol="org.apache. |
5 | coyote.http11.Http11NioProtocol" redirectPort = "8443" /> |
然后,创建一个实现 org.apache.catalina.CometProcessor 接口的 servlet。
CometProcessor
接口要求实现
event
方法。这是用于 Comet 交互的一个生命周期方法。Tomcat 将使用不同的
CometEvent
实例调用。通过检查
CometEvent
的
eventType
,可以判断正处在生命周期的哪个阶段。当请求第一次传入时,即发生
BEGIN
事件。
READ
事件表明数据正在被发送,只有当请求为
POST
时才需要该事件。遇到
END
或
ERROR
事件时,请求终止。
具体的例子,请参照这个地址。
2. HTML5
HTML5提供了两种符合W3C标准的推送方式:SSE和WEB Socket。
先介绍一下SSE(Server-sent-envets),以PHP服务端为例,
客户访问的页面是
sse.htm
05 | var source = new EventSource( 'source.php' ); |
06 | source.onmessage = function (event){ |
07 | var text = event.data; |
08 | window.webkitNotifications.createNotification( '' , 'Alert' , text).show(); |
服务端推送消息的脚本是
source.php
01 | header( "Content-Type: text/event-stream" ); |
02 | header( "Cache-Control: no-cache" ); |
04 | mysql_connect( "localhost" , "user" , "pass" ); |
05 | mysql_select_db( "eventstream" ); |
07 | $q = mysql_query( "select textnotif from notification where read='0'" ); |
08 | $r = mysql_fetch_array( $q ); |
10 | $notif = $r [textnotif]; |
13 | echo "data: " . $notif .PHP_EOL; |
SSE实现了从服务器到客户端的单向推送消息的功能,目前浏览器Chrome,Safari都可以支持。具体还有哪些浏览器支持,可以点
这里
查看。
而WEB Socket提供了一个双向的消息通道。WebSocket 是通过 HTTP 协议的初始握手阶段然后升级到 Web Socket 协议以支持实时数据通信。WebSocket 协议设计了更为轻量级的 Header。
这里可以参考到一个使用WebSocket技术的实例。
由于WebSocket具有双向沟通的优势,因此可以实现聊天室、游戏、股票交易等等需要双向通讯的应用上。而SSE虽然只能实现从服务器向客户端的单向推送,但是它可以自动重新链接,具备EventID等优势,因此也有用武之地。