论坛首页 Web前端技术论坛

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

浏览 42387 次
精华帖 (3) :: 良好帖 (0) :: 新手帖 (1) :: 隐藏帖 (0)
作者 正文
   发表时间:2008-05-31   最后修改:2011-03-02
DWR
当你有QQ消息时,QQ的小图标会晃动,我们甚至还可以设定QQ有消息时,自动弹出来!
这些在C/S模式下很容易实现!如果要在B/S模式如何来实现呢?
我们的肯定会想到在客服端调用ajax在后台不断的查询服务器.看是否有关于自己的消息.如果有则查询返回.这种做法肯定会大量的占用系统的资源!不可取!
现在DWR的反转AJAX功能.允许我们从服务器端来控制客服端.而不需要客户端来请求.服务器可以自动把消息发给指定的客户端!
[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
   发表时间:2008-07-16  
好  谢谢 !!!!!!!!!!
0 请登录后投票
   发表时间:2008-07-23  
跪谢,跪谢,呵呵,可让我找到了
0 请登录后投票
   发表时间:2008-08-14  
学习ing!!!
很好很强大
请教服务器如何判断对应某个sessionid的客户端是否关闭或者失去连接,从而能实时推送到其他客户端,完成在线用户的实时更新
0 请登录后投票
   发表时间:2008-08-14  
slan 写道
学习ing!!!
很好很强大
请教服务器如何判断对应某个sessionid的客户端是否关闭或者失去连接,从而能实时推送到其他客户端,完成在线用户的实时更新


以前也曾看过dwr ajax反转的资料,当时大家提出的问题是:dwr这些推的功能,是通过维持一个长时间的链接完成的。而这来说,是比较耗服务器资源的。所以,想请问下lz,现在这些方面有改进没呢?
0 请登录后投票
   发表时间:2008-08-14  
引用
以前也曾看过dwr ajax反转的资料,当时大家提出的问题是:dwr这些推的功能,是通过维持一个长时间的链接完成的。而这来说,是比较耗服务器资源的。所以,想请问下lz,现在这些方面有改进没呢?

这个也是我很关注的问题,比起传统的拉模式,如果要实现楼主的功能,传统的做法是js定时刷,就算现在可以用dwr使得返回的数据缩小,但还是有一个连接的过程
0 请登录后投票
   发表时间:2008-08-14  
引用
学习ing!!!
很好很强大
请教服务器如何判断对应某个sessionid的客户端是否关闭或者失去连接,从而能实时推送到其他客户端,完成在线用户的实时更新


首先可以肯定的是可以实现这个功能,不过不推荐这样去做.你可以选择变通的方式,你没有必要去判断或者监视session是否关闭或者失去连接.你关注的应该是该用户是否当前是否在线(浏览网页),你可以通过服务器的一个计划任务来定时呼叫所有在线的用户.没有回应的即视为不在线(这样做很危险),可以变通一下.比较好的实现就是每个用户登陆后发一个通知告诉服务器,然后服务器再统计人数,告诉当前所有用户更新计数器.

引用
以前也曾看过dwr ajax反转的资料,当时大家提出的问题是:dwr这些推的功能,是通过维持一个长时间的链接完成的。而这来说,是比较耗服务器资源的。所以,想请问下lz,现在这些方面有改进没呢?


在我目前系统的应用情况来看.不这么稳定(可能是我某些方面设计不合理),推荐在系统业务逻辑不这么重要的地方使用类似的应用.
0 请登录后投票
   发表时间:2008-08-14  
看了楼主的回复受益匪浅
但按照这样又会出现一个问题,如果用户甲和乙都正常登录,在线列表会显示甲和乙,如果乙由于某种原因非正常离线,这时甲的在线列表上应该还是保留了乙的在线状态,如果这时甲发送一条短消息给乙,乙是不可能正常接受到的,这时服务器如何能知道乙已经失去了连接?
我的理解是采用反转如果是一直保持和服务器的连接的话,非正常退出应该也会发送一个消息给服务器?如果能拦截这个消息,应该就能实时更新其他客户端,就算没有这个消息,发送的时候服务器如果能判断乙这时不在线的状态,至少可以把甲的这条短消息贮存到数据库中。
2 请登录后投票
   发表时间:2008-08-15  
引用
看了楼主的回复受益匪浅
但按照这样又会出现一个问题,如果用户甲和乙都正常登录,在线列表会显示甲和乙,如果乙由于某种原因非正常离线,这时甲的在线列表上应该还是保留了乙的在线状态,如果这时甲发送一条短消息给乙,乙是不可能正常接受到的,这时服务器如何能知道乙已经失去了连接?
我的理解是采用反转如果是一直保持和服务器的连接的话,非正常退出应该也会发送一个消息给服务器?如果能拦截这个消息,应该就能实时更新其他客户端,就算没有这个消息,发送的时候服务器如果能判断乙这时不在线的状态,至少可以把甲的这条短消息贮存到数据库中。

一般是这样的,你发送消息的时候直接存储到数据库,存储完毕以后单独发送一条消息给接受消息的用户,通知他去查询数据库即可。如果页面关闭了。当初她无法查询数据库。但是可以实现等她再次登陆系统时,系统自动弹出相关信息即可(其实就是留言功能)
1 请登录后投票
   发表时间:2008-08-15  
这个是所有web服务器都能够支持的吗?
楼主觉得这东西与web服务器的支持有没有关系呢
0 请登录后投票
论坛首页 Web前端技术版

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