`
yiheng
  • 浏览: 156666 次
社区版块
存档分类

Spring整合DWR comet 实现无刷新 多人聊天室

 
阅读更多

用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

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包如下

image

2、 建立一个WebProject,名称DWRComet

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

<-- 加载Spring容器配置 -->
<!--CRLF-->
<listener>
<!--CRLF-->
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
<!--CRLF-->
</listener>
<!--CRLF-->

<!--CRLF-->

    
<-- 设置Spring容器加载配置文件路径 -->
<!--CRLF-->
<context-param>
<!--CRLF-->
    <param-name>contextConfigLocation</param-name>
<!--CRLF-->
    <param-value>classpath*:applicationContext-*.xml</param-value>
<!--CRLF-->
</context-param>
<!--CRLF-->

<!--CRLF-->

    
<listener>
<!--CRLF-->
    <listener-class>org.directwebremoting.servlet.DwrListener</listener-class>
<!--CRLF-->
</listener>
<!--CRLF-->

<!--CRLF-->

    
<servlet>
<!--CRLF-->
    <servlet-name>dwr-invoker</servlet-name>
<!--CRLF-->
    <servlet-class>org.directwebremoting.servlet.DwrServlet</servlet-class>
<!--CRLF-->
    <init-param>
<!--CRLF-->
        <param-name>debug</param-name>
<!--CRLF-->
        <param-value>true</param-value>
<!--CRLF-->
    </init-param>
<!--CRLF-->
        
<!--CRLF-->
    <-- dwr的comet控制 -->
<!--CRLF-->
    <init-param>
<!--CRLF-->
      <param-name>pollAndCometEnabled</param-name>
<!--CRLF-->
      <param-value>true</param-value>
<!--CRLF-->
    </init-param>
<!--CRLF-->
</servlet>
<!--CRLF-->

<!--CRLF-->

    
<servlet-mapping>
<!--CRLF-->
    <servlet-name>dwr-invoker</servlet-name>
<!--CRLF-->
    <url-pattern>/dwr/*</url-pattern>
<!--CRLF-->
</servlet-mapping>
<!--CRLF-->

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

<?xml version="1.0" encoding="UTF-8"?>
<!--CRLF-->
<beans xmlns="http://www.springframework.org/schema/beans"
<!--CRLF-->
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
<!--CRLF-->
    xmlns:aop="http://www.springframework.org/schema/aop"
<!--CRLF-->
    xmlns:tx="http://www.springframework.org/schema/tx" 
<!--CRLF-->
    xmlns:util="http://www.springframework.org/schema/util"
<!--CRLF-->
    xmlns:context="http://www.springframework.org/schema/context"
<!--CRLF-->
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
<!--CRLF-->
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
<!--CRLF-->
    http://www.springframework.org/schema/aop 
<!--CRLF-->
    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
<!--CRLF-->
    http://www.springframework.org/schema/tx 
<!--CRLF-->
    http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
<!--CRLF-->
    http://www.springframework.org/schema/util 
<!--CRLF-->
    http://www.springframework.org/schema/util/spring-util-3.0.xsd
<!--CRLF-->
    http://www.springframework.org/schema/context 
<!--CRLF-->
    http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!--CRLF-->
</beans>
<!--CRLF-->

4、 在WEB-INF目录添加dwr.xml文件,基本代码如下

<?xml version="1.0" encoding="UTF-8"?>
<!--CRLF-->
<!DOCTYPE dwr PUBLIC "-//GetAhead Limited//DTD Direct Web Remoting 3.0//EN" "http://getahead.org/dwr/dwr30.dtd">
<!--CRLF-->
<dwr>
<!--CRLF-->
</dwr>
<!--CRLF-->

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

二、聊天室相关业务实现

1、 聊天实体类Model

package com.hoo.entity;
<!--CRLF-->

<!--CRLF-->

    
import java.util.Date;
<!--CRLF-->

<!--CRLF-->

    
/**
<!--CRLF-->
 * <b>function:</b>
<!--CRLF-->
 * @author hoojo
<!--CRLF-->
 * @createDate 2011-6-3 下午06:40:07
<!--CRLF-->
 * @file Message.java
<!--CRLF-->
 * @package com.hoo.entity
<!--CRLF-->
 * @project DWRComet
<!--CRLF-->
 * @blog http://blog.csdn.net/IBM_hoojo
<!--CRLF-->
 * @email hoojo_@126.com
<!--CRLF-->
 * @version 1.0
<!--CRLF-->
 */
<!--CRLF-->
public class Message {
<!--CRLF-->
    private int id;
<!--CRLF-->
    private String msg;
<!--CRLF-->
    private Date time;
<!--CRLF-->
    //getter、setter
<!--CRLF-->
}
<!--CRLF-->

2、 编写聊天信息的事件

package com.hoo.chat;
<!--CRLF-->

<!--CRLF-->

    
import org.springframework.context.ApplicationEvent;
<!--CRLF-->

<!--CRLF-->

    
/**
<!--CRLF-->
 * <b>function:</b>发送聊天信息事件
<!--CRLF-->
 * @author hoojo
<!--CRLF-->
 * @createDate 2011-6-7 上午11:24:21
<!--CRLF-->
 * @file MessageEvent.java
<!--CRLF-->
 * @package com.hoo.util
<!--CRLF-->
 * @project DWRComet
<!--CRLF-->
 * @blog http://blog.csdn.net/IBM_hoojo
<!--CRLF-->
 * @email hoojo_@126.com
<!--CRLF-->
 * @version 1.0
<!--CRLF-->
 */
<!--CRLF-->
public class ChatMessageEvent extends ApplicationEvent {
<!--CRLF-->

<!--CRLF-->

    
    private static final long serialVersionUID = 1L;
<!--CRLF-->

<!--CRLF-->

    
    public ChatMessageEvent(Object source) {
<!--CRLF-->
        super(source);
<!--CRLF-->
    }
<!--CRLF-->
}
<!--CRLF-->

继承ApplicationEvent,构造参数用于传递发送过来的消息。这个事件需要一个监听器监听,一旦触发了这个事件,我们就可以向客户端发送消息。

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

package com.hoo.chat;
<!--CRLF-->

<!--CRLF-->

    
import org.springframework.beans.BeansException;
<!--CRLF-->
import org.springframework.context.ApplicationContext;
<!--CRLF-->
import org.springframework.context.ApplicationContextAware;
<!--CRLF-->
import com.hoo.entity.Message;
<!--CRLF-->

<!--CRLF-->

    
/**
<!--CRLF-->
 * <b>function:</b>客户端发消息服务类业务
<!--CRLF-->
 * @author hoojo
<!--CRLF-->
 * @createDate 2011-6-7 下午02:12:47
<!--CRLF-->
 * @file ChatService.java
<!--CRLF-->
 * @package com.hoo.chat
<!--CRLF-->
 * @project DWRComet
<!--CRLF-->
 * @blog http://blog.csdn.net/IBM_hoojo
<!--CRLF-->
 * @email hoojo_@126.com
<!--CRLF-->
 * @version 1.0
<!--CRLF-->
 */
<!--CRLF-->
public class ChatService implements ApplicationContextAware {
<!--CRLF-->
    private ApplicationContext ctx;
<!--CRLF-->
    public void setApplicationContext(ApplicationContext ctx) throws BeansException {
<!--CRLF-->
        this.ctx = ctx;
<!--CRLF-->
    }
<!--CRLF-->
    
<!--CRLF-->
    /**
<!--CRLF-->
     * <b>function:</b> 向服务器发送信息,服务器端监听ChatMessageEvent事件,当有事件触发就向所有客户端发送信息
<!--CRLF-->
     * @author hoojo
<!--CRLF-->
     * @createDate 2011-6-8 下午12:37:24
<!--CRLF-->
     * @param msg
<!--CRLF-->
     */
<!--CRLF-->
    public void sendMessage(Message msg) {
<!--CRLF-->
        //发布事件
<!--CRLF-->
        ctx.publishEvent(new ChatMessageEvent(msg));
<!--CRLF-->
    }
<!--CRLF-->
}
<!--CRLF-->

上面的sendMessage需要浏览器客户端调用此方法完成消息的发布,传递一个Message对象,并且是触发ChatMessageEvent事件。

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

package com.hoo.chat;
<!--CRLF-->

<!--CRLF-->

    
import java.util.Collection;
<!--CRLF-->
import java.util.Date;
<!--CRLF-->
import javax.servlet.ServletContext;
<!--CRLF-->
import org.directwebremoting.ScriptBuffer;
<!--CRLF-->
import org.directwebremoting.ScriptSession;
<!--CRLF-->
import org.directwebremoting.ServerContext;
<!--CRLF-->
import org.directwebremoting.ServerContextFactory;
<!--CRLF-->
import org.springframework.context.ApplicationEvent;
<!--CRLF-->
import org.springframework.context.ApplicationListener;
<!--CRLF-->
import org.springframework.web.context.ServletContextAware;
<!--CRLF-->
import com.hoo.entity.Message;
<!--CRLF-->

<!--CRLF-->

    
/**
<!--CRLF-->
 * <b>function:</b>监听客户端事件,想客户端推出消息
<!--CRLF-->
 * @author hoojo
<!--CRLF-->
 * @createDate 2011-6-7 上午11:33:08
<!--CRLF-->
 * @file SendMessageClient.java
<!--CRLF-->
 * @package com.hoo.util
<!--CRLF-->
 * @project DWRComet
<!--CRLF-->
 * @blog http://blog.csdn.net/IBM_hoojo
<!--CRLF-->
 * @email hoojo_@126.com
<!--CRLF-->
 * @version 1.0
<!--CRLF-->
 */
<!--CRLF-->
@SuppressWarnings("unchecked")
<!--CRLF-->
public class ChatMessageClient implements ApplicationListener, ServletContextAware {
<!--CRLF-->
    
<!--CRLF-->
    private ServletContext ctx;
<!--CRLF-->
    public void setServletContext(ServletContext ctx) {
<!--CRLF-->
        this.ctx = ctx;
<!--CRLF-->
    }
<!--CRLF-->
    
<!--CRLF-->
    @SuppressWarnings("deprecation")
<!--CRLF-->
    public void onApplicationEvent(ApplicationEvent event) {
<!--CRLF-->
        //如果事件类型是ChatMessageEvent就执行下面操作
<!--CRLF-->
        if (event instanceof ChatMessageEvent) {
<!--CRLF-->
            Message msg = (Message) event.getSource();
<!--CRLF-->
            ServerContext context = ServerContextFactory.get();
<!--CRLF-->
            //获得客户端所有chat页面script session连接数
<!--CRLF-->

<!--CRLF-->

    
            Collection<ScriptSession> sessions = context.getScriptSessionsByPage(ctx.getContextPath() + "/chat.jsp");
<!--CRLF-->
            for (ScriptSession session : sessions) {
<!--CRLF-->
                ScriptBuffer sb = new ScriptBuffer();
<!--CRLF-->
                Date time = msg.getTime();
<!--CRLF-->
                String s = time.getYear() + "-" + (time.getMonth() + 1) + "-" +  time.getDate() + " " 
<!--CRLF-->
                        +  time.getHours() + ":" + time.getMinutes() + ":" + time.getSeconds();
<!--CRLF-->
                //执行setMessage方法
<!--CRLF-->

<!--CRLF-->

    
                sb.appendScript("showMessage({msg: '")
<!--CRLF-->
                .appendScript(msg.getMsg())
<!--CRLF-->
                .appendScript("', time: '")
<!--CRLF-->
                .appendScript(s)
<!--CRLF-->
                .appendScript("'})");
<!--CRLF-->
                System.out.println(sb.toString());
<!--CRLF-->
                //执行客户端script session方法,相当于浏览器执行JavaScript代码
<!--CRLF-->
                  //上面就会执行客户端浏览器中的showMessage方法,并且传递一个对象过去
<!--CRLF-->

<!--CRLF-->

    
                session.addScript(sb);
<!--CRLF-->
            }
<!--CRLF-->
        }
<!--CRLF-->
    }
<!--CRLF-->
}
<!--CRLF-->

上面的代码主要是监听客户端的事件,一旦客户端有触发ApplicationEvent事件或是其子类,就会执行onApplicationEvent方法。代码中通过instanceof判断对象实例,然后再执行。如果有触发ChatMessageEvent事件,就获取所有连接chat.jsp这个页面的ScriptSession。然后像所有的ScriptSession中添加script。这样被添加的ScriptSession就会在有连接chat.jsp的页面中执行。

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

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

applicationContext-beans.xml配置

<bean id="chatService" class="com.hoo.chat.ChatService"/>
<!--CRLF-->
<bean id="chatMessageClient" class="com.hoo.chat.ChatMessageClient"/>
<!--CRLF-->

上面的chatService会在dwr配置中用到

dwr.xml配置

<allow>
<!--CRLF-->
    <convert match="com.hoo.entity.Message" converter="bean">
<!--CRLF-->
        <param name="include" value="msg,time" />
<!--CRLF-->
    </convert>
<!--CRLF-->

<!--CRLF-->

    
    <create creator="spring" javascript="ChatService">
<!--CRLF-->
        <param name="beanName" value="chatService" />
<!--CRLF-->
    </create>
<!--CRLF-->
</allow>
<!--CRLF-->

charService的sendMessage方法传递的是Message对象,所以要配置Message对象的convert配置。

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

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

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!--CRLF-->
<%
<!--CRLF-->
String path = request.getContextPath();
<!--CRLF-->
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
<!--CRLF-->
%>
<!--CRLF-->

<!--CRLF-->

    
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!--CRLF-->
<html>
<!--CRLF-->
  <head>
<!--CRLF-->
    <base href="<%=basePath%>">
<!--CRLF-->
    
<!--CRLF-->
    <title>Chat</title>
<!--CRLF-->
    
<!--CRLF-->
    <meta http-equiv="pragma" content="no-cache">
<!--CRLF-->
    <meta http-equiv="cache-control" content="no-cache">
<!--CRLF-->
    <meta http-equiv="expires" content="0">    
<!--CRLF-->
    <script type="text/javascript" src="${pageContext.request.contextPath }/dwr/engine.js"></script>
<!--CRLF-->
    <script type="text/javascript" src="${pageContext.request.contextPath }/dwr/util.js"></script>
<!--CRLF-->
    <script type="text/javascript" src="${pageContext.request.contextPath }/dwr/interface/ChatService.js"></script>
<!--CRLF-->
    <script type="text/javascript">
<!--CRLF-->
        function send() {
<!--CRLF-->
            var time = new Date();
<!--CRLF-->
            var content = dwr.util.getValue("content");
<!--CRLF-->
            var name = dwr.util.getValue("userName");
<!--CRLF-->
            var info = encodeURI(encodeURI(name + " say:/n" + content));
<!--CRLF-->
            var msg = {"msg": info, "time": time};
<!--CRLF-->
            dwr.util.setValue("content", "");
<!--CRLF-->
            if (!!content) {
<!--CRLF-->
                ChatService.sendMessage(msg);
<!--CRLF-->
            } else {
<!--CRLF-->
                alert("发送的内容不能为空!");
<!--CRLF-->
            }
<!--CRLF-->
        }
<!--CRLF-->

<!--CRLF-->

    
        function showMessage(data) {
<!--CRLF-->
            var message = decodeURI(decodeURI(data.msg));
<!--CRLF-->
            var text = dwr.util.getValue("info");
<!--CRLF-->
            if (!!text) {  
<!--CRLF-->
                dwr.util.setValue("info", text + "/n" + data.time + "  " + message);
<!--CRLF-->
            } else {
<!--CRLF-->
                dwr.util.setValue("info", data.time + "  " + message);
<!--CRLF-->
            }
<!--CRLF-->
        }
<!--CRLF-->
    </script>
<!--CRLF-->
  </head>
<!--CRLF-->
  
<!--CRLF-->
  <body onload="dwr.engine.setActiveReverseAjax(true);">
<!--CRLF-->
      <textarea rows="20" cols="60" id="info" readonly="readonly"></textarea>
<!--CRLF-->
      <hr/>
<!--CRLF-->
      昵称:<input type="text" id="userName"/><br/>
<!--CRLF-->
      消息:<textarea rows="5" cols="30" id="content"></textarea>
<!--CRLF-->
      <input type="button" value=" Send " onclick="send()" style="height: 85px; width: 85px;"/>
<!--CRLF-->
  </body>
<!--CRLF-->
</html>
<!--CRLF-->

首先,你需要导入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方法。

image

分享到:
评论

相关推荐

    Spring整合DWR comet 实现无刷新 多人聊天室代码整理

    Spring整合DWR(Direct Web Remoting)和Comet技术,是一种高效的实现Web应用程序实时通信的解决方案,特别适用于创建如多人聊天室这样的实时交互应用。在这个项目中,Spring作为后端框架,负责业务逻辑处理和控制...

    DWR学习资料

    :DWR 3.0 上传文件.txt DWR3.0反向Ajax示例.txt DWR3.0学习笔记.txt DWR3.0学习网址.txt dwr分页.doc DWR分页代码.doc DWR中文文档.doc DWR中文文档.pdf dwr做comet的完整实现.doc Spring整合DWR comet 实现无刷新 ...

    用DWR的comet推,实现多人聊天室

    【描述】: 本文主要探讨如何通过Spring与Direct Web Remoting (DWR)框架的整合,利用Comet技术来构建一个无需刷新页面的多人在线聊天室。Comet是一种实现服务器到客户端实时通信的技术,它解决了传统的Ajax轮询带来...

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

    标题中的“用DWR comet+Spring实现服务器推送的例子--网页聊天室”涉及到的是Web开发中的实时通信技术。DWR(Direct Web Remoting)是一个开源Java库,它允许JavaScript在浏览器和服务器之间进行直接的、安全的远程...

    dwr comet 使用示例,使用spring作为后台的管理容器

    在这个示例中,我们将探讨如何结合DWR 3.0和Spring 2.5框架来实现Comet交互。** **1. DWR(Direct Web Remoting)** DWR使得Web开发者能够轻松地在浏览器和服务器之间交换数据。它通过创建JavaScript对象,这些对象...

    DWR3实现服务器端向客户端精确推送消息

    DWR3的消息推送功能在实时应用,如聊天室、股票报价、在线游戏等场景中非常有用。它减少了延迟,提高了用户体验。然而,需要注意的是,由于Comet技术基于长连接,可能会对服务器负载和并发性产生影响,因此在设计和...

    DWR推送技术大全 dwr推送聊天实例

    - **聊天室接口**:服务器端定义了一个或多个处理聊天消息的Java类,这些类提供了添加、获取和广播消息的方法。 - **JavaScript接口**:DWR自动生成了与服务器端接口对应的JavaScript API,前端可以直接调用这些API...

    DWR的Comet测试,又称反Ajax(ReverseAjax),使用DWR3.0 + Spring 2.5

    comet.jsp就是聊天室接收消息的界面,action.jsp就是发送消息的界面,为了更好的说明问题,将接收界面和发送界面放在两个窗口中, 测试时同时可以打开多个接收界面和发送界面, 执行发送界面的发送按钮会发现在所有...

    DWR最新中文参考手册

    3. **逆向AJAX(Reverse AJAX)**:DWR支持服务器向客户端推送数据,即所谓的长轮询或Comet技术,使得服务器能够主动更新客户端的状态,例如实时股票报价或聊天室功能。 4. **兼容性**:DWR考虑到了不同浏览器的兼容...

Global site tag (gtag.js) - Google Analytics