Website Logo. Upload to /source/logo.png ; disable in /source/_includes/logo.html

舞乐 VOLER

舞动我人生

使用DWR实现消息推送

Nov 21, 2015

dwr支持如下3种模式的消息推送:

  • Polling,浏览器每隔一段时间向服务器发出请求,查看是否有更新的内容;
  • Comet,
  • Piggyback,服务器等待浏览器下一次发出请求时,将更新的内容合并到响应一起返回。

默认使用Piggyback模式,使用Polling/Comet模式需要额外的配置。

DWR简单入门介绍了基于DWR构建项目。

使用Maven构建项目,在pom.xml中添加如下配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<repositories>
  <repository>
      <!-- Please consider setting up your own on-site repository proxy such 
          as with Nexus and pointing the url element below at that instead -->
      <id>oss-sonatype-snapshots</id>
      <name>OSS Sonatype Snapshots Repository</name>
      <url>http://oss.sonatype.org/content/repositories/snapshots</url>
      <releases>
          <enabled>false</enabled>
      </releases>
      <snapshots>
          <enabled>true</enabled>
      </snapshots>
  </repository>
</repositories>

<dependencies>
...
  <dependency>
      <groupId>org.directwebremoting</groupId>
      <artifactId>dwr</artifactId>
      <version>3.0.0-SNAPSHOT</version>
  </dependency>
  <dependency>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
      <version>1.2</version>
  </dependency>
    <!-- 使用Tomcat需要额外添加的依赖包 -->
  <dependency>
      <groupId>org.apache.tomcat</groupId>
      <artifactId>catalina</artifactId>
      <version>6.0.44</version>
  </dependency>
</dependencies>

使用的容器是Tomcat(与使用Jetty的配置不同),在web.xml中添加如下配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<servlet>
  <servlet-name>dwr-invoker</servlet-name>
  <servlet-class>org.directwebremoting.server.tomcat.DwrCometProcessor</servlet-class>
  <init-param>
      <param-name>debug</param-name>
      <param-value>true</param-value>
  </init-param>
  <init-param>
      <!-- 2.0 RC3之前的参数名为pollAndCometEnabled -->
      <param-name>activeReverseAjaxEnabled</param-name>
      <param-value>true</param-value>
  </init-param>
  <init-param>
      <param-name>maxWaitAfterWrite</param-name>
      <param-value>1000</param-value>
  </init-param>
</servlet>
<servlet-mapping>
  <servlet-name>dwr-invoker</servlet-name>
  <url-pattern>/dwr/*</url-pattern>
</servlet-mapping>

dwr.xml中的配置

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE dwr PUBLIC
    "-//GetAhead Limited//DTD Direct Web Remoting 3.0//EN"
    "http://getahead.org/dwr/dwr30.dtd">

<dwr>
  <allow>
      <create creator="new" javascript="messagePush">
          <param name="class" value="me.util.MessagePushUtil" />
      </create>
  </allow>
</dwr>

采用的调试方式是,打开接收页面(receiver.jsp)和发送页面(sender.jsp),在发送页面将消息发送至服务端,由服务端将消息推送至接受页面。JSP文件中使用JQuery替代了dwr提供的<script type='text/javascript' src='/dwr/util.js'></script>

接收页面(receiver.jsp)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<%@ page language="java" contentType="text/html; charset=UTF-8"
  pageEncoding="UTF-8"%>
<%@ page isELIgnored="false"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" type="text/css" href="/css/easyui.css">
<link rel="stylesheet" type="text/css" href="/css/demo.css">
<script type="text/javascript" src="/js/jquery.js"></script>
<script type="text/javascript" src="/js/jquery.easyui.min.js"></script>
<script type='text/javascript' src='/dwr/engine.js'></script>
<script type='text/javascript' src='/dwr/interface/messagePush.js'></script>
<title>消息推送</title>
</head>
<body>
  <h2>接受消息推送</h2>
</body>
<script>
  $(function() {
      dwr.engine.setActiveReverseAjax(true);
        dwr.engine.setNotifyServerOnPageUnload(true);
        messagePush.onPageLoad('{sessionScope.uid}');
  });

    function showMessage(autoMessage) {
      $.messager.show({
          title : "消息推送",
          msg : autoMessage,
          showType : 'slide',
          timeout : 5000
      });
  }
</script>
</html>

发送页面(sender.jsp)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<%@ page language="java" contentType="text/html; charset=UTF-8"
  pageEncoding="UTF-8"%>
<%@ page isELIgnored="false"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" type="text/css" href="./css/easyui.css">
<link rel="stylesheet" type="text/css" href="./css/demo.css">
<script type="text/javascript" src="./js/jquery.js"></script>
<script type="text/javascript" src="./js/jquery.easyui.min.js"></script>
<script type='text/javascript' src='/dwr/engine.js'></script>
<script type='text/javascript' src='/dwr/interface/messagePush.js'></script>
<title>消息推送</title>
</head>
<body>
  <h2>发送消息推送</h2>
  <div id="p" class="easyui-panel"
      style="width: 400px; height: 100x; padding: 10px;">
      <div>
          ID:<input id="uid" style="width: 40%; height: 25px">消息:<input
              id="msg" style="width: 40%; height: 25px"><br> <br> <a
              href="javascript:void(0)" style="width: 30%; height: 25px"
              onclick="sendMsg()">发送</a>
      </div>
  </div>
</body>
<script>
  function sendMsg() {
      messagePush.sendMessageAuto($('#uid').val(), $('#msg').val());
      $('#uid').val('');
      $('#msg').val('');
  }
</script>
</html>

me/util/MessagePushUtil.java中的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package me.util;

import java.util.Collection;

import javax.servlet.ServletException;

import org.apache.log4j.Logger;
import org.directwebremoting.Browser;
import org.directwebremoting.ScriptBuffer;
import org.directwebremoting.ScriptSession;
import org.directwebremoting.ScriptSessionFilter;
import org.directwebremoting.WebContextFactory;

import me.message.MessagePushServlet;

public class MessagePushUtil {

  private static final Logger LOGGER = Logger.getLogger(MessagePushUtil.class);

  public void onPageLoad(String userID) {

      ScriptSession scriptSession = WebContextFactory.get().getScriptSession();
      // 工厂方法get()返回WebContext实例,通过WebContext获取servlet参数
      // ScriptSession与HttpSession类似
      scriptSession.setAttribute("uid", userID);

      MessagePushServlet mpServlet = new MessagePushServlet();
      try {
          mpServlet.init();
          LOGGER.info(String.format("消息推送初始化成功,uid:%s", userID));
      } catch (ServletException e) {
          LOGGER.error(String.format("消息推送初始化错误,uid:%s", userID));
      }

  }

  /**
  * 根据userID向指定用户推送消息
  * 
  * @param userID
  * @param autoMessage
  */
  public void sendMessageAuto(String userID, String autoMessage) {

      final String uid = userID;
      final String message = autoMessage;

      Browser.withAllSessionsFiltered(new ScriptSessionFilter() {
          // 实现过滤器中的match()方法
          public boolean match(ScriptSession session) {
              if (session.getAttribute("uid") == null)
                  return false;
              else
                  return (session.getAttribute("uid")).equals(uid);
          }
      }, new Runnable() {

          private ScriptBuffer script = new ScriptBuffer();

          public void run() {
              // 调用JSP中定义的showMessage()方法,实现消息的前端显示
              script.appendCall("showMessage", message);
              Collection<ScriptSession> sessions = Browser.getTargetSessions();
              for (ScriptSession scriptSession : sessions) {
                  scriptSession.addScript(script);
              }
              LOGGER.info(String.format("向用户推送消息,uid:%s,message:%s", uid, message));
          }
      });
  }
}

me/message/MessagePushServlet.java中的内容,覆盖DwrServlet中的init()方法实现ScriptSession监听器,在页面中加入engine.js时,ScriptSession创建,默认由org.directwebremoting.impl.DefaultScriptSessionManager管理,当发生unload事件时,DefaultScriptSessionManager会被通知销毁ScriptSession。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package me.message;

import javax.servlet.ServletException;
import javax.servlet.http.HttpSession;

import org.apache.log4j.Logger;
import org.directwebremoting.Container;
import org.directwebremoting.ServerContextFactory;
import org.directwebremoting.WebContextFactory;
import org.directwebremoting.event.ScriptSessionEvent;
import org.directwebremoting.event.ScriptSessionListener;
import org.directwebremoting.extend.ScriptSessionManager;
import org.directwebremoting.servlet.DwrServlet;

public class MessagePushServlet extends DwrServlet {

  private static final long serialVersionUID = 4298890285665323894L;
  private static final Logger LOGGER = Logger.getLogger(MessagePushServlet.class);

  @Override
  public void init() throws ServletException {

      Container container = ServerContextFactory.get().getContainer();
      // 工厂方法get()返回ServerContext实例
      ScriptSessionManager manager = container.getBean(ScriptSessionManager.class);
      ScriptSessionListener listener = new ScriptSessionListener() {
          public void sessionCreated(ScriptSessionEvent ev) {
              HttpSession session = WebContextFactory.get().getSession();

              String userID = (String) session.getAttribute("uid");
              LOGGER.info("a ScriptSession is created");
              ev.getSession().setAttribute("uid", userID);
          }

          public void sessionDestroyed(ScriptSessionEvent ev) {
              LOGGER.info("a ScriptSession is distroyed");
          }
      };
      manager.addScriptSessionListener(listener);
  }
}

运行前还要修改Tomcat下的server.xml中的配置

1
2
3
4
5
6
<!--
<Connector connectionTimeout="20000" port="8080" 
protocol="HTTP/1.1" redirectPort="8443"/>
-->
<Connector URIEncoding="UTF-8" connectionTimeout="20000" port="8080"
protocol="org.apache.coyote.http11.Http11NioProtocol" redirectPort="8443"/>

运行项目,打开http://localhost:8080/receiver.jsp,页面加载时,调用

messagePush.onPageLoad(‘<%=session.getAttribute(“uid”)%>’);

调用服务端的me.util.MessagePushUtil.onPageLoad(String userID)方法,初始化成功(可以使用字符串,如"123456789",替换'<%=session.getAttribute(“uid”)%>‘)。打开http://localhost:8080/sender.jsp,填写刚才的uid(123456789)和消息内容,点击发送,调用

messagePush.sendMessageAuto($(‘#uid’).val(), $(‘#msg’).val());

调用服务端的me.util.MessagePushUtil.sendMessageAuto(String userID, String autoMessage)方法,调用receiver.jsp的showMessage(autoMessage)方法,可以在接收页面看到来自服务端推送的消息。

页面预览如下,

发送页面 接收页面

除此之外,在log4j.properties中添加类似的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
log4j.rootCategory=INFO, console
log4j.category.org.directwebremoting.log=INFO, dwr, console

log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%5p] %d{yyyy-MM-dd HH:mm:ss} %l - %m%n

log4j.appender.dwr=org.apache.log4j.RollingFileAppender
log4j.appender.dwr.File=../logs/dwr.log
log4j.appender.dwr.MaxFileSize=100KB
log4j.appender.dwr.MaxBackupIndex=1
log4j.appender.dwr.layout=org.apache.log4j.PatternLayout
log4j.appender.dwr.layout.ConversionPattern=[%5p] %d{yyyy-MM-dd HH:mm:ss} %l - %m%n
参考资料

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

DWR简单入门

Reverse Ajax

Logging