`

webqq更新——采用反向AJAX实现在线人员上下线模拟

阅读更多
原文:http://www.abigdreamer.com/mywork/webqq-update-online-reverse-ajax-implementation-off-the-assembly-line-simulation.html
本blog已转移到造梦师http://www.abigdreamer.com,欢迎大家常去我的blog看看!

说明:本次更新只是模拟了一下人员的上下线,并没有采用真是的数据(我一台电脑开那么多浏览器测试的话有点受不了,谁叫电脑烂呢,呵呵) 。另外需要注意的就是,需要用这个新的dwr.jar覆盖掉原来的那个dwr.jar,要不会出问题的!

有朋友问我怎样用反向AJAX来实现在线人员列表的更新,提到DWR允许javascript访问服务器端的Java方法,这使得AJAX使用起来会比较容易,而在DWR2.0里面添加了一个非常强大的功能——反向AJAX,也就是说,服务器端的Java方法可以取得浏览器端的Web上下文,并可以调用javascript的方法,将服务器端的数据异步地传输给浏览器。本文将通过一个demo展示这种特性。这个demo实现了类似股票交易系统中实时更新数据的功能,事实上是通过发布-订阅模式去实现的。也就是说,客户端订阅一个主题,服务器端通过一个线程向订阅这个主题的浏览器定时、异步地发送数据,从而实现了这种实时更新的功能。
    我们知道,客户端浏览器可以随时连接到web服务器,并向服务器请求资源,而服务器却没有这种能力,它不能主动地于客户端浏览器建立连接,并主动地将数据发送给浏览器。DWR支持3种从服务器端发送数据给客户端的方式:
1、轮询。客户端在每个时间周期内向服务器发送请求,看看服务器端有没有数据更新,如果有,就向服务器请求数据。
2、Comet:基于HTTP长连接的服务器推动方式。客户端向服务器发送请求后,服务器将数据通过response发送给客户端,但并不会将此response关闭,而是一直通过response将最新的数据发送给客户端浏览器,直到客户端浏览器关闭。
3、PiggyBack(回传)。服务器端将最新的数据排成队列,然后等待客户端下一次请求,接收到请求后就将等待更新的数据发给客户端。
这3种方式各有优劣,而DWR可以同时支持轮询和Comet。
首先,我们要让DWR程序支持反向AJAX。只需要在web.xml中添加如下配置:
<servlet>
        <servlet-name>dwr-invoker</servlet-name>
        <servlet-class>org.directwebremoting.servlet.DwrServlet</servlet-class>
        <init-param>
            <param-name>debug</param-name>
            <param-value>true</param-value>
        </init-param>
        <init-param>
            <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>1</load-on-startup>
    </servlet>    
    <servlet-mapping>
        <servlet-name>dwr-invoker</servlet-name>
        <url-pattern>/dwr/*</url-pattern>
    </servlet-mapping>

将初始化参数activeReverseAjaxEnabled设置为true表示启动反向AJAX。另外,添加这个功能的核心之处在于服务器端的发布者——Publisher.java,在这个类里面,首先通过org.directwebremoting.WebContext来获得访问这个应用的Web上下文:
WebContext webContext = WebContextFactory.get();
ServletContext servletContext = webContext.getServletContext();
serverContext = ServerContextFactory.get(servletContext);
webContext.getScriptSessionsByPage("");


这里主要通过WebContext类获得DWR应用的WEB上下文,用ServletContext获得DWRServlet的上下文,以及通过WEB上下文获取访问本应用的客户端浏览器的ScriptSession。通过ScriptSession,可以在服务器端向客户端浏览器发出执行js方法的指令,并把服务器端数据传送给js方法,具体的用法如下:

Collection sessions = serverContext
						.getScriptSessionsByPage("/webQQ/webQQ.jsp");
				ScriptProxy proxy = new ScriptProxy(sessions);

				WebQQUser user = users.getNextUserStatu();
				proxy.addFunctionCall("publishUserStatus", user);


这段代码首先通过getScriptSessionsByPage方法获得所有访问/webQQ/webQQ.jsp这个资源的客户端浏览器的ScriptSession,并为这些session建立代理(ScriptProxy),通过这个代理,让客户端执行publishUserStatus的js方法。其中addFunctionCall就是向客户端发送执行js方法的服务器端方法,第一个参数是js方法的签名,后面的都是js方法的参数。user是要发布的即时数据。user这个对象是随机生成的(见org.darkness.test.webqq. WebQQUsers类),Publish.java这个类启动了一个线程(worker),这个线程不断地生成user的数据,并发布给客户端。
以下是html页面的核心部分的代码:

var listeners = [];

function subscribeUserStatus(callBackHandler){
	listeners.push(callBackHandler);
}

function publishUserStatus(user){
	for(var i=0;i<listeners.length;i++){
		listeners[i].fun.call(listeners[i].scope,user);
	}
}

subscribeUserStatus({
	fun:function(user){
		try{
			var rootChildNodes = Ext.getCmp('im-tree').root.childNodes;
			for(var i=0;i<rootChildNodes.length;i++){
				var childNodes = rootChildNodes[i].childNodes;
				for(var j=0;j<childNodes.length;j++){
					if(childNodes[j].id == user.account){
						if(user.isShowOnline == 0){
							childNodes[j].getUI().getIconEl().src = 'images/user_delete.gif';
							
							var tempChild = childNodes[j];
							// appendChild就会将其放置到最尾端,不需要再remove了
							//rootChildNodes[i].removeChild(tempChild);
							rootChildNodes[i].appendChild(tempChild);
						}else{
							childNodes[j].getUI().getIconEl().src = 'images/user.gif';
							
							// 将刚上线的人员移动到最顶端
							var firstChild = rootChildNodes[i].firstChild;
							var tempChild = childNodes[j];
							rootChildNodes[i].insertBefore(tempChild, firstChild);
						}
						
						// 本来想用TreeNode自带的排序方法的,可试过了,总是没反应
						//rootChildNodes[i].sort(function(nodeA, nodeB){
						//	return nodeA.getUI().getIconEl().src > nodeB.getUI().getIconEl().src;
						//}, rootChildNodes[i]);
						return;
					}
				}
			}
			
		}catch(e){
		}
	},
	scope:this
});
dwr.engine.setActiveReverseAjax(true);

这一块代码主要是将改变在线或下线的用户的图标。
subscribeUserStatus方法订阅在线人员列表的数据(这些数据由服务器端的Java方法进行发布)。其中通过一个匿名回调函数,将取得改变的数据在页面显示出来(即人员的上、下线)。该回调方法非常简单,只是将TreeNode组件中的图标改变了一下,实现了实时提醒用户上下线的功能。
另外,服务器端还有一个监听器PublisherServletContextListener,这是为了在适当的时候关闭发布者的线程。这个监听器要结合其他两个DWR的监听器使用,只需在web.xml里面声明就行了:
<listener>
     <listener-class>org.directwebremoting.servlet.EfficientShutdownServletContextAttributeListener</listener-class>
</listener>
<listener>
        <listener-class>org.directwebremoting.servlet.EfficientShutdownServletContextListener</listener-class>
    </listener>
    <listener>
        <listener-class> org.darkness.webqq.web.servlet.PublisherServletContextListener</listener-class>
    </listener>

最后,看一下dwr的映射关系dwr.xml:
<dwr>
  <allow>
    
    <create creator="new" javascript="Publisher" scope="application">
      <param name="class" value="org.darkness.webqq.web.servlet.Publisher"/>
    </create>
    <convert converter="bean" match="org.darkness.webqq.model.WebQQUser"/>

    <!-- this is a bad idea for live, but can be useful in testing -->
    <convert converter="exception" match="java.lang.Exception"/>
    <convert converter="bean" match="java.lang.StackTraceElement"/>
  </allow>
</dwr>

注意<convert converter="bean" match="org.darkness.webqq.model.WebQQUser"/>这个配置,dwr允许将自定义的Java类型与js对象进行相互转换,但要声明转换器。
以下是程序运行的结果:

  • 大小: 25 KB
分享到:
评论
11 楼 suken2302401 2009-11-14  
最好的做法是监听scriptSession  这种做法对于访问量大的  服务器扛不住的
10 楼 g3123298 2009-06-19  
哥们我有事找您 请教您一下!!
9 楼 g3123298 2009-06-09  
ext中2个js文件 一个放着treepanel了 另一个怎么调用它里面的treepanel?
8 楼 myclover 2009-06-03  
可以详细点说明一下反正ajax技术吗?
最好能够使用图解的方式给我们看看
这样看着好像还不是很明白,客户端和
服务器所传输的格式是怎么样的啊?
还是依赖HTTP协议进行传输的吗?
7 楼 depravedangel 2009-06-01  
g3123298 写道

depravedangel 写道
没有说随机的事件 g3123298 写道 depravedangel 写道 WebQQUser user = users.getNextUserStatu(); g3123298 写道 做了一对 但应该是一个在线显示另一个不在线显示 定时 开始全下线 然后循环在线&amp;amp;amp;amp;nbsp;&amp;amp;amp;amp;nbsp;&amp;amp;amp;amp;nbsp; 我是在WebQQUser user = users.getNextUserStatu();这个方法里面采用了一个线程来随机产生一个人员的上下线状态,所以不一定是 “然后循环在线”,可能一个人刚上线了,接着又下线了,这个是随机的,你可以将这个方法内部换成你在线人员列表里面的真实数据。 我可能不太理解您随机出现的意思, 但我想要的需求是在mysql数据库读在线的人数 然后前台有人下线触角一个事件然后对方变成灰色,没有说随机的事件 打扰您了 我是采取随机策略来模拟人员的上下线来进行测试的,你在正式的项目中之需要将随机生成的人员列表改成你真正的在线人员列表就可以了 请述我打扰您了,我很菜 变成真正的在线人员 直接在数据库多填些数据 做模拟人员我不太懂 望您做事坐到低送佛送到西,您这样给我让我做我消化不了 谢谢

我是在内存中模拟的人员上下线,并没有在数据库填数据
WebQQUser user = users.getNextUserStatu();一次也只模拟了一个人的在线状态,你可以将这块代码换成你内存中的在线人员列表。
既然都是在内存中的,不存在切换的问题,你自己想想。
6 楼 g3123298 2009-06-01  
depravedangel 写道

没有说随机的事件
g3123298 写道
depravedangel 写道 WebQQUser user = users.getNextUserStatu(); g3123298 写道 做了一对 但应该是一个在线显示另一个不在线显示 定时 开始全下线 然后循环在线&amp;amp;amp;nbsp;&amp;amp;amp;nbsp;&amp;amp;amp;nbsp; 我是在WebQQUser user = users.getNextUserStatu();这个方法里面采用了一个线程来随机产生一个人员的上下线状态,所以不一定是 “然后循环在线”,可能一个人刚上线了,接着又下线了,这个是随机的,你可以将这个方法内部换成你在线人员列表里面的真实数据。 我可能不太理解您随机出现的意思, 但我想要的需求是在mysql数据库读在线的人数 然后前台有人下线触角一个事件然后对方变成灰色,没有说随机的事件 打扰您了 我是采取随机策略来模拟人员的上下线来进行测试的,你在正式的项目中之需要将随机生成的人员列表改成你真正的在线人员列表就可以了

请述我打扰您了,我很菜
变成真正的在线人员 直接在数据库多填些数据 做模拟人员我不太懂
望您做事坐到低送佛送到西,您这样给我让我做我消化不了 谢谢
5 楼 g3123298 2009-06-01  
还有2个小点
第一个点就是发送的那个键盘事件Enter的

第二个点就是带有特殊符号的发送不到对方比方 ' i'm from Beijing 进不到数据库
4 楼 depravedangel 2009-05-31  
没有说随机的事件
g3123298 写道

depravedangel 写道
WebQQUser user = users.getNextUserStatu(); g3123298 写道 做了一对 但应该是一个在线显示另一个不在线显示 定时 开始全下线 然后循环在线&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp; 我是在WebQQUser user = users.getNextUserStatu();这个方法里面采用了一个线程来随机产生一个人员的上下线状态,所以不一定是 “然后循环在线”,可能一个人刚上线了,接着又下线了,这个是随机的,你可以将这个方法内部换成你在线人员列表里面的真实数据。 我可能不太理解您随机出现的意思, 但我想要的需求是在mysql数据库读在线的人数 然后前台有人下线触角一个事件然后对方变成灰色,没有说随机的事件 打扰您了

我是采取随机策略来模拟人员的上下线来进行测试的,你在正式的项目中之需要将随机生成的人员列表改成你真正的在线人员列表就可以了
3 楼 g3123298 2009-05-31  
depravedangel 写道

WebQQUser user = users.getNextUserStatu();
g3123298 写道
做了一对 但应该是一个在线显示另一个不在线显示 定时 开始全下线 然后循环在线&amp;nbsp;&amp;nbsp;&amp;nbsp; 我是在WebQQUser user = users.getNextUserStatu();这个方法里面采用了一个线程来随机产生一个人员的上下线状态,所以不一定是 “然后循环在线”,可能一个人刚上线了,接着又下线了,这个是随机的,你可以将这个方法内部换成你在线人员列表里面的真实数据。

我可能不太理解您随机出现的意思,
但我想要的需求是在mysql数据库读在线的人数 然后前台有人下线触角一个事件然后对方变成灰色,没有说随机的事件 打扰您了
2 楼 depravedangel 2009-05-27  
WebQQUser user = users.getNextUserStatu();
g3123298 写道

做了一对 但应该是一个在线显示另一个不在线显示 定时 开始全下线 然后循环在线&nbsp;&nbsp;&nbsp;

我是在WebQQUser user = users.getNextUserStatu();这个方法里面采用了一个线程来随机产生一个人员的上下线状态,所以不一定是 “然后循环在线”,可能一个人刚上线了,接着又下线了,这个是随机的,你可以将这个方法内部换成你在线人员列表里面的真实数据。
1 楼 g3123298 2009-05-26  
做了一对 但应该是一个在线显示另一个不在线显示
定时 开始全下线 然后循环在线   

相关推荐

Global site tag (gtag.js) - Google Analytics