comet初级入门指南

闲来无事 一直想把以前做的一小块comet应用写个博客记下来

当初用的时候没找到比较好的例子 一边摸索一边鼓捣

今天写下来给需要的人参阅一下。。


[b]Comet是基于 HTTP 长连接的“服务器推”技术[/b]
[quote]
服务器推”是一种很早就存在的技术,以前在实现上主要是通过客户端的套接口,或是服务器端的远程调用。因为浏览器技术的发展比较缓慢,没有为“服务器推”的实现提供很好的支持,在纯浏览器的应用中很难有一个完善的方案去实现“服务器推”并用于商业程序。最近几年,因为 AJAX 技术的普及,以及把 IFrame 嵌在“htmlfile“的 ActiveX 组件中可以解决 IE 的加载显示问题,一些受欢迎的应用如 meebo,gmail+gtalk 在实现中使用了这些新技术;同时“服务器推”在现实应用中确实存在很多需求。因为这些原因,基于纯浏览器的“服务器推”技术开始受到较多关注,Alex Russell(Dojo Toolkit 的项目 Lead)称这种基于 HTTP 长连接、无须在浏览器端安装插件的“服务器推”技术为“Comet”。目前已经出现了一些成熟的 Comet 应用以及各种开源框架;一些 Web 服务器如 Jetty 也在为支持大量并发的长连接进行了很多改进。[/quote]

一般我是极其反感看此类的简介,因为你看了半天你根本不知道他在讲什么,一堆废话,一堆术语和关键字。

[size=medium]大白话来讲comet就是抓住HTTP请求不释放,攥在手里,等你想要释放的时候在释放。所以就有了推的感觉。

某人给你打电话,你接起电话寒暄一阵挂断,就完成了此次事件。但是过了2分钟你想起有个事没说,你却找不到他了(没有来电显示,对方使用公共匿名电话拨打)。为了避免这种情况发生,就出现了轮训,何为轮训就是使用匿名公共电话的人一分钟给你打一次电话,以防你有事忘了说。这样在现实中就完蛋了,啥也不用干,一天就打电话得了。
换到我们互联网应用,因为他是机器,没有状态和感情,不用考虑他感受,但是这么做长久的轮训也会造成客户端浏览器假死,而被轮训的,或者说接电话的就会累死。他同时要准备接听无数个电话,迫使他做出开辟一个新的房间,弄了个总机,分成无数个分机同时准备接电话。这就是蛋疼的轮训。

而comet的意思就是,你给我打电话,OK 我接起来,一直不挂掉,没话就不说,想起来什么什么时候说,当然这么做也会有压力。。。电话费啊(服务端和客户端一直占用,不断开。。。)
[/size]
罗哩叭嗦一大堆 正题来了。

先看页面代码
定义请求的服务器地址和servlet 添加上你的用户ID为后面推送做准备
这段代码是在哪找的忘了。。。识别各种浏览器添加iframe并开启事件。
木有什么好说明的。

var server = '<%=basePath%>SiteInfo?userId=<%=session.getAttribute("userId")%>';

var comet = {
connection : false,
iframediv : false,

initialize: function() {
if (navigator.appVersion.indexOf("MSIE") != -1) {
comet.connection = new ActiveXObject("htmlfile");
comet.connection.open();
comet.connection.write("<html>");
comet.connection.write("<script>document.domain = '"+document.domain+"'");
comet.connection.write("</html>");
comet.connection.close();
comet.iframediv = comet.connection.createElement("div");
comet.connection.appendChild(comet.iframediv);
comet.connection.parentWindow.comet = comet;
comet.iframediv.innerHTML = "<iframe id='comet_iframe' src='"+server+"'></iframe>";

} else if (navigator.appVersion.indexOf("KHTML") != -1) {
comet.connection = document.createElement('iframe');
comet.connection.setAttribute('id', 'comet_iframe');
comet.connection.setAttribute('src', server);
with (comet.connection.style) {
position = "absolute";
left = top = "-100px";
height = width = "1px";
visibility = "hidden";
}
document.body.appendChild(comet.connection);

} else {
comet.connection = document.createElement('iframe');
comet.connection.setAttribute('id', 'comet_iframe');
with (comet.connection.style) {
left = top = "-100px";
height = width = "1px";
visibility = "hidden";
display = 'none';
}
comet.iframediv = document.createElement('iframe');
comet.iframediv.setAttribute('src', server);
comet.connection.appendChild(comet.iframediv);
document.body.appendChild(comet.connection);
}
},
//添加私人消息
//这里是回调方法
privateMessage: function(data){
// alert("有新消息!");
$.messager.anim('show',1000);
$.messager.show(0,'<a href="Site_listReceive.action">您有'+data+'条新短信!</a>');
},

//退出
// onUnload: function() {
// if (comet.connection) {
// comet.connection = false;
//}
//}
}//comet end

<%}%>
if (window.addEventListener) {
window.addEventListener("load", comet.initialize, false);
// window.addEventListener("unload", comet.onUnload, false);
} else if (window.attachEvent) {
window.attachEvent("onload", comet.initialize);
// window.attachEvent("onunload", comet.onUnload);
}



下面是servlet的服务端代码

注释写的不是很全面 代码应该很容易懂的

package com.gmako.web.comet;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletResponse;
import org.apache.catalina.CometEvent;
import org.apache.catalina.CometProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import com.gmako.entity.UserInfo;
import com.gmako.service.ISiteInformationService;
import com.gmako.service.impl.SiteInformationServiceImpl;

public class SiteInfoServlet extends HttpServlet implements CometProcessor {
private static final long serialVersionUID = -3667180332947986301L;
private static MessageSender messageSender = null;
// <用户,长连接>
//声明两个MAP 用来存储response和request
protected static Map<String, HttpServletResponse> connections = new HashMap<String, HttpServletResponse>();
protected static Map<String, HttpServletRequest> requests = new HashMap<String, HttpServletRequest>();
private static final Integer TIMEOUT = 60 * 1000;

@Override
public void destroy() {
messageSender.stop();
messageSender = null;
}

@Override
public void init() throws ServletException {
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 {
HttpServletRequest request = event.getHttpServletRequest();//获取请求响应
HttpServletResponse response = event.getHttpServletResponse();
String userId = (String) request.getParameter("userId");
if(userId==null||"".equals(userId)){ //判断用户
return;
}
if (event.getEventType() == CometEvent.EventType.BEGIN) {//获取事件
event.setTimeout(Integer.MAX_VALUE);//设置过期时间
log("Begin for session: " + request.getSession(true).getId()+"userId是:"+userId);
PrintWriter writer = response.getWriter();
writer
.println("<!doctype html public \"-//w3c//dtd html 4.0 transitional//en\">");
writer
.println("<html><head><script type=\"text/javascript\">var comet = window.parent.comet;</script></head><body>");
writer.println("<script type=\"text/javascript\">");
writer.println("var comet = window.parent.comet;");
writer.println("</script>");
writer.flush();

// for chrome
if (request.getHeader("User-Agent").contains("KHTML")) {
for (int i = 0; i < 100; i++) {
writer.print("<input type=hidden name=none value=none>");
}
writer.flush();
}

System.out.println("链接的IP是:"+request.getLocalAddr());
System.out.println("链接的IP是:"+request.getHeaderNames());
if (userId != null||!("".equals(userId))) { //讲response和request放入集合中 以用户ID为key
synchronized (connections) {
connections.put(userId, response);
}
synchronized (requests) {
requests.put(userId + "", request);
}
}

} else if (event.getEventType() == CometEvent.EventType.ERROR) {
log("Error for session: " + request.getSession(true).getId()+"userId是:"+userId+",非正常断开!");
if (userId != null) {
synchronized (connections) {
connections.remove(userId);
}
synchronized (requests) {

requests.remove(userId);
}
}
event.close();
} else if (event.getEventType() == CometEvent.EventType.END) {
log("End for session: " + request.getSession(true).getId()+"userId是:"+userId+",正常断开!");
if (userId != null) {
synchronized (connections) {
connections.remove(userId);
}
synchronized (requests) {
requests.remove(userId);
}
}
event.close();
}
}

public static void send(int userId) {
System.out.println("传过来的userId是" + userId);
if(messageSender != null){
messageSender.send(userId + ""); //调用send方法
}
}

public void start() {

}

private class MessageSender implements Runnable {

protected boolean running = true;
protected final ArrayList<String> messages = new ArrayList<String>();

public void stop() {
running = false;
}

/**
* Add message for sending.
添加要发送的消息到消息队列内
*/
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();
}
if (connections == null) { //判断response集合是否有等待的
try {
synchronized (this) {
wait();
}
} catch (InterruptedException e) {
// Ignore
}
}
if (requests == null) { //判断request集合是否有等待的
try {
synchronized (this) {
wait();
}
} catch (InterruptedException e) {
// Ignore
}
}
HttpServletResponse res = null;
PrintWriter writer = null;
for (int j = 0; j < pendingMessages.length; j++) {
System.out.println(requests.size());
System.out.println("消息队列里面的值是:"
+ pendingMessages[j]);
if (connections.get(pendingMessages[j]) != null) { //以用户ID为key取出该用户的response 并直接打印
res = connections //由于前段为AJAX请求 所以可以直接通过该回调获取打印内容
.get(pendingMessages[j]);
try {
writer = res.getWriter();
} catch (IOException e) {
e.printStackTrace();
}

System.out.println("我发了消息!");
writer
.print("<script type=\"text/javascript\">");
writer.println("comet.privateMessage('"
+ 1 + "');");
writer.print("</script>");
writer.flush();
log("Writing:" + "发送了一条推送给"
+ pendingMessages[j].toString());
writer.flush();
// writer.close();
}
}
log("Closing connection");

}

}
}


在附件内我将完整的代码传上来包括页面的JS和后台的几个class。。。如果用的话直接看看没什么问题。

同样 你需要修改tomcat 目录下conf文件夹下的server.xml

    <Connector port="8088" 
connectionTimeout="20000" protocol="org.apache.coyote.http11.Http11NioProtocol"
redirectPort="8443" useBodyEncodingForURI="true" URIEncoding="utf-8" />
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值