pushlet学习

网络资源:

一、简述:

    pushlet是一种comet实现,在servlet机制下,数据从server端的java对象直接推送(push)到(动态)HTML页面,而无需任何java applet或者插件的帮助。

二、使用pushlet需要做哪些准备工作

1.下载pushlet

http://sourceforge.net/projects/pushlets/files/pushlets/2.0.4/pushlet-2.0.4.zip/download

2.eclipse 或者myeclipse搭建简单项目

1)创建工程

a. File à new à projectàweb project   项目名称为:  pushlet-demo

2)加依赖文件

a.在下载文件的webapps 演示文件夹中找寻文件

b.src目录中添加  log4j.properties  pushlet.properties   sources.properties ,添加后修改sources.properties  清空演示配置,准备添加自己的配置

source1=nl.justobjects.pushlet.test.TestEventPullSources$TemperatureEventPullSource

source2=nl.justobjects.pushlet.test.TestEventPullSources$SystemStatusEventPullSource

source3=nl.justobjects.pushlet.test.TestEventPullSources$PushletStatusEventPullSource

source4=nl.justobjects.pushlet.test.TestEventPullSources$AEXStocksEventPullSource

source5=nl.justobjects.pushlet.test.TestEventPullSources$WebPresentationEventPullSource

source6=nl.justobjects.pushlet.test.TestEventPullSources$PingEventPullSource

注意:可以不清空,以上斜体部分在sources.properties注释掉即可。

c.添加依赖jar

在下载文件的lib目录下复制 pushlet.jar  pushletclient.jar导入到自己工程的/WEB-INF/lib 

d.配置web.xml

<servlet>

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

注意:此处名字太长如果记不住可以利用eclipse等工具随便下个类中用快捷键提示得到。

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

</servlet>

 

<servlet-mapping>

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

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

 注意:此处默认为/pushlet.srv,一般无需改动,要想改动需要在对应的js文件。

</servlet-mapping>

    

3)添加实例

a. 创建数据源

package com.source;

 

import java.io.Serializable;

import nl.justobjects.pushlet.core.Event;

import nl.justobjects.pushlet.core.EventPullSource;

 

publicclass HelloWorldEventPullSource implements Serializable{

  privatestaticfinallongserialVersionUID = 1L;

  staticpublicclass HelloWorldEvent extends EventPullSource{

      @Override

      protectedlong getSleepTime() {

          return 1000; //刷新时间

      }

      @Override

      protected Event pullEvent() {

          Event event =Event.createDataEvent("/source/event");//事件标识

          注意:此处”/source/event”将对应页面js代码中的PjoinListen中的参数

          event.setField("msg", "hello,world");//封装参数

          return event;

      }

  }

}

    

b. 配置数据源 

sources.properties  进行数据源配置

添加source1=com.source.HelloWorldEventPullSource$HelloWorldEvent

  注意: HelloWorldEventPullSourc这是类名,HelloWorldEvent是内部类名。

c. 页面调用 

Index.html

 

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>

  <head>

    <title>index.html</title>

    <meta http-equiv="content-type" content="text/html; charset=UTF-8">

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

<!js 从演示项目的  lib copy -->

  <script type="text/javascript">

      PL._init(); 

      PL.joinListen('/source/event');  //事件标识 在数据源中引用

      function onData(event) { 

          alert(event.get("msg"));

        }

    </script>

  </head>

  <body>

  </body>

</html>

4.   测试  http://localhost:8080/pushlet-demo/index.html

 

三、对pushlet的一点看法:

    个人认为pushlet后台代码核心主要在事件源,所以我们自己创建一个事件源类,这个事件源类必须实现EventSource或者继承EventPullSource类。如果事件源类实现了EventSourc。接口,那么它需要实现把事件”推”到pushlet框架的方法;如果事件源类扩展了EventPullSource类,那么Pushlet框架会每隔一段时间自动地拉取事件源产生的事件。然后,把事件源类捆绑到TestEventPullSources类中,最后将这个事件源的类名存放到类路径下的资源文件sources.properties中。将事件源捆绑到TestEventPullSources类中,并存放于资源文件中,目的是便于对事件源的创建和管理。当Pushlet.服务器被载人时,事件源管理器就会在类路径中寻找资源文件,并将文件中的事件源载人激活。

     一个简单的做法,当我们继承了EventPullSource类(只能继承的原因是该类是个抽象类),主要实现两个方法,一个就是getSleepTime()pullEvent(),其中getSleepTime是为了控制刷新数据的时间,而在pullEvent是写自己业务代码的地方。写完这个类后,除了要在jsp页面完成一些代码外,更主要的是要在sources.properties文件进行相关的配置。(详见第二点)显而易见,这种做法虽然理解起来简单但是不便于扩展,配置起来也有一定的麻烦。

经过研究源码,查找API,发现EventPullSource这个类除了上面提到的方法外,还有activate()isAlive()passivate()run()start()stop()仔细研究发现其实EventPullSource是实现了EventSourcerunnable这两个接口的。既然这样,可以想到自己是否也可以写一个类实现这两个接口,将自己的业务方法封装在pullEvent方法中,并在Jsp页面中的javaScript中编写一些必须的方法,发现可以达到推送的目的。然而这样不需要在sources.properties中进行相关配置,省去了很多麻烦。

说的再多不如给一段代码来的直观点:

package com.unistrong.dial.pushlet;

 

import java.util.HashMap;

import java.util.Map;

 

import com.unistrong.dial.vo.Dial;

 

import nl.justobjects.pushlet.core.Dispatcher;

import nl.justobjects.pushlet.core.Event;

import nl.justobjects.pushlet.core.EventSource;

import nl.justobjects.pushlet.util.Log;

 

public class ServerMain implements EventSource,Runnable {

    private volatile boolean alive = false;

    private volatile boolean active = false;

    private static int threadNum = 0;

    private Thread thread;

    private long sleepTime=0;

    private String eventName;

    private Map<String, String> map=new HashMap<String, String>();//同一个id的事件只能在集合中出现一次

    public ServerMain() {

    }

    public ServerMain(long sleepTime,String eventName,Map<String, String> map) {

        this.sleepTime=sleepTime;

        this.eventName=eventName;

        this.map=map;

    }

    public Event pullEvent(){

        Dial dial=new Dial();

        dial.setDid("123");

        dial.setData("10000");

        dial.setDname("dial");

        Event event = Event.createDataEvent(eventName);

        event.setField("did", dial.getDid());

        //event.setField("data", dial.getData());

        //event.setField("dname", dial.getDname());

        return event;

    }

 

    public void start() {

        thread = new Thread(this, "EventPullSource-" + (++threadNum));

        thread.setDaemon(true);

        thread.start();

    }

 

    public boolean isAlive() {

        return alive;

    }

    /**

     * Stop the event generator thread.----->停止事件构造器线程

     */

    public void stop() {

        alive = false;

 

        if (thread != null) {

            thread.interrupt();

            thread = null;

        }

 

    }

 

    /**

     * Activate the event generator thread.------->激活事件构造器线程。

     */

    synchronized public void activate() {

        if (active) {

            return;

        }

        active = true;

        if (!alive) {

            start();

            return;

        }

        Log.debug(getClass().getName() + ": notifying...");

        notifyAll();

    }

 

    /**

     * Deactivate the event generator thread.--------->停用事件构造其线程。

     */

    public void passivate() {

        if (!active) {

            return;

        }

        active = false;

    }

 

    /**

     * Main loop: sleep, generate event and publish.----->主循环:设置睡眠,事件构造器和进行发布

     */

    public void run() {

        Log.debug(getClass().getName() + ": starting...");

        alive = true;

        while (alive) {

            try {

 

                Thread.sleep(sleepTime);

 

                // Stopped during sleep: end loop.睡眠时间停止:结束循环。

                if (!alive) {

                    break;

                }

 

                // If passivated wait until we get

                // get notify()-ied. If there are no subscribers

                // it wasts CPU to remain producing events...

                /*直到我们得到通知才能不在等待,如果没有用户订阅我们要消耗cpu来维持活动*/

                synchronized (this) {

                    while (!active) {

                        Log.debug(getClass().getName() + ": waiting...");

                        wait();

                    }

                }

 

            } catch (InterruptedException e) {

                break;

            }

 

            try {

                // Derived class should produce an event.

                //实现类产生一个事件。

                Event event = pullEvent();

 

                // Let the publisher push it to subscribers.

                //将订阅信息推到用户那里

                Dispatcher.getInstance().multicast(event);

            } catch (Throwable t) {

                Log.warn("EventPullSource exception while multicasting ", t);

                t.printStackTrace();

            }

        }

        Log.debug(getClass().getName() + ": stopped");

    }

}

在这段代码中我们主要注意以下几个方面即可:

1.个人认为根据自己的业务的实际情况可以增加一些属性,并且要生成一无参和有参数的构造方法。有参数的构造方法的参数根据实际情况而定,当然上面代码中的参数我认为是几个必要的参数,其他视情况而定。

2.主要的业务代码仍然在pullEvent方法中实现。

3.除了pullEvent这个方法自己写之外,其他方法完全可以复制EventPullSource里面的代码,省力又准确。

4.写好这个类,我们就可以在struts框架的Action中,或者servlet中直接调用其中的activate()方法。当然如果再考虑严谨点我们多写一个类当做业务层来调用里面的方法,不仅可以推送数据,也可以停用单个或者所有的event.

再贴段代码看看就会明白:

package com.unistrong.dial.pushlet;

 

import java.util.HashMap;

import java.util.Map;

 

public class EventManager {

    private static EventManager em=new EventManager();

 

    public EventManager() {

    }

 

    public static EventManager getInstance(){

        return em;

    }

    //eventList集合主要用来放所有的事件,为了防止重复所以使用map集合

    private Map<String, ServerMain> eventList=new HashMap<String, ServerMain>();

    public Map<String, ServerMain> getEventList() {

        return eventList;

    }

    

    /**

     * 创建订阅

     */

    public void createEvent(long sleepTime,String eventName,Map<String, String> map){

        System.out.println("开启订阅:"+eventName);

        ServerMain sm=new ServerMain(sleepTime,eventName,map);

        eventList.put(eventName, sm);

        sm.activate();

    }

    /**

     * 停止订阅

     */

    public void removeEvent(String eventName){

        if(eventList.containsKey(eventName)){

            System.out.println("停用:"+eventName);

            ServerMain sm=new ServerMain();

            sm.stop();

            eventList.remove(eventName);

        }

    }

}

这样看起来就专业多,也严谨的多。

最后,在jsp页面的javaScript中应该注意的几个方面:

首先,不论你在后台是继承EventPullSource方法还是实现EventSource接口,都要在

Jsp页面的<head>标签里引入ajax-pushlet-client.js文件。

【关于继承EventPullSource:】

javaScript中写入以下代码并部署工程运行即可。

    <script type="text/javascript">

        PL._init(); 

        PL.joinListen('/source/event');  //事件标识 在数据源中引用

        function onData(event) { 

            alert(event.get("msg"));

        }

    </script>

【关于实现EventSource接口:】

javaScript中写入以下代码:

<script type="text/javascript">

        PL.setDebug(false);

        function onEvent(event){

            var did=event.get("did");

            alert(did);

            var data=event.get("data");

            var dname=event.get("dname");

            document.getElementById("chartdiv").innerHTML=did;

        }

        function listen(){

            PL.joinListen("/${id}");<!—这里监听的id是要唯一标识的,可以由后台传过来-->

        }

        function leave(){

            PL.leave();

        }

        

    </script>

查看js源码就可以看到这几个方法的作用,不过要注意的是我们要在页面的onload事件中调用其中的方法。如下:

<body onload="listen();return false;" onunload="leave();return false;">

………

</body>

如此运行就可以看到自己想要的效果。

以上是我自己在学习pushlet中的一些总结,当然里面有很大一部分是来自网络的支持,也有自己的看法,倘若有不正确之处,可以随时指正,这样我们可以共同进步。

 

pushlet笔记 3种发布方式

1.2.1. Direct Publishing (local)

1.2.1. Direct Publishing (local)

Your application may directly publish Events by using/src/nl/justobjects/pushlet/core/Dispatcher.getInstance().java . Since Dispatcher is (currently) a Singleton, sending the Event is a matter of callingDispatcher.getInstance().multicast()/unicast()/broadcast() .

The other two methods (EventSource and Pushlet protocol) will eventually callDispatcher.getInstance().multicast()/unicast()/broadcast() .

Note that in order to call Dispatcher.getInstance().multicast()/unicast()/broadcast() , your class needs to be in the same classloader as the Dispatcher.getInstance(). This may be an issue when for example your sender is in another web application. You may use the Pushlet protocol in that case or put pushlet.jar on the system CLASSPATH. In Tomcat you can also make pushlet.jar a shared library.

 

直接用Dispatcher的方法发布,可以向所有人广播,发布给订阅者,指定sessionid发布

1.2.2. Using EventSource

An EventSource is an object that is managed (activated/passivated) by the Pushlet framework. Developing your own EventSource involves creating a class that implements nl.justobjects.pushlet.core.EventSource(when your EventSource pushes Events to the framework) or that extendsnl.justobjects.pushlet.core.EventPullSource (when the framework should pull your EventSource at dedicated intervals) and adding your EventSource to a properties file aptly namedsources.properties .

See /webapps/pushlet/WEB-INF/classes/sources.properties for an example. This file specifies which EventSource objects need to be created and managed. Note: since v2.0.3 sources.properties can also be placed under WEB-INF. See /src/nl/justobjects/pushlet/core/EventSourceManager.java how this is done. See examples in /src/nl/justobjects/pushlet/test/TestEventPullSources where several example sources are bundled.

During initialization the EventSourceManager will look for the file sources.properties in the CLASSPATH. Usually this file will be put under WEB-INF/classes.

一个自己写的事件源类,需要配置在sources.properties ,要继承EventPullSource 或实现EventSource

1.2.3. Using the Pushlet protocol

The Chat and WebPresentation examples use the remote publication of events through the Pushlet (control) protocol. In a webapp the Pushlet JS API provides a p_publish() method through which your app may send events.

The /src/nl/justobjects/pushlet/test/PushletPingApplication.java provides a complete example illustrating sending and receiving Events and using/src/nl/justobjects/pushlet/client/PushletClient.java . DHTML clients may use the JavaScript pushlet library.

 

使用pushlet 协议 收发交互     可以跨服务器

 

Ajax-pushlet-clientpushletajax客户端方式,对该JS进行分析后发现,它是采用了面向对象的javascript技术和充分利用XMLHttpRequest对象来实现的HTTP长连接,达到了服务器技术。

 

1、属性
NV_P_FORMAT: 'p_format=xml-strict',//
数据格式,默认是严格严格XML

    NV_P_MODE: 'p_mode=pull', //pushlet采用的模式,默认为pull模式

    pushletURL: null,//请求URL地址

    webRoot: null,//项目根路径

    sessionId: null,//sessionId

    STATE_ERROR: -2,//一些状态常量

    STATE_ABORT: -1,

    STATE_NULL: 1,

    STATE_READY: 2,

    STATE_JOINED: 3,

    STATE_LISTENING: 3,

    state: 1,//状态

 

       statusMsg: 'null', //状态消息

    statusChanged: false,//状态是否发生了改变

    statusChar: '|',//状态分隔符

 

2、方法
       _init
:获取XMLHttpRequest对象

设定pushlet的请求URL

将状态置为STATE_READY.

 

    _doRequest (anEvent, aQuery):首先判断是不是出现错误,然后再判断是否需要等待状态,若不需要等待则构建查询字符串。最后调用_getXML(url, PL._onResponse)方法向服务器发出请求,后一个参数为回调方法。

 

    _getWebRoot:获取项目根目录,可以根据实际项目修改

 

    _getXML:以get方式请求URL,用同步或者异步方式

 

_onResponse(xml):先将xml转变为pushlet事件对象(_rsp2Events),得到多个PushletEvent对象,再调用_onEvent()处理每个事件。

 

_rsp2Eventsxml):取得事件标签(event),然后调用PushletEvent(),可能得到多个PushletEvent对象。

 

_onEventevent):处理由服务器端传来的事件,首先判断事件类型,由不同的类型调用不同的处理方法:

Data:调用_doCallback(event, window.onData),方法处理。onData是自定义;

Refresh:从event中取得刷新时间,然后调用_doRequest('refresh')刷新;

Error:将state置为STATE_ERROR,获取错误原因,调用_doCallback(event, window.onError)

join-ack:将状态置为” STATE_JOINED”,取得sessionId,调用_doCallback(event, window.onJoinAck)

join-listen-ack:将state设为STATE_LISTENING,取得sessionId,调用_doCallback(event, window.onJoinListenAck);

listen-ackhbhb-ackleave-ackrefresh-acksubscribe-ackunsubscribe-ackabort/nack$/等类型也类似。

 

_addEvent(elm, evType, callback, useCapture):取得elm对象,调用以下三者之一:

obj.addEventListener(evType, callback, useCapture);

obj.attachEvent('on' + evType, callback);

obj['on' + evType] = callback

ajax-pushlet-client.js初始化时调用了该方法:PL._addEvent(window, 'load', PL._init, false);即初始化时相当于:window.onload=PL._init();

 

_doCallback(eventcbFunction):如果指定了回调方法,则调用cbFunctionevent,如果没有指定那么调用window.onEvent(event)

 

       _getObjectobj):获取对象引用,若参数为对象直接返回该对象引用,若为字符串则按ID取得该对象。

 

 

PushletEventXML):与nl.justobjects.pushlet.Event相对应。其内部方法或属性如下:

Arr:数组,用于存放链值对;

getSubject():取得p_subject标签值;

getEvent():取得p_event标签值;

putname, value);将链值对放入arr中;

getname):按链取得值;

toString:转换为链值对字符串;

toTable:转换为表格;

 

将传入xml解析到arr中。

 

其对外公开方法:

heartbeat():实质是调用了PL._doRequest('hb'),向后台请求“hb”事件。再被封装成了:p_heartbeat()精简方式。

相似的方法还有:

Join_doRequest('join', PL.NV_P_FORMAT + '&' + PL.NV_P_MODE);

joinListenPL._doRequest('join-listen', query);请求加入同时监听;

leave_doRequest('leave')

listen(aSubject)_doRequest('listen', query);按主题监听;

publish(aSubject, theQueryArgs)_doRequest('publish', query);按主题发布;

subscribe(aSubject, aLabel)_doRequest('subscribe', query);按主题订阅;

unsubscribe(aSubscriptionId)_doRequest('unsubscribe', query);按订阅ID退订;

 

 

pushlet:为一个servlet拦截订阅请求,调用EventSourceManager激活配置文件sources.properties中的所有事件源。

EventSourceManager: 事件源管理器,加载sources.properties配置文件,读取事件类加入到一个verctor中,还包含事件的activatepassivatestop方法。

EventPullSource: 为一线程类,用于被用户继承,包含两个抽象方法getSleepTime()pullEvent(),前者返回线程睡眠时间,后者返回事件。线程启动后循环睡眠然后针对事件发送多播。Dispatcher.getInstance().multicast(event);

command命令类: 用于封装参数session,event,request,response

constroller: doPushlish doJoin doLeave doRefresh等一些主要的操作方法。

你可能感兴趣的:(Comet,Pushlet)