`
浮生过半
  • 浏览: 3038 次
  • 性别: Icon_minigender_1
  • 来自: 大连
社区版块
存档分类
最新评论

comet初级入门指南

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

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

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


Comet是基于 HTTP 长连接的“服务器推”技术
引用

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


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

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

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

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

罗哩叭嗦一大堆 正题来了。

先看页面代码
定义请求的服务器地址和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" />

分享到:
评论
发表评论

文章已被作者锁定,不允许评论。

相关推荐

Global site tag (gtag.js) - Google Analytics