简介: 自 JSR 315 规范(即Servlet 3.0)的草案公开发布以来,最新一代Servlet 规范的各类新特性被愈来愈多的
开发人员所关注。规范中提到的一系列高级目标:如可插拔的Web 框架、便捷开发特性、加强安全性支持等都使人期
待。但其中关注程度最高的,毫无疑问是异步Servlet。本文将详细介绍Comet 风格应用的实现方式,以及Servlet
3.0 中的异步处理特性在Comet 风格程序中的实际应用。web
概述
做为 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 v三、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 的实战演示进行讲解。异步