CometProcessor 接口要求实现 event 方法。这是用于 Comet 交互的一个生命周期方法。Tomcat 将使用不同的 CometEvent 实例调用。通过检查 CometEvent 的 eventType,可以判断正处在生命周期的哪个阶段。当请求第一次传入时,即发生 BEGIN 事件。READ 事件表明数据正在被发送,只有当请求为 POST 时才需要该事件。遇到 END 或 ERROR 事件时,请求终止。
在清单 2 的例子中,Servlet 使用一个 MessageSender 类发送数据。这个类的实例是在 servlet 的 init 方法中在其自身的线程中创建,并在 servlet 的 destroy 方法中销毁的。清单 3 显示了 MessageSender.
清单 3. MessageSender
private class MessageSender implements Runnable { protected boolean running = true; protected final ArrayList messages = new ArrayList(); private ServletResponse connection; private synchronized void setConnection(ServletResponse connection){ this.connection = connection; notify(); } public void send(String message) { synchronized (messages) { messages.add(message); log("Message added #messages=" + messages.size()); messages.notify(); } } public void run() { while (running) { if (messages.size() == 0) { try { synchronized (messages) { messages.wait(); } } catch (InterruptedException e) { // Ignore } } String[] pendingMessages = null; synchronized (messages) { pendingMessages = messages.toArray(new String[0]); messages.clear(); } try { if (connection == null){ try{ synchronized(this){ wait(); } } catch (InterruptedException e){ // Ignore } } PrintWriter writer = connection.getWriter(); for (int j = 0; j < pendingMessages.length; j++) { final String forecast = pendingMessages[j] + " "; writer.println(forecast); log("Writing:" + forecast); } writer.flush(); writer.close(); connection = null; log("Closing connection"); } catch (IOException e) { log("IOExeption sending message", e); } } } } |
这个类基本上是样板代码,与 Comet 没有直接的关系。但是,有两点要注意。这个类含有一个 ServletResponse 对象。回头看看清单 2 中的 event 方法,当事件为 BEGIN 时,response 对象被传入到 MessageSender 中。在 MessageSender 的 run 方法中,它使用 ServletResponse 将数据发送回客户机。注意,一旦发送完所有排队等待的消息后,它将关闭连接。这样就实现了长轮询。如果要实现流风格的 Comet,那么需要使连接保持开启,但是仍然刷新数据。
回头看清单 2 可以发现,其中创建了一个 Weatherman 类。正是这个类使用 MessageSender 将数据发送回客户机。这个类使用 Yahoo RSS feed 获得不同地区的天气信息,并将该信息发送到客户机。这是一个特别设计的例子,用于模拟以异步方式发送数据的数据源。清单 4 显示了它的代码。
清单 4. Weatherman
private class Weatherman implements Runnable{ private final List zipCodes; private final String YAHOO_WEATHER = "http://weather.yahooapis.com/forecastrss?p="; public Weatherman(Integer... zips) { zipCodes = new ArrayList(zips.length); for (Integer zip : zips) { try { zipCodes.add(new URL(YAHOO_WEATHER + zip)); } catch (Exception e) { // dont add it if it sucks } } } public void run() { int i = 0; while (i >= 0) { int j = i % zipCodes.size(); SyndFeedInput input = new SyndFeedInput(); try { SyndFeed feed = input.build(new InputStreamReader(zipCodes.get(j) .openStream())); SyndEntry entry = (SyndEntry) feed.getEntries().get(0); messageSender.send(entryToHtml(entry)); Thread.sleep(30000L); } catch (Exception e) { // just eat it, eat it } i++; } } private String entryToHtml(SyndEntry entry){ StringBuilder html = new StringBuilder(" "); html.append(entry.getTitle()); html.append(" "); html.append(entry.getDescription().getValue()); return html.toString(); } } |
这个类使用 Project Rome 库解析来自 Yahoo Weather 的 RSS feed.如果需要生成或使用 RSS 或 Atom feed,这是一个非常有用的库。此外,这个代码中只有一个地方值得注意,那就是它产生另一个线程,用于每过 30 秒钟发送一次天气数据。最后,我们再看一个地方:使用该 Servlet 的客户机代码。在这种情况下,一个简单的 JSP 加上少量的 JavaScript 就足够了。清单 5 显示了该代码。
清单 5. 客户机 Comet 代码
<%@page contentType="text/html" pageEncoding="UTF-8"%>
|
该代码只是在用户单击 Go 按钮时开始长轮询。注意,它直接使用 XMLHttpRequest 对象,所以这在 Internet Explorer 6 中将不能工作。您可能需要使用一个 Ajax 库解决浏览器差异问题。除此之外,惟一需要注意的是回调函数,或者为请求的 onreadystatechange 函数创建的闭包。该函数粘贴来自服务器的新的数据,然后重新调用 go 函数。
现在,我们看过了一个简单的 Comet 应用程序在 Tomcat 上是什么样的。有两件与 Tomcat 密切相关的事情要做:一是配置它的连接器,二是在 Servlet 中实现一个特定于 Tomcat 的接口。您可能想知道,将该代码 “移植” 到 Jetty 有多大难度。接下来我们就来看看这个问题。
[1] [2] [3] 下一页