Comet 流(Forever Frame)
Comet 流是按照长轮询的实现思路进一步发展的产物。令长轮询将事件通知发送回客户端后不再关闭连接,而是一直保持直到超时事件发生才重新建立新的连接,这种变体我们就称为 Comet 流。客户端可以使用 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 的实战演示进行讲解。
Web 日志系统实战
Apache Log4j 是当前最主流的日志处理器,它有许多不同的 Appender 可以将日志输出到控制台、文件、数据库、Email 等等。在大部分应用中用户都不可能查看服务器的控制台或者日志文件,如果能直接在浏览器上“实时”的查看日志将会是给开发维护带来方便,在本例中将实现一个日志输出到浏览器的 Appender 实现。
清单 1. Log4j 异步 Web Appender
* 基于 AsyncContext 支持的 Appender
* @author zzm
*/
public class WebLogAppender extends WriterAppender {
/**
* 异步 Servlet 上下文队列
*/
public static final Queue<AsyncContext> ASYNC_CONTEXT_QUEUE
= new ConcurrentLinkedQueue<AsyncContext>();
/**
* AsyncContextQueue Writer
*/
private Writer writer = new AsyncContextQueueWriter(ASYNC_CONTEXT_QUEUE);
public WebLogAppender() {
setWriter(writer);
}
public WebLogAppender(Layout layout) {
this();
super.layout = layout;
}
}
上面是 Appender 类的代码模版,派生自 org.apache.log4j.WriterAppender,Log4j 默认提供的所有 Appender 都从此类继承,子类代码执行的逻辑仅仅是告知 WriterAppender 如何获取 Writer。而我们最关心的如何异步将日志信息输出至浏览器,则在 AsyncContextQueueWriter 中完成。