基于dwr2.0的Push推送技术详细解析以及实例

DWR从2.0开始增加了push功能,也就是在异步传输的情况下可以从Web-Server端发送数据到
Browser.
我们知道,Web的访问机制天生是设计用来pull数据的,也就是只允许Browser端主动发起请求,server
是被动的响应.不允许Server向Browser发出一个connection请求,也就是说没有为server向Browser
push数据提供设计实现.
虽然没有直接的实现方法,却可以使用一些变通的方式完成类似的功能:
1. Polling
Polling其实就是轮询,是通过Browser在一个相对短的间隔时间内,反复向Server发出请求,然
后更新页面,这种方式没有什么新鲜的,只是需要浏览器端做一些工作就可以,哪怕没有太多服务器端的配
置也没问题.轮询的方式对于服务器来说会依据不同的访问间隔而产生不同程度的额外负载,因为每次访
问都有重新建立连接的过程.
2. Comet
Comet方式通俗的说就是一种长连接机制(long lived http).同样是由Browser端主动发起请
求,但是Server端以一种似乎非常慢的响应方式给出回答,这样在这个期间内,服务器端可以使用同一个
connection把要更新的数据主动发送给Browser.Comet又有很多中实现方式,但是总的来说对Server
端的负载都会有增加.虽然对于单位操作来说,每次只需要建议一次connection,但是由于connection是
保持较长时间的,对于server端的资源的占用要有所增加.
3. Piggyback
Piggyback方式是一种半主动的方式,也就是说还是由Browser主动发出请求,但是每次请求的
响应中除了当次的响应之外,还会把上次请求以来已经发生的变化同时发给Browser.也就是说,当次请
求的更新会搭载到下一次请求的响应中一并发回.这样,在Browser的感觉就好象上一次请求又有了更
新.但是这种感觉取决于Browser向Server发出请求的频度.如果,第二次请求迟迟没有发出,那么上一次
的更新就不会取到.
在DWR2.0中可以使用Active(主动) 和 Passive(被动)两种工作模式,在这里我们主要讨论
Active(主动)模式.Active(主动)模式又分为以下3种:
• Full Streaming Mode
• Early Closing Mode
• Polling Mode
Full Streaming Mode
这是Active模式下的一种默认配置,具有很快的响应速度,而且建立好的链接只有每60秒检查一次浏
览器是否是活跃的.这种工作模式的配置非常简单,在Web.xml中配置DWR的时候,加上下面的内容:

<servlet>
<servlet-name>dwr-invoker</servlet-name>
<servlet-class>org.directwebremoting.servlet.DwrServlet</servlet-class>
<init-param>
<param-name>activeReverseAjaxEnabled</param-name>
<param-value>true</param-value>
</init-param>
</servlet>

然后在Browser页面端加上下面一句就可以了:

dwr.engine.setActiveReverseAjax(true);

需要说明的是,长链接会增加Server的资源占用,有些Server比如Jetty允许在客户端关闭线程

(connection),在新版本中会把这种能力延伸到GlassFish 和Tomcat.总之,DWR的主导思想是尽

量保护Server,减小负载.

Early Closing Mode

在Browser和Server之间有Proxy或者mod_jk的情况下,需要能够良好的工作,需要这种模式:这种

模式和Full Streaming Mode相似,以Full模式开启connection,但是,如果没有输出的情况下,

它会在一个配置好的时间内关闭Connection,通常这个时间是60秒.

从2.04版开始,DWR默认使用Early Closing Mode,如果要要想使用Full Streaming Mode,需

要进行如下的配置:

<init-param>
<param-name>maxWaitAfterWrite</param-name>
<param-value>-1</param-value>
</init-param>

这里,设置maxWaitAfterWrite是-1,表示这个时间和Full Streaming Mode一样,设置关闭时间是60

秒.

Polling Mode

Polling Mode 是一种轮询方式,这可以避免长时间保持连接而产生的对服务器资源的占用.如果要是用

轮询方式,还需要做以下的配置:

<init-param>
<param-name>org.directwebremoting.extend.ServerLoadMonitor</param-name>
<param-value>org.directwebremoting.impl.PollingServerLoadMonitor</param-value>
</init-param>
<init-param>
<param-name>disconnectedTime</param-name>
<param-value>60000</param-value>
</init-param>

这是将轮询周期改为6000毫秒,也就是6秒

让Web具备了Push的方式,这对于很多应用是梦寐以求的,比如,如果有一个基于Web的网络聊天系统,
如果使用Push技术可以更加满足功能的需要,还有比如说一些需要server端根据数据条件主动向
browser端发送数据的应用需求,都非常需要这样的功能.
下面就举一个股票报盘的例子,能够让Server端通过主动的方式想Browser端发送股票信息.

先说一下所需jar包:dwr.jar commons-logging.jar
然后介绍如何配置:
1. 在web.xml中配置如下内容:

<servlet>
<servlet-name>dwr-invoker</servlet-name>
<servlet-class>org.directwebremoting.servlet.DwrServlet</servlet-class>
<init-param>
<param-name>activeReverseAjaxEnabled</param-name>
<param-value>true</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>dwr-invoker</servlet-name>
<url-pattern>/dwr/*</url-pattern>
</servlet-mapping>

2. 在dwr.xml中配置如下内容:

<dwr>
<allow>
<!-- Reverse Ajax Stock push Demo Config -->
<create creator="new" javascript="StocksPusher">
<param name="class" value="dwr.reverse.StocksPusher"/>
</create>
</allow>
</dwr>

3. 股票报盘的页面getStockInfo.html

<!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" />
<title>DWR Reverse Ajax Demo : Show Stock info</title>
<link rel="stylesheet" type="text/css" href="generic.css" />
<script type='text/javascript' src='/AjaxShow/dwr/engine.js'></script>
<script type='text/javascript' src='/AjaxShow/dwr/util.js'></script>
<script type='text/javascript' src='/AjaxShow/dwr/interface/
StocksPusher.js'></script>
<script type="text/javascript">
function beginShow() {
StocksPusher.beginShow();
StocksPusher.sendStocks();
}
function endShow(){
StocksPusher.closeShow();
}
</script>
</head>
<body onload="dwr.engine.setActiveReverseAjax(true);">
<h3>使用DWR Reverse Ajax进行股票报盘</h3>
<p>下面显示的股票信息是可以动态变化的</p>
<input type="button" value="开市..." onclick="beginShow()"/>
=========================
<input type="button" value="闭市..." onclick="endShow()"/>
<hr>
<table style="500px" border="0" cellpadding="0">
<tr>
<td class="headName" ><b>Stock Name</b></td>
<td class="headValue" ><b>Stock Value</b></td>
</tr>
<tr><td>中移动</td><td><div id="zyd">wait...</div></td></tr>
<tr><td>中石化</td><td><div id="zsh">wait...</div></td></tr>
<tr><td>中石油</td><td><div id="zsy">wait...</div></td></tr>
<tr><td>海尔电器</td><td><div id="hedq">wait...</div></td></tr>
<tr><td>冀东水泥</td><td><div id="jdsn">wait...</div></td></tr>
<tr><td>用友软件</td><td><div id="yyrj">wait...</div></td></tr>
<tr><td>柳钢股份</td><td><div id="lggf">wait...</div></td></tr>
<tr><td>招商银行</td><td><div id="zsyh">wait...</div></td></tr>
<tr><td>中国铁建</td><td><div id="zgtj">wait...</div></td></tr>
<tr><td>深发展</td><td><div id="sfz">wait...</div></td></tr>
<tr><td>金山软件</td><td><div id="jsrj">wait...</div></td></tr>
<tr><td>大连实德</td><td><div id="dlsd">wait...</div></td></tr>
<tr><td>九寨沟</td><td><div id="jzg">wait...</div></td></tr>
<tr><td>中国平安</td><td><div id="zgpa">wait...</div></td></tr>
<tr><td>工商银行</td><td><div id="gsyh">wait...</div></td></tr>
<tr><td>鞍钢股份</td><td><div id="aggf">wait...</div></td></tr>
<tr><td>中国航天</td><td><div id="zght">wait...</div></td></tr>
</table>
<br>
</body>
</html>

4. 报盘的主程序StocksPusher.java ,关键部分在代码后面有中文注释

package dwr.reverse;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.directwebremoting.WebContext;
import org.directwebremoting.WebContextFactory;
import org.directwebremoting.proxy.dwr.Util;
import org.directwebremoting.util.Logger;
/**
* Reverse Ajax class.
*
* @author Henry Huang
*/
public class StocksPusher {
private static boolean closeMarket = false;
/**
* Initialize the stocklist with values.
*/
public StocksPusher() {
}
/**
* Send the Stock-Values to the file "getStockInfo.html"
*/
public void sendStocks() throws InterruptedException {
WebContext wctx = WebContextFactory.get(); //这里是获取WebContext上下文
String currentPage = wctx.getCurrentPage(); //从上下文中获取当前页面,这些是DWR
Reverse Ajax 要求的必须方式
Collection sessions = wctx.getScriptSessionsByPage(currentPage); //再一个page中
可能存在多个ScriptSessions,
Util utilAll = new Util(sessions); //Util 是DWR 在Server端模拟Brower端 dwr.util.js
的类, Engine也是
while(true){
Thread.sleep(500);
if(closeMarket) break;
StocksBean stock = StockPriceTracer.getNextStockInfo();
utilAll.setValue(stock.getStock(), stock.getValue()); //这里的setValue()用法和
dwr.util.js中的setValue()函数用法完全一样,第一个参数是页面Element的id ,第二个参数是对该id
赋的新值
System.out.println("Pushing stock: " + stock.getStock() + " = " +
stock.getValue());
}
}
public void beginShow(){
closeMarket = false;
}
public void closeShow(){
closeMarket = true;
}
}

5. 还有一个类是为了模拟实时获取股票信息的工具StockPriceTracer.java,也可能是访问数据库,

也可能来至卫星的大盘数据,等等,这个类是用随机的方法获得股票价格:

package dwr.reverse;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Stack;
/**
* Reverse Ajax class.
*
* @author Henry Huang
*/
public class StockPriceTracer {
private static StockPriceTracer tracer = null;
private List<StocksBean> stocks = new ArrayList<StocksBean>();
private Stack<StocksBean> cycleStack = new Stack<StocksBean>();
private StockPriceTracer(){
stocks.add(new StocksBean("zsy", "36.55"));
stocks.add(new StocksBean("dlsd", "91.01"));
stocks.add(new StocksBean("zsh", "22.59"));
stocks.add(new StocksBean("lggf", "5.07"));
stocks.add(new StocksBean("hedq", "71.77"));
stocks.add(new StocksBean("jdsn", "31.61"));
stocks.add(new StocksBean("yyrj", "51.29"));
stocks.add(new StocksBean("zsyh", "52.70"));
stocks.add(new StocksBean("zgtj", "16.96"));
stocks.add(new StocksBean("sfz", "54.34"));
stocks.add(new StocksBean("jsrj", "178.48"));
stocks.add(new StocksBean("zyd", "134.48"));
stocks.add(new StocksBean("jzg", "76.32"));
stocks.add(new StocksBean("zgpa", "80.63"));
stocks.add(new StocksBean("gsyh", "18.79"));
stocks.add(new StocksBean("aggf", "20.19"));
stocks.add(new StocksBean("zght", "11.13"));
}
public static StocksBean getNextStockInfo(){
if(null == tracer) tracer = new StockPriceTracer();
if(tracer.cycleStack.empty()) tracer.cycleStack.addAll(tracer.stocks);
StocksBean tmp = tracer.cycleStack.pop();
tmp.setValue(tracer.getRandomPrice(tmp.getValue()));
return tmp;
}
private String getRandomPrice(String current){
float fcurrent = 0.0F;
try{
fcurrent = Float.parseFloat(current);
}catch(NumberFormatException e){
fcurrent = 0.01F;
}
Random rdm = new Random();
float tmp = fcurrent + rdm.nextFloat();
return String.valueOf(tmp);
}
}

6. 还有一个类是一个JavaBeanStockBean.java

package dwr.reverse;
public class StocksBean {
private String stock = "";
private String value = "";
public StocksBean(String stock, String value) {
this.setStock(stock);
this.setValue(value);
}
public String getStock() {
return stock;
}
public void setStock(String stock) {
this.stock = stock;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}