Jetty+Dojo+Tomcat的Comet学习笔记-Web聊天系统【ChatRoom】

这两天写了一个简单的基于jetty comet包和dojo的web聊天系统,作为comet学习的一个小练习吧,简单介绍一下。

 

具体的编程环境的配置在上一篇文章中已经介绍,不再赘述。【Jetty + Dojo + Tomcat 的Comet学习笔记 - HelloWorld】

 

  • 服务端

服务器端主要有两个类:ChatService和ConfigurationServlet。

其中ChatService继承自BayeuxService,用来实现对publish/subscrible模式中的通道(channel)的消息进行监听的。可以取得通道(channel)中的消息,以及消息的发送者。ChatService代码如下:

package cn.ict.net.lx.comet; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; import java.util.Hashtable; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.mortbay.cometd.BayeuxService; import dojox.cometd.Bayeux; import dojox.cometd.Client; public class ChatService extends BayeuxService { // channel 和 订阅这个channel的客户端的集合之间的Map映射 ConcurrentMap<String, Set<User>> _members = new ConcurrentHashMap<String, Set<User>>(); Hashtable<User, Client> userList = new Hashtable<User, Client>(); public ChatService(Bayeux bayeux, String name) { super(bayeux, name); // TODO Auto-generated constructor stub this.subscribe("/ChatRoom", "onChannelMessge"); } public void sendPublichMessage(Map<String, Object> message){ Client sender = this.getBayeux().newClient("Server"); this.getBayeux().getChannel("/ChatRoom", false).publish(sender, message, null); } public void sendPrivateMessage(Map<String, Object> message, Client to){ this.send(to, "/ChatRoom", message, null); } /** * @decription 监听通道channel的所有消息 * @author Administrator * @param Client jonier 新加入channel的Client * @param String channel 通道名 * @param Map<String, Object> data joiner发送到channel的消息 * @param String id 消息的id * @return void */ public void onChannelMessge(Client joiner, String channel, Map<String, Object> data, String id){ // TODO: 处理消息 String msgType = (String) data.get("Type"); if (msgType != null){ // 用户登录消息 if (msgType.equalsIgnoreCase("c_login")){ data.put("Type", ""); Map<String, Object> msg = new HashMap<String, Object>(); ArrayList<String> arr = new ArrayList<String>(); Enumeration<User> users = this.userList.keys(); while(users.hasMoreElements()) { User user = users.nextElement(); msg.put("Type", "s_new_joiner"); msg.put("NewJoiner", data.get("UserName")); // 向其他客户端发送新登陆的用户信息 this.sendPrivateMessage(msg, this.userList.get(user)); arr.add(user.getUsername()); } // 将用户加入在线用户列表 User u = new User((String) data.get("UserName"), (String)data.get("Password")); u.setImageUrl((String) data.get("ImageUrl")); this.userList.put(u, joiner); arr.add(u.getUsername()); // 向这个登录的客户端发送在线用户列表 Map<String, Object> m = new HashMap<String, Object>(data); m.put("Type", "s_login_ack_sucess"); m.put("userList", arr); this.sendPrivateMessage(m, joiner); } } } } class User{ private String username; private String password; private String imageUrl; public User(String username, String password) { super(); this.username = username; this.password = password; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getImageUrl() { return imageUrl; } public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; } } 

在onChannelMessage中,通过获取消息数据中的消息字段(如:Type),来处理响应的消息。这里只处理了用户的登录的消息。用户登录时,服务器需要向其他在线客户端发送新用户登录消息,以及向新登陆的用户发送在线用户列表。

ConfigurationServlet主要是用来配置Servlet的,在这里的作用是new 一个ChatService对象实例,处理消息,代码如下:

package cn.ict.net.lx.comet; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import dojox.cometd.Bayeux; /** * Servlet implementation class for Servlet: ConfigurationServlet * */ public class ConfigurationServlet extends javax.servlet.GenericServlet implements javax.servlet.Servlet { static final long serialVersionUID = 1L; /* (non-Java-doc) * @see javax.servlet.http.HttpServlet#HttpServlet() */ public ConfigurationServlet() { super(); } /* (non-Java-doc) * @see javax.servlet.http.HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub } /* (non-Java-doc) * @see javax.servlet.http.HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) */ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub } /* (non-Javadoc) * @see javax.servlet.GenericServlet#init() */ public void init() throws ServletException { // TODO Auto-generated method stub super.init(); Bayeux bayeux = (Bayeux) this.getServletContext().getAttribute(Bayeux.DOJOX_COMETD_BAYEUX); new ChatService(bayeux, "ChatRoom"); // 可以在此创建其他的Service // 同时可以在次添加Bayeux对象的配置和扩展以及安全策略 } @Override public void service(ServletRequest arg0, ServletResponse arg1) throws ServletException, IOException { // TODO Auto-generated method stub } } 

此外,还需要在web.xml中配置ConfigurationServlet映射,web.xml如下:

<?xml version="1.0" encoding="UTF-8"?> <web-app id="WebApp_ID" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> <display-name>dojoSrc</display-name> <welcome-file-list> <welcome-file>index.html</welcome-file> <welcome-file>index.htm</welcome-file> <welcome-file>index.jsp</welcome-file> <welcome-file>default.html</welcome-file> <welcome-file>default.htm</welcome-file> <welcome-file>default.jsp</welcome-file> </welcome-file-list> <!-- ************************** ChatRoom Servlet Configuration ***************************** --> <!-- <servlet> <servlet-name>cometd</servlet-name> <servlet-class> org.mortbay.cometd.continuation.ContinuationCometdServlet </servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>cometd</servlet-name> <url-pattern>/cometd/*</url-pattern> </servlet-mapping> --> <servlet> <servlet-name>cometd</servlet-name> <servlet-class> org.mortbay.cometd.continuation.ContinuationCometdServlet </servlet-class> <!-- <init-param> <param-name>filters</param-name> <param-value>/WEB-INF/filters.json</param-value> </init-param> --> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>cometd</servlet-name> <url-pattern>/cometd/*</url-pattern> </servlet-mapping> <servlet> <description></description> <display-name>ConfigurationServlet</display-name> <servlet-name>ConfigurationServlet</servlet-name> <servlet-class> cn.ict.net.lx.comet.ConfigurationServlet </servlet-class> <load-on-startup>2</load-on-startup> </servlet> <servlet-mapping> <servlet-name>ConfigurationServlet</servlet-name> <url-pattern>/ConfigurationServlet</url-pattern> </servlet-mapping> <!-- Listenter方法和ConfigurationServlet方法二者选一 --> <!-- <listener> <listener-class> cn.ict.net.lx.comet.BayeuxStartupListener </listener-class> </listener> --> </web-app> 

 

  • 客户端(浏览器)

客户端使用dojox.cometd实现和服务器的通信,通过dojox.cometd.publish向channel中发送消息,subscribe来接收消息。具体消息格式代码中都已经写了,主要的字段有:From, To, Type, Message, ImageUrl等。客户端页面代码如下:

<!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"> <title>多人聊天室</title> <mce:script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/dojo/1.5/dojo/dojo.xd.js" mce_src="http://ajax.googleapis.com/ajax/libs/dojo/1.5/dojo/dojo.xd.js" djConfig="parseOnLoad:true"></mce:script> <mce:style type="text/css"><!-- @import "http://ajax.googleapis.com/ajax/libs/dojo/1.5/dijit/themes/tundra/tundra.css"; @import "http://ajax.googleapis.com/ajax/libs/dojo/1.5/dijit/themes/soria/soria.css"; @import "http://ajax.googleapis.com/ajax/libs/dojo/1.5/dojox/layout/resources/FloatingPane.css"; @import "http://ajax.googleapis.com/ajax/libs/dojo/1.5/dojox/layout/resources/ResizeHandle.css"; @import "http://ajax.googleapis.com/ajax/libs/dojo/1.5/dojo/resources/dojo.css"; @import "http://ajax.googleapis.com/ajax/libs/dojo/1.5/dojox/layout/resources/GridContainer.css"; @import "http://ajax.googleapis.com/ajax/libs/dojo/1.5/dojox/layout/resources/ExpandoPane.css"; --></mce:style><style type="text/css" mce_bogus="1"> @import "http://ajax.googleapis.com/ajax/libs/dojo/1.5/dijit/themes/tundra/tundra.css"; @import "http://ajax.googleapis.com/ajax/libs/dojo/1.5/dijit/themes/soria/soria.css"; @import "http://ajax.googleapis.com/ajax/libs/dojo/1.5/dojox/layout/resources/FloatingPane.css"; @import "http://ajax.googleapis.com/ajax/libs/dojo/1.5/dojox/layout/resources/ResizeHandle.css"; @import "http://ajax.googleapis.com/ajax/libs/dojo/1.5/dojo/resources/dojo.css"; @import "http://ajax.googleapis.com/ajax/libs/dojo/1.5/dojox/layout/resources/GridContainer.css"; @import "http://ajax.googleapis.com/ajax/libs/dojo/1.5/dojox/layout/resources/ExpandoPane.css"; </style> <mce:script type="text/javascript"><!-- dojo.require("dojo.parser"); dojo.require("dijit.form.Button"); dojo.require("dojox.layout.FloatingPane"); dojo.require("dijit.form.TextBox"); dojo.require("dijit.form.Textarea"); dojo.require("dijit.Dialog"); dojo.require("dojox.cometd"); dojo.require("dojox.cometd.timestamp"); dojo.require("dijit.form.SimpleTextarea"); dojo.require("dojox.layout.ContentPane"); dojo.require("dijit.layout.BorderContainer"); dojo.require("dijit.form.ValidationTextBox"); dojo.require("dijit.TitlePane"); // 消息格式 var msgObj = { From: "", To: "", Type: "", Message: "", UserName: "", // for login Password: "", // for login ImageUrl: "" }; var loginDlg = null; var nickname = ""; var userList = null; var userPane = null; var multChatPane = null; var chatWinList = []; // 打开聊天窗口 function onOpenChatWindow(title){ var div = document.createElement("div"); document.body.appendChild(div); var win = new dojox.layout.FloatingPane({ title: "与 <font color='blue' style="bolder" mce_style="bolder">"+title+"</font> 聊天中", dockable: true, maxable: true, resizable: true, style: "top:10px;left:10px;width:530px;height:520px;", }, div ); win.username = title; // 记录该窗口的聊天对象的名字 // 布局 var layout = new dijit.layout.BorderContainer( { style: "width: 500px;height:480px;", id: "chatwin_" + Math.random(), }, document.createElement("div") ); // 侧边栏 var leading = new dojox.layout.ContentPane({ content: "<table style='width:100%; height:100%'>" + "<tr><td><img style='width:120px;height:120px' src="./default.png" mce_src="default.png">" + "</td></tr>" + "<tr><td><img style='width:120px;height:120px' src="./default.png" mce_src="default.png">" + "</td></tr>" + "</table>", region: "leading", style: "width:25%" }, document.createElement("div")); // 消息显示区 var center = new dojox.layout.ContentPane({ content: "Center", region: "center", splitter: "true", }, document.createElement("div")); // 输入区 var bottom = new dojox.layout.ContentPane({ content: "Bottom", region: "bottom", splitter: "true", }, document.createElement("div")); // 消息内容面板 win.msgText = document.createElement("div"); document.body.appendChild(win.msgText); win.msgText.style.width="100%"; win.msgText.style.height="100%"; win.msgText.style.overflow="auto"; center.setContent(win.msgText); // 输入消息面板 win.msgInput = document.createElement('div'); document.body.appendChild(win.msgInput); win.msgInput.textbox = new dijit.form.TextBox({value:"", tabindex:0, style: "width:90%;"}, document.createElement('div')); win.msgInput.btnSend = new dijit.form.Button({label:"发送"}, document.createElement('div')); win.msgInput.textbox.connect(win.msgInput.textbox, "onKeyDown", function(event){ var keycode = -1; if (event.which == null) keycode = event.keyCode; else keycode = event.which; if ((keycode==10 || keycode==13) && win.msgInput.textbox.getValue()!=="") { // alert(win.msgInput.textbox.getValue()); sendPrivateMessage(win.msgInput.textbox.getValue(), win.username); win.msgInput.textbox.attr("value",""); // alert("HHH"); OK! } }); win.msgInput.btnSend.connect(win.msgInput.btnSend, "onClick", function(){ sendPrivateMessage(win.msgInput.textbox.value, win.username); win.msgInput.textbox.attr("value",""); // alert("DDDD"); OK! }); win.msgInput.appendChild(win.msgInput.textbox.domNode); win.msgInput.appendChild(win.msgInput.btnSend.domNode); bottom.setContent(win.msgInput); layout.addChild(bottom); layout.addChild(leading); layout.addChild(center); win.setContent(layout); win.startup(); win.bringToTop(); // 窗口关闭事件 win.connect(win, "onClick", onChatWinClosed); return win; } // 关闭一个聊天窗口 function onChatWinClosed(event){ if (event.target.attributes[0].value === "closeNode") { var i; for (i=0; i<chatWinList.length; i++) { if (chatWinList[i] === this) { chatWinList[i] = null; break; } } // alert(chatWinList[i]); OK! } } // 点击一个在线用户 function onUserItemClick(event){ var i; for(i=0; i<chatWinList.length; i++) { if(chatWinList[i]!=null && chatWinList[i].username===this.username) { chatWinList[i].bringToTop(); chatWinList[i].show(); break; } } if(i===chatWinList.length){ var username = this.username; chatWinList.push(onOpenChatWindow(username)); } } // 收到消息并显示 function onReveiceMessage(msg){ if (msg.data.Type==="") return; // 用户登录成功消息 if (msg.data.Type==="s_login_ack_sucess"){ userList = document.createElement("div"); document.body.appendChild(userList); for (var i=0; i<msg.data.userList.length; i++) { var content = "<table><tr>" + "<td><img src="default.png" mce_src="default.png" style='width:50px;height:50px;'></td>" + "<td><font color='green'>" + msg.data.userList[i] + "</font></td>" + "</tr></table>"; var item = new dojox.layout.ContentPane({content: content}, document.createElement("div")); userList.appendChild(item.domNode); item.username = msg.data.userList[i]; item.connect(item, "onClick", onUserItemClick); } var main = document.getElementById("main"); main.style.display = ""; var chat = document.getElementById("chat"); chat.style.display = ""; userPane = createTitlePane("在线人员", userList); dojo.byId("leading").appendChild(userPane.domNode); multChatPane = createTitlePane("多人聊天", chat); dojo.byId("center").appendChild(multChatPane.domNode); return; // 新用户加入的时候更新消息 }else if (msg.data.Type==="s_new_joiner"){ var content = "<table><tr>" + "<td><img src="default.png" mce_src="default.png" style='width:50px;height:50px;'></td>" + "<td><font color='green'>" + msg.data.NewJoiner + "</font></td>" + "</tr></table>"; var item = new dojox.layout.ContentPane({content: content}, document.createElement("div")); item.connect(item, "onClick", onUserItemClick); item.username = msg.data.NewJoiner; userList.appendChild(item.domNode); userPane.startup(); return; }else if (msg.data.Type==="c_public"){ displayMsg(document.getElementById("content"), msg); }else if (msg.data.Type==="c_private"){ // OK! var i=0; for(i=0; i<chatWinList.length; i++) { if (chatWinList[i]!=null && msg.data.From===nickname && msg.data.To===chatWinList[i].username) // 发出去的消息 { displayMsg(chatWinList[i].msgText, msg); break; } if (chatWinList[i]!=null && msg.data.From===chatWinList[i].username) // 发给我的消息 { displayMsg(chatWinList[i].msgText, msg); break; } } // 没打开窗口时接收消息 if (i===chatWinList.length) { // alert(msg.data.From); OK! var win = onOpenChatWindow(msg.data.From); chatWinList.push(win); displayMsg(win.msgText, msg); } } } // 在消息面板content:Div中显示msg function displayMsg(content, msg){ var now = new Date(); var time = now.getHours() + ":" + now.getMinutes() + ":" + now.getSeconds(); var date = now.getFullYear() + " 年" + (now.getMonth()+1) + "月" + now.getDate() + "日"; // 有点怪的月份! var formatHead = '<font color="green">'; var formatEnd = '</font>'; var msgText = '<font color="blue">' + msg.data.From + "</font>" + " 说: " + msg.data.Message + " " + formatHead + "【" + time + " " +date +"】" + formatEnd; var div = document.createElement("div"); div.innerHTML = msgText; content.appendChild(div); if(content.doScroll) content.doScroll("scrollbarDown"); else content.scrollTop += 1000000; } // 初始化cometd function initChatService(url) { dojox.cometd.init(url); dojox.cometd.subscribe("/ChatRoom", onReveiceMessage); } // 发送按钮事件 function onBtnSend() { var m = document.getElementById("message").value; msgObj.From = nickname; msgObj.To = "Server"; msgObj.Message = document.getElementById("message").value; msgObj.Type = "c_public"; dojox.cometd.publish("/ChatRoom", msgObj); document.getElementById("message").value=""; } // 发送私聊消息 function sendPrivateMessage(msg, to){ msgObj.From = nickname; msgObj.To = to; msgObj.Message = msg; msgObj.Type = "c_private"; dojox.cometd.publish("/ChatRoom", msgObj); } // 检查昵称是否为空 function CheckNickName(){ var nickname = document.getElementById("nickName").value; if (nickname.length == 0) { alert("请输入昵称!"); document.getElementById("nickName").focus(); } else document.getElementById("nickName").disabled = "true"; } // 登录 function login(username, password){ if (username == "" || password == ""){ alert("用户名密码错误!"); return; } msgObj.UserName = username; msgObj.Password = password; msgObj.From = username; msgObj.To = "Server"; msgObj.Type = "c_login"; msgObj.ImageUrl = "./default.png"; nickname = username; dojox.cometd.publish("/ChatRoom", msgObj); loginDlg.hide(); } function onLogin(){ var username = document.getElementById("username").value; var password = document.getElementById("password").value; login(username, password); } function showLoginDialog(){ var div = document.getElementById("login"); div.style.display = ""; var dlg = new dijit.Dialog({ title: "用户登录", content: div, style: "width: 300px; align: center;" }); dlg.show(); return dlg; } // 创建面板 function createTitlePane(title, content){ var pane = new dijit.TitlePane({ title: title, content: content }); document.body.appendChild(pane.domNode); return pane; } // 创建列表控件, store为dojox.data.CsvStore, layout为表格的行列格式,表头 /* function createGridList(store, layout){ var grid = new dojox.grid.DataGrid({ query:{}, store: store, clientSort: true, rowSelector: '20px', structure: layout }, document.createElement('div')); dojo.byId("gridContainer").appendChild(grid.domNode); grid.startup(); return grid; } */ dojo.addOnLoad(function (){ initChatService("/dojoSrc/cometd/cometd"); loginDlg = showLoginDialog(); }); // --></mce:script> </head> <body class="tundra"> <div id="chat" style="display:none;width:100%;height:680px"> <div id="content" style="width:100%;height:655px;overflow:auto"></div> <div style="width:100%;"> <div tabindex="0" id="message" dojoType="dijit.form.TextBox" style="width:300px;"></div> <div id="btn_send" dojoType="dijit.form.Button" onclick="onBtnSend()">发  送</div> </div> </div> <!-- 登录对话框 --> <div id="login" align="center" style="display:none;" mce_style="display:none;"> <table> <tr> <td>用户名:</td> <td> <div dojoType="dijit.form.TextBox" id="username"></div> </td> </tr> <tr> <td>密  码:</td> <td> <div tabindex="0" type="password" name="password" id="password" dojoType="dijit.form.ValidationTextBox" required="true" intermediateChanges=false invalidMessage="请输入密码" /> </td> </tr> <tr> <td></td> </tr> </table> <div dojoType="dijit.form.Button" align="center" onclick="onLogin()">用户登录</div> </div> <!-- 主界面 --> <div id="main"> <div id="mainLayout" dojoType="dijit.layout.BorderContainer" style="width:100%;height:850px;"> <div id="leading" style="width:25%;" dojoType="dijit.layout.ContentPane" region="leading"></div> <div id="top" style="height:10%" dojoType="dijit.layout.ContentPane" region="top"></div> <div id="center" dojoType="dijit.layout.ContentPane" splitter="true" region="center"></div> </div> </div> </body> </html> 

 

Jetty+Dojo+Tomcat的Comet学习笔记-Web聊天系统【ChatRoom】_第1张图片Jetty+Dojo+Tomcat的Comet学习笔记-Web聊天系统【ChatRoom】_第2张图片Jetty+Dojo+Tomcat的Comet学习笔记-Web聊天系统【ChatRoom】_第3张图片Jetty+Dojo+Tomcat的Comet学习笔记-Web聊天系统【ChatRoom】_第4张图片Jetty+Dojo+Tomcat的Comet学习笔记-Web聊天系统【ChatRoom】_第5张图片

 

界面还不够丰富,功能也还不够完善,项目需要,先练练手的。

你可能感兴趣的:(tomcat,String,div,聊天,dojo,Comet)