什么是 Comet?
解释: Alex Russell ( Dojo Toolkit 的项目 Lead )称这种基于 HTTP 长连接、无须在浏览器端安装插件的 “ 服务器推 ” 技术为 “Comet” 。
有两种实现 Comet 应用的实现模型,目前主要讨论的是基于 AJAX 的长轮询 (long-polling) 方式
例子如下:
Servlet实现类:TestComet
?
public class TestComet extends HttpServlet implements CometProcessor {
private static final long serialVersionUID = 1L;
// 发送器
private MessageSender messageSender = null;
private static final Integer TIMEOUT = 60 * 1000;
@Override
public void destroy() {
messageSender.stop();
messageSender = null;
}
@Override
public void init() throws ServletException {
System.out.println("--init-----------------");
// 初始化发送器
messageSender = new MessageSender();
Thread messageSenderThread = new Thread(messageSender, "MessageSender["
+ getServletContext().getContextPath() + "]");
messageSenderThread.setDaemon(true);
// 启动发送线程
messageSenderThread.start();
}
public void event(final CometEvent event) throws IOException,
ServletException {
System.out.println("--event-----------------");
// 获取事件对应的REQUEST 和 RESPONSE
HttpServletRequest request = event.getHttpServletRequest();
HttpServletResponse response = event.getHttpServletResponse();
if (event.getEventType() == CometEvent.EventType.BEGIN) {
request.setAttribute("org.apache.tomcat.comet.timeout", TIMEOUT);
log("Begin for session: " + request.getSession(true).getId());
// 注入RESPONSE
messageSender.setConnection(response);
Weatherman weatherman = new Weatherman(messageSender, 95118, 32408);
new Thread(weatherman).start();
} else if (event.getEventType() == CometEvent.EventType.ERROR) {
log("Error for session: " + request.getSession(true).getId());
event.close();
} else if (event.getEventType() == CometEvent.EventType.END) {
log("End for session: " + request.getSession(true).getId());
event.close();
} else if (event.getEventType() == CometEvent.EventType.READ) {
throw new UnsupportedOperationException(
"This servlet does not accept data");
}
}
}
?信息发送器:
public class MessageSender implements Runnable {
// 标志位
protected boolean running = true;
// 信息列表
protected final ArrayList<String> messages = new ArrayList<String>();
// HTTP RESPONSE
private ServletResponse connection;
// 注入HTTP RESPONSE
public 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() {
// 线程启动
log("start");
while (running) {
if (messages.size() == 0) {
try {
synchronized (messages) {
log("MessageSender wait[空闲状态,线程等待]");
// 释放锁
messages.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
// Ignore
}
}
String[] pendingMessages = null;
synchronized (messages) {
// 导出发送的信息至数组
pendingMessages = messages.toArray(new String[0]);
// 清空信息队列
messages.clear();
}
try {
if (connection == null) {
try {
synchronized (this) {
// 等待注入HTTP RESPONSE
wait();
}
} catch (InterruptedException e) {
// Ignore
e.printStackTrace();
}
}
// 输出流操作
OutputStream out = connection.getOutputStream();
for (int j = 0; j < pendingMessages.length; j++) {
final String forecast = pendingMessages[j] + "<br>";
out.write(forecast.getBytes());
out.flush();
connection.flushBuffer();
log("Writing[写入]:" + forecast);
}
} catch (IOException e) {
log("IOExeption sending message", e);
}
}
}
// 停止
public void stop() {
running = false;
}
// 日志
private void log(Object obj) {
System.out.println(obj);
}
// 日志
private void log(Object obj, Throwable e) {
System.out.println(obj);
e.printStackTrace();
}
}
?YAHOO天气预报:
public class Weatherman implements Runnable {
// 链接列表
private final List<URL> zipCodes;
// YAHOO WEATHER
private final String YAHOO_WEATHER = "http://weather.yahooapis.com/forecastrss?p=";
// 发送器
private MessageSender messageSender;
public Weatherman(MessageSender messageSender, Integer... zips) {
this.messageSender = messageSender;
zipCodes = new ArrayList<URL>(zips.length);
for (Integer zip : zips) {
try {
// 添加具体链接
zipCodes.add(new URL(YAHOO_WEATHER + zip));
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void run() {
System.out.println("Weatherman 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(10000L);
} catch (Exception e) {
e.printStackTrace();
}
i++;
}
}
// 格式转换
private String entryToHtml(SyndEntry entry) {
StringBuilder html = new StringBuilder("<h2>");
html.append(entry.getTitle());
html.append("</h2>");
html.append(entry.getDescription().getValue());
return html.toString();
}
}
?WEB.XML配置:
<servlet>
<description>TestComet</description>
<display-name>TestComet</display-name>
<servlet-name>TestComet</servlet-name>
<servlet-class>cn.test.TestComet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>TestComet</servlet-name>
<url-pattern>/TestComet</url-pattern>
</servlet-mapping>
?TOMCAT配置,NIO
<Connector connectionTimeout="20000" port="8888" protocol="org.apache.coyote.http11.Http11NioProtocol" redirectPort="8443"/>
?
?
参考文档:
Comet :基于 HTTP 长连接的 “ 服务器推 ” 技术
http://www.ibm.com/developerworks/cn/web/wa-lo-comet/
?
使用 Java 实现 Comet 风格的 Web 应用(一)
http://blog.csdn.net/ligaoyang/archive/2009/08/03/4402889.aspx
?
使用 Java 实现 Comet 风格的 Web 应用(二)
http://blog.csdn.net/ligaoyang/archive/2009/08/03/4402898.aspx
?
注:
以上例子只支持FIREFOX,不支持IE
可能会出现一些问题,例如有些包可能有冲突,需要在context.xml中添加
<Loader delegate="true" />
?
ServletException { }
什么时候调用