开源框架Pushlet入门,使用Pushlet将消息从服务器端推送到客户端

来源:互联网 发布:js block none 编辑:程序博客网 时间:2024/04/30 12:23

http://www.cnblogs.com/linjiqin/archive/2011/12/30/2307788.html

一、comet基本概念
1.comet是一个用于描述客户端和服务器之间交互的术语,即使用长期保持的http连接来在连接保持畅通的情况下支持客户端和服务器间的事件驱动的通信。
2.传统的web系统的工作流程是客户端发出请求,服务器端进行响应,而comet则是在现有技术的基础上,实现服务器数据、事件等快速push到客户端,所以会出现一个术语”服务器推“技术。

            

二、push实现方式
1.原理:
利用jsp/servel技术,在不关闭http流的情况下push数据到客户端浏览器;
2.实现:
基于ajax的长轮询(long-polling)方式

               

ajax的出现使得javascript可以调用xmlhttprequest对象发出http请求,javascript响应处理函数根据服务器返回的信息对html页面的显示进行更新。使用ajax实现“服务器推”与传统的ajax应用不同之处在于:

1)、服务器端会阻塞请求直到有数据传递或超时才返回。 
2)、客户端 javascript 响应处理函数会在处理完服务器返回的信息后,再次发出请求,重新建立连接。 
3)、当客户端处理接收的数据、重新建立连接时,服务器端可能有新的数据到达;这些信息会被服务器端保存直到客户端重 新建立连接,客户端会一次把当前服务器端所有的信息取回。

             

           

 Pushlet实例

一、首先建立一个web工程pushlet,将pushlet.jar放到lib目录中,引入到工程。并且将pushlet.properties和sources.properties两个文件拷贝到WEB-INF目录中去。工程的目录结构如图示

我们一般只需要对sources.properties进行修改即可,创建的消息源必须在这个文件中进行配置。消息源需要实现EventSource接口          

 

二、配置web.xml文件

复制代码
<?xml version="1.0" encoding="UTF-8"?><web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee     http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">    <!--    注意,缺省不需要修改<url-pattern>/pushlet.srv</url-pattern>,如果修改,    需要在对应的js文件中也要修改。pushlt缺省就是通过pushlet.srv触发servlet的。    -->    <servlet>        <servlet-name>pushlet</servlet-name>        <servlet-class>            nl.justobjects.pushlet.servlet.Pushlet        </servlet-class>        <load-on-startup>1</load-on-startup>    </servlet>    <servlet-mapping>        <servlet-name>pushlet</servlet-name>        <url-pattern>/pushlet.srv</url-pattern>    </servlet-mapping>    <welcome-file-list>        <welcome-file>index.jsp</welcome-file>    </welcome-file-list></web-app>
复制代码

         

三、看一下index.jsp这个文件的内容

复制代码
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%><%String path = request.getContextPath();String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";%><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html>  <head>    <base href="<%=basePath%>">        <title>HelloWorld</title>        <meta http-equiv="pragma" content="no-cache">    <meta http-equiv="cache-control" content="no-cache">    <meta http-equiv="expires" content="0">        <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">    <meta http-equiv="description" content="This is my page">    <script type="text/javascript" src="ajax-pushlet-client.js"></script>    <script type="text/javascript">         //对pushlet的初始化,触发web.xml中的servlet。        PL._init();         //这里的监听的主题,必须在sources.properties中配置的对象中声明这个主题。        //sources.properties配置着事件源(EventSources),在服务器启动时会自动激活。        //可以通过服务器的启动记录查看得到。可以将这个文件放到WEB-INF目录下面或者classess目录下面都可以。        PL.joinListen('/linjiqin/hw');         function onData(event) {             alert(event.get("hw"));         }      </script>  </head>  <body>  </body></html>
复制代码

             

四、修改sources.properties文件

复制代码
source1=nl.justobjects.pushlet.test.TestEventPullSources$TemperatureEventPullSourcesource2=nl.justobjects.pushlet.test.TestEventPullSources$SystemStatusEventPullSourcesource3=nl.justobjects.pushlet.test.TestEventPullSources$PushletStatusEventPullSourcesource4=nl.justobjects.pushlet.test.TestEventPullSources$AEXStocksEventPullSourcesource5=nl.justobjects.pushlet.test.TestEventPullSources$WebPresentationEventPullSourcesource6=nl.justobjects.pushlet.test.TestEventPullSources$PingEventPullSource#source1~source6是系统缺省自带的,source7是我自己配置的,并且在index.jsp中的脚本中,#配置的/linjiqin/hw是和这儿是对应的。具体是这样的。HwPlushlet是com.ljq.test.HelloWorldPlushlet对象的一个内部类,#并且继承EventPullSource接口。source7=com.ljq.test.HelloWorldPlushlet
复制代码

 

五、核心代码HelloWorldPlushlet

复制代码
package com.ljq.test;import java.io.Serializable;import nl.justobjects.pushlet.core.Event;import nl.justobjects.pushlet.core.EventPullSource;@SuppressWarnings("serial")public class HelloWorldPlushlet extends EventPullSource implements Serializable {    /**     * 设置休眠时间     */    @Override    protected long getSleepTime() {        return 1000;    }    /**     * 创建事件     *      * 业务部分写在pullEvent()方法中,这个方法会被定时调用。     */    @Override    protected Event pullEvent() {        Event event = Event.createDataEvent("/linjiqin/hw");        event.setField("hw", "HelloWorld!!!!");        return event;    }}
复制代码

        

这样这个基本的例子就OK了,运行一下,看看吧。
访问http://localhost:8083/pushletprj 会定时弹出alert窗口,窗口的内容如下图:

        


如果这个页面关闭了,服务器会自动取消订阅,和移除对应的session信息。下面是我关闭页面后,服务器端的输出信息,如图:

pushlet 2.0.3 源码分析(服务器端)

Pushlet 2.0.3 源码分析
----服务器端
1 总体架构
Pushlet从功能上实现了服务器推技术,整个框架涉及了服务器端以及客户端的部署。服务器端采用servlet技术,监听客户端请求。客户端分为两大类,浏览器以及桌面应用程序。下图描述了系统的整体框架:


图1 pushlet总体架构图
从图中可以看出服务器端返回响应的出口只有一个,那就是clientAdapter,它只是一个接口,根据不同的客户端类型来产生相应的adapter发送响应结果。
各个类的主要职责描述:
Pushlet:负责接收所有用户请求,并将请求包装为event对象,在根据session、event、request、response对象构造一个command对象,最后将command对象交由controller处理。
Session:代表一次用户会话,此类不同于httpsession,因为此session的实现是使用类似url重写方式,在服务器分配了sessionid之后的每个请求中都加入这个参数以标识会话。会话在其存活期内有效。
Controller:是所有命令的执行器,包括各种控制命令以及数据推送命令。不过对于数据推送的实际执行并不是在controller中实现,而是委托给subscriber只执行。
Subscriber:这是核心类之一。它维护了一个阻塞的事件队列,根据客户端使用的不同模式(框架定义的模式有:stream,pull/poll,stream使用了http长连接,pull/poll则是通过客户端定时刷新实现服务端推送)来发送响应事件。
Dispatcher:事件分发器,也是核心类之一。事件来源可以是客户(通过publish命令发布事件),也可以是eventSource。实现了多播,广播以及单播事件,具体采用哪种方式根据事件属性决定。事件接收端即是subscriber的事件队列。
clientAdapter:有3个具体实现,browserAdapter、XMLAdapter、serializedAdapter。分别用来发送javascript、xml、序列化数据。使用于不同的客户端。具体使用哪种adapter需要根据用户请求事件的format参数决定。
其他公共类:提供了日志、可配置等功能。


图2 核心类的对应关系
2 原理分析
Pushlet采用服务器端回调技术以及HTTP长连接实现了服务器推服务的两种模式,即stream,pull/poll。其中对于浏览器客户端还应用了DHTML技术,通过回调javascript函数在不刷新页面的前提下实时更新,普通的桌面应用很容易便可以实现这种效果。为了提高系统的可靠性以及健壮性,通信过程中开通了两条通道,控制通道和数据通道。控制通道不会阻塞,能够实时接收客户命令,而数据通道工作在阻塞模式下,当传输模式为stream时,数据通道连接不断开,直至用户发送断开命令或客户端退出或服务器异常,为了防止阻塞时间过长导致客户端无法得知服务器是否正常工作,系统设置了阻塞过期时间,并且在过期之后向客户端发送心跳消息表明自身仍然存活。而在pull/poll模式下,阻塞直至有数据可以发送,然后断开连接。浏览器客户端需要不停的发送心跳请求,目的是为了解决浏览器一直繁忙的状态。以下是系统的协议服务(protocol services)

 

 

Service Description
join 启动一个会话
leave 结束会话
subscribe 订阅相关主题
unsubscribe 取消订阅相关主题
listen 打开数据通道,以stream、pull或poll模式开始数据流传输。在pull/poll模式下,服务器提供所谓的刷新操作,实际上是客户端来重新请求以获取数据
join-listen 通过一个请求完成会话启动,订阅并监听数据。执行完之后的状态与执行完listen类似。
publish 发布数据,然后服务器将其分发。客户端可以使用它进行多播或单播数据。
heartbeat 表明会话存活


3 具体实现
Pushlet采用了大量的单例和工厂模式,另外还有适配器模式、命令模式。实现中遵循面向接口以及抽象类编程,这些使得系统易于理解,易于扩展。系统的大多数属性都是在配置文件中指定,如果有通过系统扩展点编写的扩展类要替代默认实现的话,只需要修改配置文件指向你自己的类文件即可,不需改动代码。接下来就沿着请求—响应的主线来分析系统源码。
请求入口pushlet
Init()方法:
30 String webInfPath = getServletContext().getRealPath("/") + "/WEB-INF";
31 Config.load(webInfPath);//载入配置文件,存放在该类的变量中
32
33 Log.init();//初始化日志类
34
35 // Start
36 Log.info("init() Pushlet Webapp - version=" + Version.SOFTWARE_VERSION + " built=" + Version.BUILD_DATE);
37
38 // Start session manager,负责管理session生命周期,这是系统的扩展点,下文详解.
39 SessionManager.getInstance().start();
40
41 // Start event Dispatcher,负责分发系统或客户事件
42 Dispatcher.getInstance().start();
43
44
45 if (Config.getBoolProperty(Config.SOURCES_ACTIVATE)) {
46 EventSourceManager.start(webInfPath);//启动事件源管理器
47 } else {
48 Log.info("Not starting local event sources");
49 }
初始化完毕之后便可以处理用户请求了.它可以处理两种请求,get和post,处理方式主要是提取请求参数,然后将其封装成event事件对象,再进一步构造command对象,最终的处理有交给了controller。这部分的代码很简单,因为主要的处理逻辑都委托给了controller。这段代码有几点是值得学习的。
1) 抽象。Event对象封装了属性—值对,内部通过hashmap实现,原理上来讲,它可以封装任何信息,为了使这样的一个抽象能够适于作为系统的通用数据抽象形式,还需要加入一个必备属性,即event_type。请求以及数据均被定义为事件,然后通过内部协议来区分它们。通过抽象之后,系统可以以一致的处理形式应对各种数据。后面将要分析的subscriber维护着一个事件队列,使用该队列完成所有的交互。这便是使用了这个抽象机制的好处。
2) 命令模式command。一个命令里包含了请求事件、响应事件以及response,request,session对象。Controller便是这个命令的执行器,通过一个简单的doCommand接口隐藏了内部复杂的处理逻辑,降低了模块的耦合度。执行完命令之后,要输出的结果就是响应事件responseEvent。Controller处理代码如下
49 // Update lease time to live,更新session生存期,防止过期
50 session.kick();
51
52 // Set remote IP address of client,设置远程客户端地址
53 session.setAddress(aCommand.httpReq.getRemoteAddr());
54
55 debug("doCommand() event=" + aCommand.reqEvent);
56
57 // Get event type
58 String eventType = aCommand.reqEvent.getEventType();
59
60 // Determine action based on event type,根据事件类型采取
相应的操作,分别构造响应事件
61 if (eventType.equals(Protocol.E_REFRESH)) {
62 // Pull/poll mode clients that refresh
63 doRefresh(aCommand);
64 } else if (eventType.equals(Protocol.E_SUBSCRIBE)) {
65 // Subscribe
66 doSubscribe(aCommand);
67 } else if (eventType.equals(Protocol.E_UNSUBSCRIBE)) {
68 // Unsubscribe
69 doUnsubscribe(aCommand);
70 } else if (eventType.equals(Protocol.E_JOIN)) {
71 // Join
72 doJoin(aCommand);
73 } else if (eventType.equals(Protocol.E_JOIN_LISTEN)) {
74 // Join and listen (for simple and e.g. REST apps)
75 doJoinListen(aCommand);
76 } else if (eventType.equals(Protocol.E_LEAVE)) {
77 // Leave
78 doLeave(aCommand);
79 } else if (eventType.equals(Protocol.E_HEARTBEAT)) {
80 // Heartbeat mainly to do away with browser "busy" cursor
81 doHeartbeat(aCommand);
82 } else if (eventType.equals(Protocol.E_PUBLISH)) {
83 // Publish event
84 doPublish(aCommand);
85 } else if (eventType.equals(Protocol.E_LISTEN)) {
86 // Listen to pushed events
87 doListen(aCommand);
88 }
89
90 // Handle response back to client
91 if (eventType.endsWith(Protocol.E_LISTEN) ||
92 eventType.equals(Protocol.E_REFRESH)) {
//请求类型是listen或refresh,表明是获取数据
93 // Data channel events
94 // Loops until refresh or connection closed
95 getSubscriber().fetchEvents(aCommand);
96
97 } else {
98 // Send response for control commands,控制命令,直接返回。
99 sendControlResponse(aCommand);
00 }

sendControlResponse()代码:

31 aCommand.sendResponseHeaders();//设置响应头,主要是客户端//缓存无效
32
33 // Let clientAdapter determine how to send event
//通过clientAdapter发送响应事件
34 aCommand.getClientAdapter().start();
35
36 // Push to client through client adapter
37 aCommand.getClientAdapter().push(aCommand.getResponseEvent());
38
39 // One shot response
40 aCommand.getClientAdapter().stop();

Subscriber:
fetchEvents()部分代码:
。。。。。。。。。。。。。。。。。
。。。。。。。。。。。。。。。。
15 Event[] events = null;
16
17 // Main loop: as long as connected, get events and push to client
18 long eventSeqNr = 1;
19 while (isActive()) {//这个循环保证了连接不被关闭,即可以以流的//方式发送响应到客户端,真正意义上的服务器推送数据
20 // Indicate we are still alive
21 lastAlive = Sys.now();
22
23 // Update session time to live
24 session.kick();
25
26 // Get next events; blocks until timeout or entire contents
27 // of event queue is returned. Note that "poll" mode
28 // will return immediately when queue is empty.
29 try {
30 // Put heartbeat in queue when starting to listen in stream mode
31 // This speeds up the return of *_LISTEN_ACK
32 if (mode.equals(MODE_STREAM) && eventSeqNr == 1) {
33 eventQueue.enQueue(new Event(E_HEARTBEAT));
34 }
35 //此方法获取事件队列里的事件,有超时设置,为阻塞操作
36 events = eventQueue.deQueueAll(queueReadTimeoutMillis);
37 } catch (InterruptedException ie) {
38 warn("interrupted");
39 bailout();
40 }
41
42 // Send heartbeat when no events received,超时后,发送心跳信息
43 if (events == null) {
44 events = new Event[1];
45 events[0] = new Event(E_HEARTBEAT);
46 }
47
48 // ASSERT: one or more events available
49
50 // Send events to client using adapter
51 // debug("received event count=" + events.length);
52 for (int i = 0; i < events.length; i++) {
53 // Check for abort event
54 if (events[i].getEventType().equals(E_ABORT)) {
55 warn("Aborting Subscriber");
56 bailout();
57 }
58
59 // Push next Event to client
60 try {
61 // Set sequence number
62 events[i].setField(P_SEQ, eventSeqNr++);
63
64 // Push to client through client adapter
65 clientAdapter.push(events[i]);
66 } catch (Throwable t) {
67 bailout();
68 return;
69 }
70 }
71
72 // Force client refresh request in pull or poll modes
73 if (mode.equals(MODE_PULL) || mode.equals(MODE_POLL)) { //如果不是stream模式,就在向客户端发送刷新命令,以获取新 //的数据 ,并退出循环,服务器自动关闭连接。因为这是http连接,响应方法//只要返回连接就会被关闭。 
74 sendRefresh(clientAdapter, refreshURL);
75
76 // Always leave loop in pull/poll mode
77 break;
78 }
79 }
最后,响应事件通过clientAdapter真正发送到客户端。
BrowserAdapter部分代码:
13 protected String event2JavaScript(Event event) throws IOException {
14//将事件对象转化为javascript脚本,实际上是回调脚本的代码
15 // Convert the event to a comma-separated string.
16 String jsArgs = "";
17 for (Iterator iter = event.getFieldNames(); iter.hasNext();) {
18 String name = (String) iter.next();
19 String value = event.getField(name);
20 String nextArgument = (jsArgs.equals("") ? "" : ",") + "'" + name + "'" + ", /"" + value + "/"";
21 jsArgs += nextArgument;
22 }
23
24 // Construct and return the function call */
25 return "<script language=/"JavaScript/">parent.push(" + jsArgs + ");</script>";
26 }
Command部分代码:使用适配器模式,可以将不同客户端处理方式的不同点隐藏,客户代码使用同一接口调用,这样可以很方便的添加其他客户端类型的适配器。不过我个人觉得这三种适配器已经可以适应所有客户端类型了,而且框架的作者也没做扩展的打算。因为在这里是直接硬编码生成适配器对象的,而没有用到反射机制动态生成配置文件所定义的类型。
protected ClientAdapter createClientAdapter() throws PushletException {
96
97 // Assumed to be set by parent.获取响应格式,系统定义了4种格式,
// js、xml、 ser(序列化对象)、xml-strict
98 String outputFormat = session.getFormat();
99
00 // Determine client adapter to create.根据不同的格式返回相应的//Adapter 
01 if (outputFormat.equals(FORMAT_JAVASCRIPT)) {
02 // Client expects to receive Events as JavaScript dispatch calls..
03 return new BrowserAdapter(httpRsp);
04 } else if (outputFormat.equals(FORMAT_SERIALIZED_JAVA_OBJECT)) {
05 // Client expects to receive Events as Serialized Java Objects.
06 return new SerializedAdapter(httpRsp);
07 } else if (outputFormat.equals(FORMAT_XML)) {
08 // Client expects to receive Events as stream of XML docs.
09 return new XMLAdapter(httpRsp);
10 } else if (outputFormat.equals(FORMAT_XML_STRICT)) {
11 // Client expects to receive Events embedded in single XML doc.
12 return new XMLAdapter(httpRsp, true);
13 } else {
14 throw new PushletException("Null or invalid output format: " + outputFormat);
15 }
16 }

单例模式以及工厂模式:
Dispatcher,SessionManager两者都使用了单例模式,在全局维持一个实例,充当了全局对象的作用,因为保存在这些对象里的数据或方法可以很方便的被进程内的其他对象访问,如session集合、dispatcher的各种分发事件的方法。这种方案在进程内可以很好的工作,但是如果想将应用扩展成为分布式应用,那就必须修改这些实现。
为什么要考虑分布式的可能呢?因为stream模式是通过HTTP长连接实现的。保持这个连接意味着每有一个订阅请求,就会持续占用那个连接,直到产生取消订阅的请求或者服务器异常。因为连接一致被占用,相应的servlet线程也被占用了,这样系统的总吞吐量就取决于线程池的大小乃至操作系统的连接限制。这样的限制直接导致了这个框架无法满足中型以上的系统需求。其中一种解决方案就是使框架支持分布式,通过多台服务器并行处理请求,在分布式系统中,相应的分布式sessionManger,Dispatcher是必须的,但是实现这两个类的难度显然是很高的,不知在以后的版本中是否会有这种实现。
目前,我觉得pull/poll模式更为实用,因为这种模式不会持续保持连接,使线程池可以发挥作用,但是,它是靠客户端定时刷新的,这样会给服务器带来较大的压力,如果刷新很频繁的话,实际的吞吐量也不高。(本人并没有实际测试过,但是从理论分析应该是这样的)
Controller、Session、Subscriber、Subscription、EventSourceManager这些类使用了工厂模式并结合java反射机制动态生成实例对象,这些都是框架预留的扩展点,开发人员可以通过扩展点实现符合自己需求的类,并通过配置文件将其整合到框架中来。一段典型的代码如下:
摘自Controller.java
33 public static Controller create(Session aSession) throws PushletException {
34 Controller controller;
35 try {
//读取配置文件,并生成实例对象
36 controller = (Controller) Config.getClass(CONTROLLER_CLASS, "nl.justobjects.pushlet.core.Controller").newInstance();
37 } catch (Throwable t) {
38 throw new PushletException("Cannot instantiate Controller from config", t);
39 }
40 controller.session = aSession;
41 return controller;
42 }

总结:通过阅读pushlet的源码,让我学到了很多实战编程经验,希望本文可以给java爱好者一点点帮助。
注:本文并没有分析所有代码细节,而且只针对服务器端代码,如果有兴趣的话可以自己到网上下载pushlet的源码,去体验高人的风范!

使用Pushlet将消息从服务器端推送到客户端【原创】

 (2012-07-03 11:07:41)
转载
标签: 

pushlet

 

消息推送

 

b/s

 

java

 

it

分类: Java技术

使用Pushlet来实现服务器端向客户端推送信息

1.   实现方式:

有两种实现方式:

1.         通过配置文件来实现定时的从服务器端向客户端推送信息

2.         通过API主动向另外一端推送信息

 

以下分别给予介绍。

 

2.   特别注意

在开始测试之前,有三点非常重要,需要实现讲明,否则程序将会无法正常运行:

2.1.     JSP页面上的设定

JSP页面上必须添加以下代码以确保Pushlet能够正确的获得后台服务的地址:

<base href="<%=request.getContextPath()%>">

 

2.2.     PushletJS文件的Bug修改

需要修改被引用的JS文件ajax-pushlet-client.js的内容,找到

PL.pushletURL = PL._getWebRoot() + 'pushlet.srv';

将其修改为

PL.pushletURL = 'pushlet.srv';

修改的原因是Pushlet进行地址解析的方法在某些应用中会解析错误,导致请求的路径是nullpushlet.srv?,最终导致无法正确的请求到服务器的信息。

 

2.3.     中文问题

一般情况下,如果不做特殊处理,中文问题将会导致Pushlet的客户端停止响应,解决办法是,在使用Pushlet的客户端代码发送消息之前,将其进行转码,代码为

encodeURIComponent( msg)

3.   正式开始

以上准备工作完毕,就可以正式的开发测试样例了。

 

3.1.     定时的从后台向前台push信息

(1)       eclipse中创建一个动态的web工程

(2)       配置及库文件文件:从http://www.pushlets.com/ 下载最新的pushlet的开发包,将其中的以下文件按照描述进行设定

序号

文件名

源位置

目标位置

备注

1.         

pushlet.jar

{pushlet-2.0.4}\lib

项目类路径

如果使用的是applet的话,还需要将pushletclient.jar设置到项目的类路径中去

2.         

log4j.properties

pushlet.properties

sources.properties

{pushlet-2.0.4}\webapps\pushlet\WEB-INF\classes

项目的src根路径

注意稍后需要修改sources.properties,其他两个文件的内容不需要修改

3.         

ajax-pushlet-client.js

{pushlet-2.0.4}\webapps\pushlet\lib

项目的webroot\lib

需要按照之前的描述修改其中的内容

(3)       修改web.xml,将pushlet的自启动servlet添加进去

  <servlet>

    <servlet-name>pushlet</servlet-name>

    <servlet-class>nl.justobjects.pushlet.servlet.Pushlet</servlet-class>

    <load-on-startup>1</load-on-startup>

  </servlet>

  <servlet-mapping>

    <servlet-name>pushlet</servlet-name>

    <url-pattern>/pushlet.srv</url-pattern>

  </servlet-mapping>

(4)       创建服务器端代码,特别注意类和内部静态类的名字

 

package com.guoguo;

 

import java.io.Serializable;

import java.io.UnsupportedEncodingException;

import sun.rmi.runtime.Log;

import nl.justobjects.pushlet.core.Event;

import nl.justobjects.pushlet.core.EventPullSource;

 

 

public class HelloWorldPushlet implements Serializable {

 

     

      private static final long serialVersionUID = -8940934044114406724L;

 

      public static class HWPushlet extends EventPullSource {

           Log log = Log.getLog(HWPushlet.class.getName(),

                      HWPushlet.class.getName(), true);

 

          

           @Override

           protected long getSleepTime() {

                 return 1000;//每一秒钟自动执行一次

           }

 

          

           @Override

           protected Event pullEvent() {

             //注意,一下是设定消息的主题/guoguo/helloworld,号称主题是可以继承的

            //但是笔者的测试是失败的,也许方法不对,呵呵

                 Event event = Event.createDataEvent("/guoguo/helloworld");

                 String data= "hello,world 郭强 "+System.currentTimeMillis();

                 try {

                      data=new String(data.getBytes("UTF-8"),"ISO-8859-1");

                 } catch (UnsupportedEncodingException e) {

                      e.printStackTrace();

                 }

                 event.setField("hw",data);

                 return event;

           }

 

      }

}

 

(5)       注册服务器端代码为事件源,在sources.properties文件中,添加以下行

source7=com.guoguo.HelloWorldPushlet$HWPushlet

以上方式适用于有内部类的情况,如果没有内部类的话,使用以下的方式进行注册(这时外部类必须继承父类EventPullSource)

source7=com.guoguo.HelloWorldPushlet

(6)       页面(可以参考页面内注释信息)

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"

      pageEncoding="ISO-8859-1"%>

<!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=ISO-8859-1">

<title>Pushlet Test</title>

<meta http-equiv="pragma" content="no-cache"> 

<meta http-equiv="cache-control" content="no-cache">

<base href="<%=request.getContextPath()%>">

</head>

<body>

      <script type="text/javascript" src="lib/ajax-pushlet-client.js"></script>

      <div id="guoguo"></div>

      <script type="text/javascript">

          //初始化pushlet客户端

           PL._init();

        //设定运行时显示调试信息,不需要时,直接删掉即可

           PL.setDebug(true);

        //设定监听主题:/guoguo/helloworld,与服务器端的主题完全一致

           PL.joinListen('/guoguo/helloworld');

        //接收到事件后,显示服务器信息

           function onData(event) {

                 guoguo.innerText=(event.get("hw"));

           }

      </script>

      <p1>Pushlet Test</p1>

</body>

</html>

(7)       启动服务器,即可看到页面上的信息每秒钟一次,进行定时的更新

3.2.     主动控制发送消息

3.2.1.   有刷新的提交信息(服务器端主动发送消息)

(1)       创建一个servlet,并且注册到web.xml

Servlet代码

package com.guoguo;

 

import java.io.IOException;

 

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

 

import nl.justobjects.pushlet.core.Dispatcher;

import nl.justobjects.pushlet.core.Event;

import nl.justobjects.pushlet.core.SessionManager;

 

 

public class ChatServlet extends HttpServlet {

         private static final long serialVersionUID = 1L;

 

        

         public ChatServlet() {

                   super();

         }

 

        

         protected void service(HttpServletRequest request,

                            HttpServletResponse response) throws ServletException, IOException {

                   // myUnicast();

                   myMulticast();

                   // myBroadcast();

                   request.getRequestDispatcher("chat.jsp").forward(request, response);

         }

 

         private void myUnicast() {

 

                   Event event = Event.createDataEvent("/guoguo/myevent1");

                   event.setField("key1", "Unicast msg");

                   Dispatcher.getInstance().unicast(event, "piero"); // IDpiero的用户推送

                   System.out.println("success....");

         }

 

         private void myMulticast() {

                   Event event = Event.createDataEvent("/guoguo/myevent1");

                   //Event event = Event.createDataEvent("/guoguo");

                   event.setField("key1", "Multicast msg");

                   Dispatcher.getInstance().multicast(event); // 向所有和myevent1名称匹配的事件推送

 

                   System.out.println("wa success....");

 

         }

 

         private void myBroadcast() {

                   Event event = Event.createDataEvent("/guoguo/myevent1"); // 向所有的事件推送,不要求和这儿的myevent1名称匹配

                   event.setField("key1", "Broadcast msg");

                   Dispatcher.getInstance().broadcast(event);

 

                   System.out.println("asw success....");

         }

 

}

 

Web.xml

  <servlet>

    <display-name>ChatServlet</display-name>

    <servlet-name>ChatServlet</servlet-name>

    <servlet-class>com.guoguo.ChatServlet</servlet-class>

  </servlet>

  <servlet-mapping>

    <servlet-name>ChatServlet</servlet-name>

    <url-pattern>/ChatServlet</url-pattern>

  </servlet-mapping>

 

(2)       页面端代码

发送端

<base href="<%=request.getContextPath()%>">

<form action="<%=request.getContextPath()%>/ChatServlet">

      <input type="submit">

</form>

接收端

<base href="<%=request.getContextPath()%>">

<script type="text/javascript" src="lib/ajax-pushlet-client.js"></script>

<div id="guoguo"></div>

<script type="text/javascript">

           PL._init();

           PL.joinListen('/guoguo/myevent1');

           function onData(event) {

                 guoguo.innerText = (event.get("key1"));

           }

</script>

 

启动服务器,从发送端提交信息,内容会在接收端显示出来

 

3.2.2.   无刷新的提交信息(从客户端发送消息)

发送端

<base href="<%=request.getContextPath()%>">

<script type="text/javascript" src="lib/ajax-pushlet-client.js"></script>

<script type="text/javascript">

           PL._init();

           function sendnews(msg) {

                 p_publish('/guoguo/myevent1','key1',encodeURIComponent(msg),’key2’,’msg2’);

           }

</script>

<input type="text" name="mymsg">

<input type = "button" value="发消息" onclick="sendnews(mymsg.value)"/>

 

接收端

<base href="<%=request.getContextPath()%>">

<script type="text/javascript" src="lib/ajax-pushlet-client.js"></script>

<div id="guoguo"></div>

<script type="text/javascript">

           PL._init();

           PL.joinListen('/guoguo/myevent1');

           function onData(event) {

                 guoguo.innerText = (event.get("key1"));

           }

</script>

 

启动服务器,从发送端提交信息,内容会在接收端显示出来



原创粉丝点击