在开始本节内容之前,先来了解一下什么是服务器推送技术和DWR的推送方式。
1.服务器推送技术和DWR的推送方式
传统模式的 Web 系统以客户端发出请求、服务器端响应的方式工作,服务端不能主动发送请求(消息)给客户端。
这种方式并不能满足很多现实应用的需求,譬如:
监控系统:后台硬件热插拔、LED、温度、电压发生变化;
而基于客户端套接口的“服务器推”技术大体可以分为:
1.传统轮询:在 Web 早期,这一点常使用 Meta 刷新实现。这将自动指示浏览器在指定秒数之后重新装载页面,从而支持简陋的轮询( polling )。例如在 HTML 文件中加入 <Meta HTTP-RQUIV="Refresh" CONTENT=12> ,实际上就是 HTTP 头标告知浏览器每 12 秒更新一次文档。
优点:不需要服务器端配置
缺点:用户体验度差
对服务器的压力很大,带宽流失严重
2.ajax轮询:Ajax 隔一段时间(通常使用 JavaScript 的 setTimeout 函数)就去服务器查询是否有改变,从而进行增量式的更新。但是间隔多长时间去查询成了问题,因为性能和即时性造成了严重的反比关系。间隔太短,连续不断的请求会冲垮服务器,间隔太长,务器上的新数据就需要越多的时间才能到达客户机。
优点:不需要太多的服务器端配置
降低了带宽的负荷(因为返回的不是完整的页面)
缺点:对服务器的压力并没有减少
实时性差,有一定的延迟
3.comet:Comet 方式通俗的说就是一种长连接机制 (long lived http) 。同样是由 Browser 端主动发起请求,但是 Server 端以一种似乎非常慢的响应方式给出回答。这样在这个期间内,服务器端可以使用同一个 connection 把要更新的数据主动发送给 Browser 。因此请求可能等待较长的时间,期间没有任何数据返回,但是一旦有了新的数据,它将立即被发送到客户机。Comet 又有很多种实现方式,但是总的来说对 Server 端的负载都会有增加 . 虽然对于单位操作来说,每次只需要建议一次 connection,但是由于 connection 是保持较长时间的,对于 server 端的资源的占用要有所增加。
缺点:长期占用连接,丧失了无状态高并发的特点。
应用:股票系统。实时通讯。
4.flash xml socket:这种方案实现的基础是:一、 Flash 提供了 XMLSocket 类。二、 JavaScript 和 Flash 的紧密结合:在 JavaScript 可以直接调用 Flash 程序提供的接口。
因为是使用套接口,需要设置一个通信端口,防火墙、代理服务器也可能对非 HTTP 通道端口进行限制;
应用:网络聊天室,网络互动游戏。
5.java appet:在客户端使用 Java Applet ,通过 java.net.Socket 或 java.net.DatagramSocket 或 java.net.MulticastSocket 建立与服务器端的套接口连接,从而实现 “ 服务器推送 ” 。
DWR采用的是长连接机制。
1、长连接技术通过客户端发出请求获取服务器端数据的方式通常称为"拉"技术,很形象说明客户端在拉取服务器端数据,而有时候需要服务器端主动向客户端"推"数据,比如监测聊天上线人数主动向上线发送消息,后台数据库发生变化是主动更新所有客户端展示。
2、ReverseAjax实现服务器推技术DWR2.x的推技术也叫DWRReverseAjax(逆向Ajax)主要是在BS架构中,从服务器端向多个浏览器主动推数据的一种技术。DWR的逆向Ajax主要包括两种模式:主动模式和被动模式。其中主动模式包括polling和comet两种,被动模式只有piggyback这一种:
(1)piggyback方式,是默认的方式。如果后台有什么内容需要推送到前台,是要等到那个页面进行下一次ajax请求的时候,将需要推送的内容附加在该次请求之后,传回到页面。只有等到下次请求页面主动发起了,中间的变化内容才传递回页面。
(2)comet方式当服务端建立和浏览器的连接,将页面内容发送到浏览器之后,对应的连接并不关闭,只是暂时挂起。如果后面有什么新的内容需要推送到客户端的时候直接通过前面挂起的连接再次传送数据。服务器所能提供的连接数目是一定的,在大量的挂起的连接没有关闭的情况下,可能造成新的连接请求不能接入,从而影响到服务质量。
(3)polling方式由浏览器定时向服务端发送ajax请求,询问后台是否有什么内容需要推送,有的话就会由服务端返回推送内容。这种方式和我们直接在页面通过定时器发送ajax请求,然后查询后台是否有变化内容的实现是类似的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@H_744_403@
24
25
26
27
28
29
|
package
sugar.dwr;
import
java.util.Collection;
org.directwebremoting.Browser;
org.directwebremoting.ScriptBuffer;
org.directwebremoting.ScriptSession;
public
class
MessagePush {
void
send(
final
String content){
Runnable run =
new
Runnable(){
private
ScriptBuffer script =
ScriptBuffer();
run() {
//设置要调用的 js及参数
script.appendCall(
"show"
,content);
//得到所有ScriptSession
Collection<ScriptSession> sessions = Browser.getTargetSessions();
//遍历每一个ScriptSession
for
(ScriptSession scriptSession : sessions){
scriptSession.addScript( script);
}
}
};
//执行推送
Browser. withAllSessions(run);
}
}
|
2.新建receiver.jsp用来接收来自服务器的消息:
<%@ page language= "java" import ="java.util.*" pageEncoding="UTF-8" %>
<!
DOCTYPE
HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<
html
>
head
>
title
>dwr接收</
>
script
src
=
"js/jquery-1.8.3.js"
></
script
>
type
"text/javascript"
"dwr/util.js"
>
"dwr/engine.js"
>
"dwr/interface/messagePush.js"
>
</
>
body
>
dwr接收<
br
/>
div
id
"content"
style
" width: 200px ;height: 30px;border : 1px solid ; text-align: center ; padding: 5px;"
div
>
"text/javascript"
>
dwr.engine.setActiveReverseAjax( true);
dwr.engine.setNotifyServerOnPageUnload( true);
function show(content){
$( "#content" ).text(content);
}
>
>
>
|
测试:
先打开receiver.jsp,后打开index.jsp输入文本后发送,效果如图:
1.ScriptSession使用中存在的问题
在上一节实现了服务器的推送功能,但是根据
ScriptSession的生命周期我们可以得出以下几点的问题:
(1)ScriptSession不会与HttpSession同时创建
当我们访问一个页面的时候,如果是第一次访问,就会创建一个新的HttpSession,之后再访问的时候,就会保持当前的Session,即使是刷新,也能保持当前的HttpSession。
但是,ScriptSession不同,第一次访问,会创建一个ScriptSession,但是,如果你刷新,就会创建一个新的ScriptSession.
(2)如何得到ScriptSession
在DWR中,我们可以通过WebContextFactory.get()来取得一个WebContext对象,进而通过WebContext的getScriptSession()取得ScriptSession对象。
但是要注意,在我们自定义的Servlet中,我们也可以通过WebContextFactory.get()来取得一个WebContext,但是这种方法却不能取得ScriptSession对象。因为,此WebContext对象其实不是通过DWR的上下文环境得到的,所以,就根本没有创建ScriptSession对象。
假设这种方式也能得到ScriptSession的话,那么我们实现“推”也就可以不局限在DWR的上下文环境中了,那么其灵活性就会大很多了。所以,这就是我们不能在Servlet中实现推的原因。
(3) 关于刷新就创建一个新的ScriptSession问题
在我们需要推送的页面中,如果你刷新一下,那么就提交一个Http的request,此时,如果是第一次,那么就会创建一个httpSession对象,同时,请求由DwrServlet来处理后,就会创建一个ScriptSession.这个ScriptSession会和你的request请求的URI绑定放在一个由ScriptSessionManager维护的Map里面(这里面其实是一个URI对应的Set,在Set里面放置的是URI绑定的所有ScriptSession)。
当你刷新的时候,同样的一个HttpSession,却会创建一个新的ScriptSession,然后绑定到对应的URI上。
DWR3.0可以通过
- //得到所有ScriptSession
- Collection<ScriptSession>sessions=Browser.getTargetSessions();
DWR2.x可以通过
- Collectionpages=webContext.getScriptSessionsByPage("/yourPage.jsp");
通过此方法,就可以实现调用客户端的javascript函数,实现对客户端的操作。
(5) 在上面的推送中产生的问题
上面的方法已经可以实现向所有的访问者推送。但是问题是,在客户端,如果用户刷新一次或多次,那么,Collection里面可能就保存了很多的无用的ScriptSession,所以不仅仅会影响性能问题,更重要的是,可能就不能实现你想要的功能。
(5) 在上面的推送中产生的问题
上面的方法已经可以实现向所有的访问者推送。但是问题是,在客户端,如果用户刷新一次或多次,那么,Collection里面可能就保存了很多的无用的ScriptSession,所以不仅仅会影响性能问题,更重要的是,可能就不能实现你想要的功能。
2.如何管理有效的ScriptSession
由于上面的问题,我们就需要自己管理ScriptSession。其实,有效地HttpSession,就是那个和当前的HttpSession匹配的ScriptSession。
所以,我们就可以自己维护一个Map,在这个Map里面,我们定义key就是HttpSession的Id,其值就是ScriptSession对象。
在DWR3.0中推出了 ScriptSessionListener用来监听ScriptSession的创建及销毁事件。我们可以使用该监听器来维护我们自己的Map。
1.新建一个类实现
ScriptSessionListener接口
- packagesugar.dwr;
- importjava.util.Collection;
- importjava.util.HashMap;
- importjava.util.Map;
- importjavax.servlet.http.HttpSession;
- importorg.directwebremoting.ScriptSession;
- importorg.directwebremoting.WebContext;
- importorg.directwebremoting.WebContextFactory;
- importorg.directwebremoting.event.ScriptSessionEvent;
- importorg.directwebremoting.event.ScriptSessionListener;
- publicclassDWRScriptSessionListenerimplementsScriptSessionListener{
- //维护一个Mapkey为session的Id,value为ScriptSession对象
- staticfinalMap<String,ScriptSession>scriptSessionMap=newHashMap<String,ScriptSession>();
- /**
- *ScriptSession创建事件
- */
- voidsessionCreated(ScriptSessionEventevent){
- WebContextwebContext=WebContextFactory.get();
- HttpSessionsession=webContext.getSession();
- ScriptSessionscriptSession=event.getSession();
- scriptSessionMap.put(session.getId(),scriptSession);//添加scriptSession
- System.out.println("session:"+session.getId()+"scriptSession:"+scriptSession.getId()+"iscreated!");
- }
- /**
- *ScriptSession销毁事件
- */
- voidsessionDestroyed(ScriptSessionEventevent){
- WebContextwebContext=WebContextFactory.get();
- HttpSessionsession=webContext.getSession();
- ScriptSessionscriptSession=scriptSessionMap.remove(session.getId());//移除scriptSession
- System.out.println("session:"+session.getId()+"scriptSession:"+scriptSession.getId()+"isdestroyed!");
- *获取所有ScriptSession
- staticCollection<ScriptSession>getScriptSessions(){
- returnscriptSessionMap.values();
- }
- }
2.新建一个类继承DefaultScriptSessionManager,用来绑定DWRScriptSessionListener
- importorg.directwebremoting.impl.DefaultScriptSessionManager;
- classDWRScriptSessionManagerextendsDefaultScriptSessionManager{
- publicDWRScriptSessionManager(){
- //绑定一个ScriptSession增加销毁事件的监听器
- this.addScriptSessionListener(newDWRScriptSessionListener());
- System.out.println("bindDWRScriptSessionListener");
- }
3.在web.xml中将DWRScriptSessionManager配置在 dwr-invoker servlet中