用DWR comet+Spring实现服务器推送的例子--网页聊天室

用dwr的comet(推)来实现简单的无刷新多人聊天室,comet是长连接的一种。通常我们要实现无刷新,一般会使用到Ajax。Ajax 应用程序可以使用两种基本的方法解决这一问题:一种方法是浏览器每隔若干秒时间向服务器发出轮询以进行更新,另一种方法是服务器始终打开与浏览器的连接并在数据可用时发送给浏览器。第一种方法一般利用setTimeout或是setInterval定时请求,并返回最新数据,这无疑增加了服务器的负担,浪费了大量的资源。而第二种方法也会浪费服务器资源,长期的建立连接;而相对第一种来说,第二种方式会更优于第一种方法;这里有一个一对多和多对一的关系,而comet向多个客户端推送数据就是一对多的关系。而具体使用哪种方式,要看你当前的需求而定,没有绝对的。

为什么使用Comet?

轮询方法的主要缺点是:当扩展到更多客户机时,将生成大量的通信量。每个客户机必须定期访问服务器以检查更新,这为服务器资源添加了更多负荷。最坏的一种情况是对不频繁发生更新的应用程序使用轮询,例如一种 Ajax 邮件 InBox在这种情况下,相当数量的客户机轮询是没有必要的,服务器对这些轮询的回答只会是 “没有产生新数据”。虽然可以通过增加轮询的时间间隔来减轻服务器负荷,但是这种方法会产生不良后果,即延迟客户机对服务器事件的感知。当然,很多应用程序可以实现某种权衡,从而获得可接受的轮询方法

尽管如此,吸引人们使用 Comet 策略的其中一个优点是其显而易见的高效性。客户机不会像使用轮询方法那样生成烦人的通信量,并且事件发生后可立即发布给客户机。但是保持长期连接处于打开状态也会消耗服务器资源。当等待状态的 servlet 持有一个持久性请求时,该 servlet 会独占一个线程。这将限制 Comet 对传统 servlet 引擎的可伸缩性,因为客户机的数量会很快超过服务器栈能有效处理的线程数量

如果本示例结合Jetty应用服务器效果会更好。

开发环境:

System:Windows

Webbrowser:IE6+、Firefox3+

JavaEE Server:tomcat5.0.2.8、tomcat6

IDE:eclipse、MyEclipse 8

开发依赖库:

JavaEE5、Spring 3.0.5、dwr 3

Email:hoojo_@126.com

Blog:http://blog.csdn.net/IBM_hoojo or http://hoojo.cnblogs.com/

一、准备工作

1、 下载dwr的相关jar包

https://java.net/downloads/dwr/Development%20Builds/Build%20116/dwr.jar

程序中还需要spring的相关jar包

http://ebr.springsource.com/repository/app/library/version/detail?name=org.springframework.spring&version=3.0.5.RELEASE

需要的jar包如下

2、 建立一个WebProject,名称DWRComet

在web.xml中添加dwr、spring配置如下:

  
  
  1. <--加载spring容器配置-->
  2. listener>
  3. listener-class>org.springframework.web.context.ContextLoaderListener</>
  4. >
  5. --设置spring容器加载配置文件路径-->
  6. context-param>
  7. param-name>contextConfigLocationparam-value>classpath*:applicationContext-*.xml>org.directwebremoting.servlet.DwrListenerservletservlet-name>dwr-invokerservlet-class>org.directwebremoting.servlet.DwrServletinit-param>debug>true>
  8. --dwr的comet控制-->pollAndCometEnabled>
  9. servlet-mappingurl-pattern>/dwr/*>

3、 在src目录加入applicationContext-beans.xml配置,这个配置专门配置bean对象,用来配置需要注入的对象。

<?xmlversion="1.0"encoding="UTF-8"?>
   
   
  • beansxmlns="http://www.springframework.org/schema/beans"
  • xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  • xmlns:aop="http://www.springframework.org/schema/aop"
  • xmlns:tx="http://www.springframework.org/schema/tx"
  • xmlns:util="http://www.springframework.org/schema/util"
  • xmlns:context="http://www.springframework.org/schema/context"
  • xsi:schemaLocation="http://www.springframework.org/schema/beans
  • http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  • http://www.springframework.org/schema/aop
  • http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
  • http://www.springframework.org/schema/tx
  • http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
  • http://www.springframework.org/schema/util
  • http://www.springframework.org/schema/util/spring-util-3.0.xsd
  • http://www.springframework.org/schema/context
  • http://www.springframework.org/schema/context/spring-context-3.0.xsd"beans>
  • 4、 在WEB-INF目录添加dwr.xml文件,基本代码如下

      
      
      <?xmlversion="1.0"encoding="UTF-8"?>
    1. <!DOCTYPEdwrPUBLIC"-//GetAheadLimited//DTDDirectWebRemoting3.0//EN""http://getahead.org/dwr/dwr30.dtd">
    2. <dwr>
    3. </>

    以上的准备基本完毕,下面来完成无刷新聊天室代码

    二、聊天室相关业务实现

    1、 聊天实体类Model

      
      
    1. packagecom.hoo.entity;
    2. importjava.util.Date;
    3. /**
    4. *<b>function:</b>
    5. *@authorhoojo
    6. *@createDate2011-6-3下午06:40:07
    7. *@fileMessage.java
    8. *@packagecom.hoo.entity
    9. *@projectDWRComet
    10. *@bloghttp://blog.csdn.net/IBM_hoojo
    11. *@emailhoojo_@126.com
    12. *@version1.0
    13. */
    14. publicclassMessage{
    15. privateintid;
    16. privateStringmsg;
    17. privateDatetime;
    18. //getter、setter
    19. }

    2、 编写聊天信息的事件

    packagecom.hoo.chat; 
       
       
  • importorg.springframework.context.ApplicationEvent;
  • *<b>function:</b>发送聊天信息事件
  • *@createDate2011-6-7上午11:24:21
  • *@fileMessageEvent.java
  • *@packagecom.hoo.util
  • classChatMessageEventextendsApplicationEvent{
  • staticfinallongserialVersionUID=1L;
  • publicChatMessageEvent(Objectsource){
  • super(source);
  • }
  • }
  • 继承ApplicationEvent,构造参数用于传递发送过来的消息。这个事件需要一个监听器监听,一旦触发了这个事件,我们就可以向客户端发送消息。

    3、 发送消息服务类,用户客户端发送消息。dwr需要暴露这个类里面的发送消息的方法

    importorg.springframework.beans.BeansException; 
       
       
  • importorg.springframework.context.ApplicationContext;
  • importorg.springframework.context.ApplicationContextAware;
  • importcom.hoo.entity.Message;
  • /**
  • *<b>function:</b>客户端发消息服务类业务
  • *@authorhoojo
  • *@createDate2011-6-7下午02:12:47
  • *@fileChatService.java
  • *@packagecom.hoo.chat
  • *@projectDWRComet
  • *@bloghttp://blog.csdn.net/IBM_hoojo
  • *@emailhoojo_@126.com
  • *@version1.0
  • */
  • classChatServiceimplementsApplicationContextAware{
  • privateApplicationContextctx;
  • voidsetApplicationContext(ApplicationContextctx)throwsBeansException{
  • this.ctx=ctx;
  • }
  • *<b>function:</b>向服务器发送信息,服务器端监听ChatMessageEvent事件,当有事件触发就向所有客户端发送信息
  • *@createDate2011-6-8下午12:37:24
  • *@parammsg
  • */
  • voidsendMessage(Messagemsg){
  • //发布事件
  • ctx.publishEvent(newChatMessageEvent(msg));
  • }
  • }
  • 上面的sendMessage需要浏览器客户端调用方法完成消息的发布,传递一个Message对象,并且是触发ChatMessageEvent事件。

    4、 编写监听器监听客户端是否触发ChatMessageEvent

    importjava.util.Collection; 
       
       
  • importjava.util.Date;
  • importjavax.servlet.ServletContext;
  • importorg.directwebremoting.ScriptBuffer;
  • importorg.directwebremoting.ScriptSession;
  • importorg.directwebremoting.ServerContext;
  • importorg.directwebremoting.ServerContextFactory;
  • importorg.springframework.context.ApplicationEvent;
  • importorg.springframework.context.ApplicationListener;
  • importorg.springframework.web.context.ServletContextAware;
  • importcom.hoo.entity.Message;
  • *<b>function:</b>监听客户端事件,想客户端推出消息
  • *@createDate2011-6-7上午11:33:08
  • *@fileSendMessageClient.java
  • */
  • @SuppressWarnings("unchecked")
  • classChatMessageClientimplementsApplicationListener,ServletContextAware{
  • privateServletContextctx;
  • voidsetServletContext(ServletContextctx){
  • this.ctx=ctx;
  • }
  • @SuppressWarnings("deprecation")
  • voidonApplicationEvent(ApplicationEventevent){
  • //如果事件类型是ChatMessageEvent就执行下面操作
  • if(eventinstanceofChatMessageEvent){
  • Messagemsg=(Message)event.getSource();
  • ServerContextcontext=ServerContextFactory.get();
  • //获得客户端所有chat页面scriptsession连接数
  • Collection<ScriptSession>sessions=context.getScriptSessionsByPage(ctx.getcontextpath()+"/chat.jsp");
  • for(ScriptSessionsession:sessions){
  • ScriptBuffersb=newScriptBuffer();
  • Datetime=msg.getTime();
  • Strings=time.getYear()+"-"+(time.getMonth()+1)+"-"+time.getDate()+""
  • +time.getHours()+":"+time.getMinutes()+":"+time.getSeconds();
  • //执行setMessage方法
  • sb.appendScript("showMessage({msg:'")
  • .appendScript(msg.getMsg())
  • .appendScript("',time:'")
  • .appendScript(s)
  • .appendScript("'})");
  • System.out.println(sb.toString());
  • //执行客户端scriptsession方法,相当于浏览器执行JavaScript代码
  • //上面就会执行客户端浏览器中的showMessage方法,并且传递一个对象过去
  • session.addScript(sb);
  • }
  • }
  • }
  • }
  • 上面的代码主要是监听客户端的事件,一旦客户端有触发ApplicationEvent事件或是其子类,就会执行onApplicationEvent方法代码中通过instanceof判断对象实例,然后再执行。如果有触发ChatMessageEvent事件,就获取所有连接chat.jsp这个页面的ScriptSession。然后像所有的ScriptSession中添加script。这样被添加的ScriptSession就会在有连接chat.jsp的页面中执行。

    所以这就是客户端为什么会执行服务器端的JavaScript代码。但前提是需要在web.xml中添加dwrComet配置以及在chat页面添加ajax反转。

    5、 下面开始在bean容器和dwr的配置中添加我们的配置

    applicationContext-beans.xml配置

    beanid="chatService"class="com.hoo.chat.ChatService"/>
       
       
  • beanid="chatMessageClient"class="com.hoo.chat.ChatMessageClient"/>
  • 上面的chatService会在dwr配置中用到

    dwr.xml配置

    allow>
       
       
  • convertmatch="com.hoo.entity.Message"converter="bean"paramname="include"value="msg,time"/>
  • convertcreatecreator="spring"javascript="ChatService"paramname="beanName"value="chatService"create charService的sendMessage方法传递的是Message对象,所以要配置Message对象的convert配置。

    上面的create的creator是spring,表示在spring容器中拿chatService对象。里面的参数的beanName表示在spring容器中找name等于charService的bean对象。

    6、 客户端chat.jsp页面代码

    <%@pagelanguage="java"import="java.util.*"pageEncoding="UTF-8"%<% 
  • Stringpath=request.getcontextpath();
  • StringbasePath=request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
  • %>
  • <!DOCTYPEHTMLPUBLIC"-//W3C//DTDHTML4.01Transitional//EN"htmlheadbasehref="<%=basePath%>"title>ChatMetahttp-equiv="pragma"content="no-cache"Metahttp-equiv="cache-control"content="no-cache"Metahttp-equiv="expires"content="0">
  • scripttype="text/javascript"src="${pageContext.request.contextpath}/dwr/engine.js">scriptscripttype="text/javascript"src="${pageContext.request.contextpath}/dwr/util.js"scripttype="text/javascript"src="${pageContext.request.contextpath}/dwr/interface/ChatService.js"scripttype="text/javascript">
  • functionsend(){
  • vartime=newDate();
  • varcontent=dwr.util.getValue("content");
  • varname=dwr.util.getValue("userName");
  • varinfo=encodeURI(encodeURI(name+"say:\n"+content));
  • varmsg={"msg":info,"time":time};
  • dwr.util.setValue("content","");
  • if(!!content){
  • ChatService.sendMessage(msg);
  • }else{
  • alert("发送的内容不能为空!");
  • }
  • }
  • functionshowMessage(data){
  • varmessage=decodeURI(decodeURI(data.msg));
  • vartext=dwr.util.getValue("info");
  • if(!!text){
  • dwr.util.setValue("info",text+"\n"+data.time+""+message);
  • }else{
  • dwr.util.setValue("info",data.time+""+message);
  • }
  • }
  • >
  • bodyonload="dwr.engine.setActiveReverseAjax(true);"textarearows="20"cols="60"id="info"readonly="readonly"textareahr/>
  • 昵称:inputtype="text"id="userName"/>br/>
  • 消息:textarearows="5"cols="30"id="content"inputtype="button"value="Send"onclick="send()"style="height:85px;width:85px;"/>
  • body 首先,你需要导入dwr的engine.js文件,这个很重要,是dwr的引擎文件。其次你使用的那个类的方法,也需要在导入进来。一般是interface下的,并且在dwr.xml中配置过的create。

    上面的js中调用的charService类中的sendMessage方法,所以在jsp页面中导入的是ChatService.js。

    在body的onload事件中,需要设置反转Ajax,这个很重要。

    showMessage是ChatMessageClient的onApplicationEvent方法中的appendScript中需要执行的方法。data参数也是在那里传递过来的。

    每当发送sendMessage方法后就会触发ChatMessageEvent事件,然后监听的地方就会执行onApplicationEvent方法在这方法中又会执行浏览器中的showMessage方法

    原文链接:http://www.cnblogs.com/hoojo/archive/2011/06/08/2075201.html

  • 相关文章

    IE6是一个非常老旧的网页浏览器,虽然现在很少人再使用它,但...
    PHP中的count()函数是用来计算数组或容器中元素的个数。这个...
    使用 AJAX(Asynchronous JavaScript and XML)技术可以在不...
    Ajax(Asynchronous JavaScript and XML)是一种用于改进网页...
    本文将介绍如何通过AJAX下载Excel文件流。通过AJAX,我们可以...
    Ajax是一种用于客户端和服务器之间的异步通信技术。通过Ajax...