`
yourgame
  • 浏览: 352477 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

DWR Server Push(服务器推技术) 实现即时聊天功能(WEB)`

    博客分类:
  • DWR
阅读更多
当你有QQ消息时,QQ的小图标会晃动,我们甚至还可以设定QQ有消息时,自动弹出来!
这些在C/S模式下很容易实现!如果要在B/S模式如何来实现呢?
我们的肯定会想到在客服端调用ajax在后台不断的查询服务器.看是否有关于自己的消息.如果有则查询返回.这种做法肯定会大量的占用系统的资源!不可取!
现在DWR的反转AJAX功能.允许我们从服务器端来控制客服端.而不需要客户端来请求.服务器可以自动把消息发给指定的客户端!

w_e_ibo:
http://t.cn/zjqEaRh  
http://weibo.com/liao27

[img]http://yourgame.iteye.com/upload/picture/pic/16013/4ca7a7b8-4281-3260-a568-235b61d511dd.gif [/img]

我不能把公司的代码贴出来,所以我自己写了一个DEMO,已经实现了对指定用户发送消息的功能,并且即时显示在指定的用户界面上面.当然你可以修改这些,让它弹出来显示,或者像QQ一样在你页面的某个角落让消息的图标闪动:)
下面我做一个简单的说明:

用户的JAVABEAN User.java
package com.lhq;

/**
 * 用户类
 * 
 * @author 廖瀚卿
 * 
 */
public class User {

	private String userid;

	private String username;

	public String getUserid() {
		return userid;
	}

	public void setUserid(String userid) {
		this.userid = userid;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public User(String userid, String username) {
		super();
		this.userid = userid;
		this.username = username;
	}

}


聊天处理类 ChatManager.java
package com.lhq;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;

import org.directwebremoting.ScriptSession;
import org.directwebremoting.ServerContext;
import org.directwebremoting.ServerContextFactory;
import org.directwebremoting.WebContextFactory;
import org.directwebremoting.proxy.dwr.Util;

/**
 * 处理聊天相关
 * 
 * @author 廖瀚卿
 * 
 */
public class ChatManager {

	/** 保存当前在线用户列表 */
	public static List<User> users = new ArrayList<User>();

	/**
	 * 更新在线用户列表
	 * @param username 待添加到列表的用户名
	 * @param flag 是添加用户到列表,还是只获得当前列表
	 * @param request
	 * @return 用户userid
	 */
	public String updateUsersList(String username, boolean flag, HttpServletRequest request) {
		User user = null;
		if (flag) {	
			// 这里取会话(HttpSession)的id为用户id 
			user = new User(request.getSession().getId(), username);
			//保存用户到列表
			users.add(user);	
			//将用户id和页面脚本session绑定
			this.setScriptSessionFlag(user.getUserid());
		}
		//获得DWR上下文
		ServletContext sc = request.getSession().getServletContext();
		ServerContext sctx = ServerContextFactory.get(sc);
		//获得当前浏览 index.jsp 页面的所有脚本session
		Collection sessions = sctx.getScriptSessionsByPage("/chat/index.jsp");
		Util util = new Util(sessions);
		//处理这些页面中的一些元素
		util.removeAllOptions("users");
		util.addOptions("users", users, "username");
		util.removeAllOptions("receiver");
		util.addOptions("receiver", users,"userid","username");
		if(!flag){
			return null;
		}
		return user.getUserid();
	}

	/**
	 * 将用户id和页面脚本session绑定
	 * @param userid
	 */
	public void setScriptSessionFlag(String userid) {
		WebContextFactory.get().getScriptSession().setAttribute("userid", userid);
	}

	/**
	 * 根据用户id获得指定用户的页面脚本session
	 * @param userid
	 * @param request
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public ScriptSession getScriptSession(String userid, HttpServletRequest request) {
		ScriptSession scriptSessions = null;
		Collection<ScriptSession> sessions = new HashSet<ScriptSession>();
		sessions.addAll(ServerContextFactory.get(request.getSession().getServletContext())
				.getScriptSessionsByPage("/chat/index.jsp"));
		for (ScriptSession session : sessions) {
			String xuserid = (String) session.getAttribute("userid");
			if (xuserid != null && xuserid.equals(userid)) {
				scriptSessions = session;
			}
		}
		return scriptSessions;
	}
	
	/**
	 * 发送消息
	 * @param sender 发送者
	 * @param receiverid 接收者id
	 * @param msg 消息内容
	 * @param request
	 */
	public void send(String sender,String receiverid,String msg,HttpServletRequest request){
		ScriptSession session = this.getScriptSession(receiverid, request);
		Util util = new Util(session);
		util.setStyle("showMessage", "display", "");
		util.setValue("sender", sender);
		util.setValue("msg", msg);
	}
}



聊天主页面 index.jsp
<%@ page language="java" pageEncoding="GBK"%>
<html>
	<head>
		<title>chat</title>
		<meta http-equiv="pragma" content="no-cache">
		<meta http-equiv="cache-control" content="no-cache">
		<meta http-equiv="expires" content="0">
		<script type='text/javascript' src='/chat/dwr/interface/ChatManager.js'></script>
		<script type='text/javascript' src='/chat/dwr/engine.js'></script>
		<script type='text/javascript' src='/chat/dwr/util.js'></script>
		<script type="text/javascript" src="/chat/chat.js"></script>
	</head>
	<body>
		<input type="hidden" name="userid" />
		<br>
		昵称:
		<input type="text" name="username" />
		<input type="button" value="注册" onclick="register(this);" />
		<br />
		<br />
		我要对
		<select name="receiver" id="receiver" disabled=true" >
		</select>
		说:
		<input type="text" name="message" id="message" disabled="true" />
		<input type="button" value="发送" id="send" name="send" disabled="true"
			onclick="send();" />
		<br />
		<br />
		在线用户列表:
		<ul id="users">
		</ul>

		<div id="showMessage" style="display: none">
			<span id="sender"></span>对你说:
			<span id="msg"></span>
		</div>
	</body>
</html>


JAVASCRIPT 文件 chat.js
/**
 * 注册帐号
 */
function register(button) {
	if ($('username').value == "" || $('username').value.length <= 0) {
		alert("请输入昵称");
		return;
	}

	/* 下面是对一些按钮的禁用和激活操作 */
	$('username').disabled = true;
	button.disabled = true;
	$('message').disabled = false;
	$('send').disabled = false;
	$('receiver').disabled = false;

	/* 把我输入的用户名注册到服务器,并获得用户id(这里用session id 代替) */
	ChatManager.updateUsersList($('username').value, true, function(data) {
		if (data != null && data.length > 0) {
			$('userid').value = data; // 注册成功,把userid放到当前页面
		}
	});
}

/**
 * 页面初始化
 */
function init() {
	dwr.engine.setActiveReverseAjax(true); // 激活反转 重要
	ChatManager.updateUsersList(null, false); // 当你打开界面的时候,先获得在线用户列表.
}

/**
 * 发送消息
 */
function send() {
	var sender = dwr.util.getValue('username'); // 获得发送者名字
	var receiver = dwr.util.getValue('receiver'); // 获得接受者id
	var msg = dwr.util.getValue('message'); // 获得消息内容
	ChatManager.send(sender, receiver, msg); // 发送消息
}

window.onload = init;//页面加载完毕后执行初始化方法init


配置文件 web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app 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">
	<welcome-file-list>
		<welcome-file>index.jsp</welcome-file>
	</welcome-file-list>
	<servlet>
		<servlet-name>dwr-invoker</servlet-name>
		<servlet-class>
			org.directwebremoting.servlet.DwrServlet
		</servlet-class>
		<init-param>
			<description>调试DWR,发布系统时应将其设为false</description>
			<param-name>debug</param-name>
			<param-value>true</param-value>
		</init-param>
		<init-param>
			<description>使用服务器推技术(反转AJAX)</description>
			<param-name>activeReverseAjaxEnabled</param-name>
			<param-value>true</param-value>
		</init-param>
		<init-param>
			<param-name>
				initApplicationScopeCreatorsAtStartup
			</param-name>
			<param-value>true</param-value>
		</init-param>
		<init-param>
			<param-name>maxWaitAfterWrite</param-name>
			<param-value>100</param-value>
		</init-param>
		<load-on-startup>4</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>dwr-invoker</servlet-name>
		<url-pattern>/dwr/*</url-pattern>
	</servlet-mapping>
</web-app>


DWR配置文件 dwr.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE dwr PUBLIC "-//GetAhead Limited//DTD Direct Web Remoting 2.0//EN" "http://getahead.org/dwr/dwr20.dtd">
<dwr>
	<allow>
		<convert converter="bean" match="com.lhq.User"/>
		<create creator="new" javascript="ChatManager">
			 <param name="class" value="com.lhq.ChatManager"/>
		</create>
	</allow>
</dwr>

  • chat.rar (827.1 KB)
  • 描述: 附上DEMO源代码
  • 下载次数: 9784
分享到:
评论
39 楼 sjzzqy 2012-04-23  
Collection sessions = sctx.getScriptSessionsByPage("/chat/index.jsp");   为什么我session总是空呢?  各种方法都试过了。
38 楼 fandayrockworld 2011-11-22  
"NetworkError: 500 Internal Server Error - http://localhost:8080/chat/dwr/interface/ChatManager.js"
ChatManager.js
"NetworkError: 500 Internal Server Error - http://localhost:8080/chat/dwr/util.js"
util.js
"NetworkError: 500 Internal Server Error - http://localhost:8080/chat/dwr/engine.js"
engine.js
"NetworkError: 500 Internal Server Error - http://localhost:8080/chat/dwr/engine.js"
engine.js
"NetworkError: 500 Internal Server Error - http://localhost:8080/chat/dwr/util.js"
util.js
dwr is not defined
[在此错误处中断] dwr.engine.setActiveReverseAjax(true); // 激活反转 重要 

楼主,错误···
37 楼 mdc 2011-10-25  
非常感谢楼主的分享!
36 楼 fengzl 2009-05-18  
长连接更不可取,所以这个和鸡肋差不多
35 楼 cwmwss 2009-05-18  
我下了你的源码可以运行出来了。WEB-INF里的很多TLD文件有用的吗?
34 楼 cwmwss 2009-05-18  
为什么我的DWR不支持$()标记,
改成getelementbyid后,无JS错误。
进入页面为什么看不见用户列表,注册后select中的用户还是空的
33 楼 xiaoyu966 2009-04-27  
楼主,这样吧,你看下我的源代码,我发了一个已经实现了的ext在线聊天的,只是没有lib包,当初就是看了你这个例子以后决定用dwr反转的,以前用的是pushlet,感觉要麻烦很多。因为是做毕业设计,这个又比较简单,所以就打算做的完美一点呵呵,个人对个人的聊天我也实现了,现在在加聊天记录,发送离线消息什么的。

http://xiaoyu966.iteye.com/admin/blogs/375509

定期向浏览器推一个信息,然后让浏览器回复一个请求,如果没有请求,则认为已经下线,定期任务的发送,我待会去找找资料。
32 楼 xiaoyu966 2009-04-27  
你好,楼主,你提供的思路倒是也可以考虑一下,但是服务器定期推消息怎么实现啊?我想了下好像没有类似的调用方法。可以提供一个例子吗?谢谢了。

ScriptSession这个对象只要刷新一下浏览器就会新增加一个,我后来解决了这个问题,就是用一个map来存放在线用户对应的ScriptSession,用户刷新和关闭浏览器的时候,也会更新或者删除对应的ScriptSession;

31 楼 xiaoyu966 2009-04-27  
你好楼主,我是这个问题:  fireFox下怎么判断是刷新还是关闭浏览器    的提问者,
刚看了你的回复

引用
希望有高人能解决这个问题,因为我在做一个实时聊天的应用,要监听实时在线的用户。关闭浏览器时,要销毁对应的session,要更新在线用户。


首先,你这种做法就不正确。你不能通过onunload 来检测用户是否已经离线。

你可以采用服务器推技术(server push),来实现检测在线用户。关于推技术。你可以参考我博客里面的一片文章
http://yourgame.iteye.com/blog/199079

我的建议是,也没有必要实时去检测在线用户是否还在线,你可以再用户登录的时候发送一个请求给服务器,让服务器通过推技术来检测当前有多少用户在线。这样效率要高一些。或则你用户点退出系统,也可以发送一个请求给服务器处理



我想你误解我的意思了,我采用了dwr的反转,长连接已经用上了,但是用在用户上线,下线时,实时更新其他用户的浏览器中好友列表,还有就是发送的信息。

你说:你可以采用服务器推技术(server push),来实现检测在线用户。
长连接检测在线用户,这个可行性是不是太低了?ScriptSession刷新一次就会创建一次,用这个来看检测在线的可定不行。


你说:我的建议是,也没有必要实时去检测在线用户是否还在线,你可以再用户登录的时候发送一个请求给服务器,让服务器通过推技术来检测当前有多少用户在线。这样效率要高一些。或则你用户点退出系统,也可以发送一个请求给服务器处理

这个我早就实现了,但是退出的时候分为正常退出和非正常退出两种,正常退出时,当然就是销毁session,并且给其他用户浏览器推入新的在线用户列表。非正常退出就是用户没有点退出按钮,而是直接点关闭,或者右键关闭,我要处理的也就是非正常关闭时,在关闭前销毁对应的session,并且更新在线用户列表。
30 楼 bohemia 2009-04-23  
Cometd目前应该是较好的方式;

Comet的三种实现方式:
1.长连接(ServerPush)
2.Long-Polling
3.轮询

目前Jetty的Cometd由于已经使用了Jetty的异步Servlet方式; 对服务的扩展和并发性能来说更好;
29 楼 IamNull 2009-04-23  
如果是要做网页聊天的功能,可以参考一下其他的开源组件,比如 Openfire
28 楼 arpenker 2009-02-28  
只支持ie浏览器。其它的所有浏览器全都无法正常使用。请大家三思!!!!!!这是一个不符合国际最起码标准的DEMO。


27 楼 yewen0125 2009-02-26  
谢谢, 我找这个好久了
26 楼 dopic 2009-02-21  
感觉,总有点怪,能不能在大量用户下使用啊
25 楼 ren20666041 2009-02-13  
试试看吧.
24 楼 yourgame 2009-02-12  
omeweb 写道
有没有测试过并发连接数?

你可以测试一下,并且希望您公布结果
23 楼 omeweb 2009-02-12  
有没有测试过并发连接数?
22 楼 yourgame 2009-02-05  
hanle3345678 写道
没法用啊  楼主

怎么了?
21 楼 hanle3345678 2009-02-05  
没法用啊  楼主
20 楼 wangzinan 2009-01-14  
太感谢了 正在学习中 谢谢

相关推荐

Global site tag (gtag.js) - Google Analytics