论坛首页 Java企业应用论坛

Comet基于iframe的服务器推送(Server Push)例子

浏览 19196 次
精华帖 (3) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (1)
作者 正文
   发表时间:2010-12-14  
    服务器推送技术(Server Push)是最近Web技术中最热门的一个流行术语,它的别名叫Comet(彗星)。它是继AJAX之后又一个倍受追捧的Web技术。Comet有时也称反向 Ajax 或服务器端推技术(server-side push)。其思想很简单:将数据直接从服务器推到浏览器,而不必等到浏览器请求数据。听起来简单,但是如果熟悉 Web 应用 程序,尤其是 HTTP 协议,那么您就会知道,这绝不简单。实现 Comet 风格的 Web 应用程序,同时保证在浏览器和服务器上的可伸缩性,这只是在最近几年才成为可能。目前一些主流网站都有类似的原理,例如:webQQ、开心网、白社会等等,它们中消息动态都是采用类似的技术,也许具体实现方式不一样。目前大概有三种实现方式:基于长轮询(long polling)、基于iframe“、基于流(stream)三种实现comet的方式。
     下面是在Tomcat6.x的基础上实现基于iframe的comet聊天室:

    index.jsp:聊天室页面
    ChatServlet.java:实现了长连接的Servlet
    MessageServlet.java:消息接受Servert

     仅仅是一个Demo,可以从index.jsp?name=yourNickName进入,实现了上线,下线通知,群发,消息功能,性能还没有测试...
     目前能够正常在IE,FireFox和Chrome下正常运行,但在Chrome下会一直加载不完,不知道有没有办法解决?还有不知道能不能在此基础之上搞一个基于iframe的Comet工具,包括前台js和后台java.

      首先修改Tomcat配置文件
      修改server.xml中<Connector connectionTimeout="20000" port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol" redirectPort="8443"/>,底层用NIO实现的Http连接器

index.jsp
     <%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script type="text/javascript" src="jquery-1.4.2.min.js"></script>
<link rel="stylesheet" href="960.css" />
<title>Comet Chat Demo</title>
<script type="text/javascript">
var server = 'http://localhost:8080/CometDemo/ChatComet?name=<%=request.getParameter("name")%>';

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);
		}
	},

	//添加用户
	newUser:function(data){
		var list = document.getElementById('userList');
		var li = document.createElement('li');
		li.setAttribute("id","u1"+data);
		li.innerHTML = data;
		list.appendChild(li);

		var user = document.getElementById('user');
		var option = document.createElement('option');
		option.setAttribute("id","u2"+data);
		option.innerHTML = data;
		user.appendChild(option);
	},

	//删除用户
	deleteUser:function(data){
		$('#u1'+data).remove();
		$('#u2'+data).remove();
	},

	//添加公共消息
	newMessage:function(data){
		var list = document.getElementById('messageList');
		var li = document.createElement('li');
		li.innerHTML = data;

		list.appendChild(li);
	},

	//添加私人消息
	privateMessage:function(data){
		var list = document.getElementById('privateMessage');
		var li = document.createElement('li');
		li.innerHTML = data;

		list.appendChild(li);
	},

	//退出
	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);
}
</script>
</head>
<body>
<script type="text/javascript">
function sendAll(){
	var list = document.getElementById('privateMessage');
	var li = document.createElement('li');
	li.innerHTML = "I said to "+$("#user").val()+": " + $("#message").val();
	list.appendChild(li);
	
	 $.ajax({
		 type: "POST",
		 url: "MessageServlet", 
		 data: "message="+$("#message").val()+"&user="+$("#user").val()+"&from=<%=request.getParameter("name")%>"
	 });
}
</script>
<div class="container_12">
<div class="grid_10">
	<div>公共聊天</div>
	<div id="messageList" style="height:250px;overflow:scroll;border:solid 1px black;">
	
	</div>
	<br/>
	<div>个人聊天</div>
	<div id="privateMessage" style="height:150px;overflow:scroll;border:solid 1px black;">
	
	</div>
	<br/>
	<div>
		<select id="user" style="width:100px;overflow:scroll;">
			<option value="all">All</option>
		</select>
		<input type="text" id="message" size="40"></input>
		<input type="button" value="发言" onclick="sendAll()">
	</div>
</div>
<div class="grid_2">
	<h3>用户列表</h3>
	<ol id="userList">
	
	</ol>
</div>
</div>
</body>
</html>


ChatServlet.java
package demo;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.catalina.CometEvent;
import org.apache.catalina.CometProcessor;

/**
 * 
 * @author Aries Zhao
 * 
 */
public class ChatServlet extends HttpServlet implements CometProcessor {

	private static final long serialVersionUID = -3667180332947986301L;

	// <用户,长连接>
	protected static Map<String, HttpServletResponse> connections = new HashMap<String, HttpServletResponse>();

	// 消息推送线程
	protected static MessageSender messageSender = null;

	public void init() throws ServletException {
		// 启动消息推送线程
		messageSender = new MessageSender();
		Thread messageSenderThread = new Thread(messageSender, "MessageSender["
				+ getServletContext().getContextPath() + "]");
		messageSenderThread.setDaemon(true);
		messageSenderThread.start();
	}

	public void destroy() {
		connections.clear();
		messageSender.stop();
		messageSender = null;
	}

	public void event(CometEvent event) throws IOException, ServletException {
		HttpServletRequest request = event.getHttpServletRequest();
		HttpServletResponse response = event.getHttpServletResponse();

		// 昵称
		String name = request.getParameter("name");
		if (name == null) {
			return;
		}

		if (event.getEventType() == CometEvent.EventType.BEGIN) {
			// Http连接空闲超时
			event.setTimeout(Integer.MAX_VALUE);
			log("Begin for session: " + request.getSession(true).getId());

			// 创建Comet Iframe
			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();
			}

			// 欢迎信息
			writer.print("<script type=\"text/javascript\">");
			writer.println("comet.newMessage('Hello " + name + ", Welcome!');");
			writer.print("</script>");
			writer.flush();

			// 通知其他用户有新用户登陆
			if (!connections.containsKey(name)) {
				messageSender.login(name);
			}

			// 推送已经登陆的用户信息
			for (String user : connections.keySet()) {
				if (!user.equals(name)) {
					writer.print("<script type=\"text/javascript\">");
					writer.println("comet.newUser('" + user + "');");
					writer.print("</script>");
				}
			}
			writer.flush();

			synchronized (connections) {
				connections.put(name, response);
			}
		} else if (event.getEventType() == CometEvent.EventType.ERROR) {
			log("Error for session: " + request.getSession(true).getId());
			synchronized (connections) {
				connections.remove(name);
			}
			event.close();
		} else if (event.getEventType() == CometEvent.EventType.END) {
			log("End for session: " + request.getSession(true).getId());
			messageSender.logout(name);
			synchronized (connections) {
				connections.remove(name);
			}
			PrintWriter writer = response.getWriter();
			writer.println("</body></html>");
			event.close();
		} else if (event.getEventType() == CometEvent.EventType.READ) {
			InputStream is = request.getInputStream();
			byte[] buf = new byte[512];
			do {
				int n = is.read(buf); // can throw an IOException
				if (n > 0) {
					log("Read " + n + " bytes: " + new String(buf, 0, n)
							+ " for session: "
							+ request.getSession(true).getId());
				} else if (n < 0) {
					return;
				}
			} while (is.available() > 0);
		}
	}

	// 发送消息给所有人
	public static void send(String message) {
		messageSender.send("*", message);
	}

	// 向某个连接发送消息
	public static void send(String name, String message) {
		messageSender.send(name, message);
	}

	public class MessageSender implements Runnable {

		protected boolean running = true;
		protected Map<String, String> messages = new HashMap<String, String>();

		public MessageSender() {

		}

		public void stop() {
			running = false;
		}

		// 新用户登陆
		public void login(String name) {
			synchronized (messages) {
				messages.put("Login", name);
				messages.notify();
			}
		}

		// 用户下线
		public void logout(String name) {
			synchronized (messages) {
				messages.put("Logout", name);
				messages.notify();
			}
		}

		// 发送消息
		public void send(String user, String message) {
			synchronized (messages) {
				messages.put(user, message);
				messages.notify();
			}
		}

		public void run() {
			while (running) {
				if (messages.size() == 0) {
					try {
						synchronized (messages) {
							messages.wait();
						}
					} catch (InterruptedException e) {
						// Ignore
					}
				}

				synchronized (connections) {
					synchronized (messages) {
						// 推送消息队列中的消息
						for (Entry<String, String> message : messages
								.entrySet()) {
							if (message.getKey().equals("Login")) {// 新用户登陆
								log(message.getValue() + " Login");
								for (HttpServletResponse response : connections
										.values()) {
									try {
										PrintWriter writer = response
												.getWriter();
										writer
												.print("<script type=\"text/javascript\">");
										writer
												.println("comet.newMessage('Welcome "
														+ message.getValue()
														+ " !');");
										writer.println("comet.newUser('"
												+ message.getValue() + "');");
										writer.print("</script>");
										writer.flush();
									} catch (IOException e) {
										log("IOExeption execute command", e);
									}
								}
							} else if ("Logout".equals(message.getKey())) {// 用户退出
								log(message.getValue() + " Logout");
								for (HttpServletResponse response : connections
										.values()) {
									try {
										PrintWriter writer = response
												.getWriter();
										writer
												.print("<script type=\"text/javascript\">");
										writer.println("comet.newMessage('88, "
												+ message.getValue() + "');");
										writer.println("comet.deleteUser('"
												+ message.getValue() + "');");
										writer.print("</script>");
										writer.flush();
									} catch (IOException e) {
										log("IOExeption execute command", e);
									}
								}
							} else if ("*".equals(message.getKey())) {// 群发消息
								log("Send message: " + message.getValue()
										+ " to everyone.");
								for (HttpServletResponse response : connections
										.values()) {
									try {
										PrintWriter writer = response
												.getWriter();
										writer
												.print("<script type=\"text/javascript\">");
										writer.println("comet.newMessage('"
												+ message.getValue() + "');");
										writer.print("</script>");
										writer.flush();
									} catch (IOException e) {
										log("IOExeption execute command", e);
									}
								}
							} else {// 向某人发信息
								try {
									HttpServletResponse response = connections
											.get(message.getKey());
									PrintWriter writer = response.getWriter();
									writer
											.print("<script type=\"text/javascript\">");
									writer.println("comet.privateMessage('"
											+ message.getValue() + "');");
									writer.print("</script>");
									writer.flush();
								} catch (IOException e) {
									log("IOExeption sending message", e);
								}
							}
							// 从消息队列中删除消息
							messages.remove(message.getKey());
						}
					}
				}
			}
		}
	}
}
    
   发表时间:2010-12-22  
项目中用了amq的推 感觉还不错
0 请登录后投票
   发表时间:2011-01-21  
用http://localhost:8080/CometDemo/index.jsp?name=comet1登录。
0 请登录后投票
   发表时间:2011-02-25  
部署上去试了试,能运行。很好的demo!
0 请登录后投票
   发表时间:2011-02-26  
我的为什么 不能正常运行
tomcat是6.0.26,也改了配置
还有什么需要注意的细节吗?
0 请登录后投票
   发表时间:2011-02-26  
感觉比较靠谱的方案客户端使用flash socket和服务端建立一个长连接。服务端可以用mina去实现,这样并发能力和处理能力很强的
0 请登录后投票
   发表时间:2011-02-26  
melin 写道
感觉比较靠谱的方案客户端使用flash socket和服务端建立一个长连接。服务端可以用mina去实现,这样并发能力和处理能力很强的

就等html5 websocket, web dev们就解脱了
0 请登录后投票
   发表时间:2011-02-28  
之前项目中的聊天室也想用这种方法,但无奈IE浏览器 ,尤其是老版本的浏览器不给力,只能换成了flex+socket
0 请登录后投票
   发表时间:2011-07-02  
不错的例子,中文乱码,orz~~~
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics