}
}
}
这个类基本上是样板代码,与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 加上少量的java script 就足够了。清单5 显示了该代码。
清单5. 客户机 Comet 代码
<%@page contentType="text/html" pageEncoding="UTF-8"%>
"http://www.w3.org/TR/html4/loose.dtd">
<SCRIPT TYPE="text/java script">
function go(){
var url = "http://localhost:8484/WeatherServer/Weather"
var request = new XMLHttpRequest();
request.open("GET", url, true);
request.setRequestHeader("Content-Type","application/x-java script;");
request.onreadystatechange = function() {
if (request.readyState == 4) {
if (request.status == 200){
if (request.responseText) {
document.getElementById("forecasts").innerHTML =
request.responseText;
}
}
go();
}
};
request.send(null);
}
Rapid Fire Weather
该代码只是在用户单击Go 按钮时开始长轮询。注意,它直接使用XMLHttpRequest 对象,所以这在Internet Explorer 6 中将不能工作。您可能需要使用一个Ajax 库解决浏览器差异问题。除此之外,惟一需要注意的是回调函数,或者为请求的onreadystatechange 函数创建的闭包。该函数粘贴来自服务器的新的数据,然后重新调用go 函数。
现在,我们看过了一个简单的Comet 应用程序在Tomcat 上是什么样的。有两件与Tomcat 密切相关的事情要做:一是配置它的连接器,二是在Servlet 中实现一个特定于Tomcat 的接口。您可能想知道,将该代码 “移植” 到Jetty 有多大难度。接下来我们就来看看这个问题。
Jetty 和Comet
Jetty 服务器使用稍微不同的技术来支持Comet 的可伸缩的实现。Jetty 支持被称作continuations 的编程结构。其思想很简单。请求先被暂停,然后在将来的某个时间点再继续。规定时间到期,或者某种有意义的事件发生,都可能导致请求继续。当请求被暂停时,它的线程被释放。
可以使用Jetty 的org.mortbay.util.ajax.ContinuationSupport 类为任何HttpServletRequest 创建org.mortbay.util.ajax.Continuation 的一个实例。这种方法与Comet 有很大的不同。但是,continuations 可用于实现逻辑上等效的Comet。清单6 显示清单2 中的weather servlet “移植” 到Jetty 后的代码。
清单6. Jetty Comet servlet
public class JettyWeatherServlet extends HttpServlet {
private MessageSender messageSender = null;
private static final Integer TIMEOUT = 5 * 1000;
public void begin(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {