comet学习(一)异步Servlet 与Comet 风格应用程序

简介: 自 JSR 315 规范(即Servlet 3.0)的草案公开发布以来,最新一代Servlet 规范的各种新特性被越来越多的
开发人员所关注。规范中提到的一系列高级目标:如可插拔的Web 框架、便捷开发特性、增强安全性支持等都令人期
待。但其中关注程度最高的,毫无疑问是异步Servlet。本文将详细介绍Comet 风格应用的实现方式,以及Servlet
3.0 中的异步处理特性在Comet 风格程序中的实际应用。

 

概述
作为 Java EE 6 体系中重要成员的JSR 315 规范,将Servlet API 最新的版本从2.5 提升到了3.0,这是近10 年来
Servlet 版本号最大的一次升级,此次升级中引入了若干项令开发人员兴奋的特性,如:
   1. 可插拔的Web 架构(Web framework pluggability)。
   2. 通过Annotations 代替传统web.xml 配置文件的EOD 易于开发特性(ease of development)。
   3. Serlvet 异步处理支持。
   4. 安全性提升,如Http Only Cookies、login/logout 机制。
   5. 其它改进,如文件上传的直接支持等。
    其中,在开源社区中讨论得最多的就是Servlet 异步处理的支持,所谓Servlet 异步处理,包括了非阻塞的输入/输
出、异步事件通知、延迟request 处理以及延迟response 输出等几种特性。这些特性大多并非JSR 315 规范首次提
出,譬如非阻塞输入/输出,在Tomcat 6.0 中就提供了Advanced NIO 技术以便一个Servlet 线程能处理多个Http
Request,Jetty、GlassFish 也曾经有过类似的支持。但是使用这些Web 容器提供的高级特性时,因为现有的
Servlet API 没有对这类应用的支持,所以都必须引入一些Web 容器专有的类、接口或者Annotations,导致使用了这
部分高级特性,就必须与特定的容器耦合在一起,这对很多项目来说都是无法接受的。因此JSR 315 将这些特性写入
规范,提供统一的API 支持后,这类异步处理特性才真正具备广泛意义上的实用性,只要支持Servlet 3.0 的 Web 容
器,就可以不加修改的运行这些Web 程序。

 

 JSR 315 中的Servlet 异步处理系列特性在很多场合都有用武之地,但人们最先看到的,是它们在“服务端推”
(Server-Side Push)方式—— 也称为Comet 方式的交互模型中的价值。在JCP(Java Community Process)网
站上提出的JSR 315 规范目标列表,关于异步处理这个章节的标题就直接定为了“Async and Comet support”(异步
与Comet 支持)。

 

“Comet 技术”、“服务端推技术(Server-Side Push)”、“反向Ajax 技术”这几个名称说的是同一件事情,可能
您已经听说过其中的一项或者几项。但没听说过也没有关系,一句话就足以表达它们全部的意思:“在没有客户端请
求的情况下,服务端向客户端发送数据”。
这句话听起来很简单很好理解,但是任何一个长期从事B/S 应用程序开发的程序都清楚,这实现起来并不简单,甚至
很长一段时间内,人们认为这是并不可能的。因为这种做法完全不符合传统基于HTTP 协议的交互思想:只有基于
Socket 层次的应用才能做到Server 和Client 端双方对等通讯,而基于HTTP 的应用中,Server 只是对来自Client
的请求进行回应,不关心客户端的状态,不主动向客户端请求信息,因此Http 协议被称为无状态、单向性协议,这种
交互方式称为Request-Response 交互模型。
无状态、单向的经典Request-Response 交互模型有很多优点,譬如高效率、高可伸缩等。对于被动响应用户请求为
主的应用,像CMS、MIS、ERP 等非常适合,但是对于另外一些需要服务端主动发送的需求,像聊天室(用户不发言
的时候也需要把其它用户的发言传送回来)、日志系统(客户端没有请求,当服务端有日志输出时主动发送到客户
端)则处理起来很困难,或者说这类应用根本不适合使用经典的Request-Response 交互模型来处理。当“不适合”
与“有需求”同时存在时,人们就开始不断寻找突破这种限制的方法。

 

Comet 实现的方法
1.简单轮询
最早期的Web 应用中,主要通过JavaScript 或者Meta HTML 标签等手段,定时刷新页面来检测服务端的
变化。显然定时刷新页面服务端仍然在被动响应客户端的请求,只不过客户端的请求是连续、频繁的,让用户
看起来产生有服务端自动将信息发过来的错觉。这种方式简单易行,但缺陷也非常明显:可能大部分请求都是
无意义的,因为服务端期待的事件没有发生,实际上并没有需要发送的信息,而不得不重复的回应着页面上所
有内容给浏览器;另外就是当服务端发生变化时,并不能“实时”的返回,刷新的间隔太短,产生很大的性能
浪费,间隔太长,事件通知又可能晚于用户期望的时间到达。
当绝大部分浏览器提供了XHR(XmlHttpRequest)对象支持后,Ajax 技术出现并迅速流行,这一阶段做的轮
询就不必每次都返回都返回整个页面中所有的内容,如果服务端没有事件产生,只需要返回极少量内容的
http 报文体。Ajax 可以节省轮询传输中大量的带宽浪费,但它无法减少请求的次数,因此Ajax 实现的简单轮
询仍然有轮询的局限性,对其缺陷只能一定程度缓解,而无法达到质变。


2.长轮询(混合轮询)
长轮询与简单轮询的最大区别就是连接时间的长短:简单轮询时当页面输出完连接就关闭了,而长轮询一般会
保持30 秒乃至更长时间,当服务器上期待的事件发生,将会立刻输出事件通知到客户端,接着关闭连接,同
时建立下一个连接开始一次新的长轮询。
长轮询的实现方式优势在于当服务端期待事件发生,数据便立即返回到客户端,期间没有数据返回,再较长的
等待时间内也没有新的请求发生,这样可以让发送的请求减少很多,而事件通知的灵敏度却大幅提高到几乎是
“实时”的程度。


3.Comet 流(Forever Frame)
Comet 流是按照长轮询的实现思路进一步发展的产物。令长轮询将事件通知发送回客户端后不再关闭连接,
而是一直保持直到超时事件发生才重新建立新的连接,这种变体我们就称为Comet 流。客户端可以使用
页码,2/10
XmlHttpRequest 对象中的readyState 属性来判断是Receiving 还是Loaded。Comet 流理论上可以使用一
个链接来处理若干次服务端事件通知,更进一步节省了发送到服务端的请求次数。
无论是长轮询还是Comet 流,在服务端和客户端都需要维持一个比较长时间的连接状态,这一点在客户端不算什么太
大的负担,但是服务端是要同时对多个客户端服务的,按照经典Request-Response 交互模型,每一个请求都占用一
个Web 线程不释放的话,Web 容器的线程则会很快消耗殆尽,而这些线程大部分时间处于空闲等待的状态。这也就
是为什么Comet 风格服务非常期待异步处理的原因,希望Web 线程不需要同步的、一对一的处理客户端请求,能做
到一个Web 线程处理多个客户端请求。

实战Servlet 异步处理
当前已经有不少支持Servlet API 3.0 的 Web 容器,如GlassFish v3、Tomcat 7.0、Jetty 8.0 等,在本文撰写时,
Tomcat 7 和Jetty 8 都仍然处于测试阶段,虽然支持Servlet 3.0,但是提供的样例代码仍然是与容器耦合的NIO 实
现,GlassFish v3 提供的样例(玻璃鱼聊天室)则是完全标准的Servlet 3.0 实现,如果读者需要做找参考样例,不妨
优先查看GlassFish 的example 目录。本文后一部分会提供另外一个更具备实用性的例子“Web 日志系统”作为
Servlet API 3.0 的实战演示进行讲解。

你可能感兴趣的:(servlet)