开源框架Pushlet入门

    

一、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$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
#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信息。下面是我关闭页面后,服务器端的输出信息,如图:

 
 
分类:   j2ee案例

     在这里讲解一下关于开源框架Pushlet中的定点推送消息和与浏览器参数交互 
   通过上面的方法我就可以完成点对点的网页版本的聊天软件了,当然需要达到上面的要求我们这里需要对Pushlet的源码进行改进。 
  首先,我这里就讲述Pushlet的入门配置,默认大家是了解Pushlet框架的。 
1. 与浏览器参数交互 
  需要定位一个客户端就必须要给这个客户设置一个唯一的key值,我这里就叫它为clientid,当然也有朋友会说,我使用Pushlet中每个Session的id来作为clientid,这样当然是可以的,但为了便于管理我们一般会生成自己的clientid来进行操作,我在这里要做的就是使用自己生成的clientid来替换Pushlet自己生成的Sesssion的id。 
  在Pushlet初始化去监听一个事件的时候我们是没有办法传递参数的,这里我就需要改动一下ajax-pushlet-client.js中的代码,来满足我们的需求,添加参数数组的定义,并且修改_doRequest函数如下代码: 

Java代码     收藏代码
  1. var PL = {  
  2.     NV_P_FORMAT: 'p_format=xml-strict',  
  3.     NV_P_MODE: 'p_mode=pull',  
  4.     pushletURL: null,  
  5.     webRoot: null,  
  6.     sessionId: null,  
  7.     parameters: new Array(new Array),// 对参数数组的定义  
  8.     STATE_ERROR: -2,  
  9.     STATE_ABORT: -1,  
  10.     STATE_NULL: 1,  
  11.     STATE_READY: 2,  
  12.     STATE_JOINED: 3,  
  13.     STATE_LISTENING: 3,  
  14.     state: 1,  
  15.       
  16.     ......  
  17.   
  18.     _doRequest: function(anEvent, aQuery) {  
  19.          ......  
  20.            
  21.          if(PL.parameters.length > 0) {  
  22.              for (var i = 0; i < PL.parameters.length; i++) {  
  23.                  var para = PL.parameters[i];  
  24.                  url += "&" + para.name + "=" + para.value;  
  25.              }  
  26.          }  
  27.            
  28.          ......      
  29.     },  


    加入代码后的_doRequest函数如下: 

Java代码     收藏代码
  1. _doRequest: function(anEvent, aQuery) {  
  2.      ......  
  3.        
  4.      // Construct base URL for GET  
  5.      var url = PL.pushletURL + '?p_event=' + anEvent;  
  6.   
  7.      // Optionally attach query string  
  8.      if (aQuery) {  
  9.          url = url + '&' + aQuery;  
  10.      }  
  11.   
  12.      // Optionally attach session id  
  13.      if (PL.sessionId != null) {  
  14.          url = url + '&p_id=' + PL.sessionId;  
  15.          if (anEvent == 'p_leave') {  
  16.              PL.sessionId = null;  
  17.          }  
  18.      }  
  19.        
  20.      if(PL.parameters.length > 0) {  
  21.          for (var i = 0; i < PL.parameters.length; i++) {  
  22.              var para = PL.parameters[i];  
  23.              url += "&" + para.name + "=" + para.value;  
  24.          }  
  25.      }  
  26.        
  27.      PL.debug('_doRequest', url);  
  28.      PL._getXML(url, PL._onResponse);  
  29.   
  30.  },  


    代码改好后就可以这样写我的Pushlet初始化代码了,传入我们所需要的clientid给服务器,代码如下: 

Java代码     收藏代码
  1. var initPushlet = function() {  
  2.      PL._init();  
  3.      PL.parameters.push({"clientid":"servicekey""value":"4214f0c0da6760a9e95e3c164998ac06"});  
  4.      PL.joinListen('/conversation');  
  5.  };  


  那么,客户端把参数传过来了服务端要怎么接收它呢?这就就需要修改一下Pushlet的java源码,首先我们修改nl.justobjects.pushlet.core.SessionManager中的createSession方法,如下: 

Java代码     收藏代码
  1. /** 
  2.  * Create new Session (but add later). 
  3.  */  
  4. public Session createSession(Event anEvent) throws PushletException {  
  5.     // Trivial  
  6.     return Session.create(anEvent);  
  7. }  


    再修改nl.justobjects.pushlet.core.Session中的create方法,如下: 

Java代码     收藏代码
  1. public static Session create(Event anEvent) throws PushletException {  
  2.     Session session;  
  3.     try {  
  4.         session = (Session) Config.getClass(SESSION_CLASS, "nl.justobjects.pushlet.core.Session").newInstance();  
  5.     } catch (Throwable t) {  
  6.         throw new PushletException("Cannot instantiate Session from config", t);  
  7.     }  
  8.   
  9.     // get clientid  
  10.     session.id = anEvent.getField("clientid");  
  11.     session.controller = Controller.create(session);  
  12.     session.subscriber = Subscriber.create(session);  
  13.     session.event = anEvent;  
  14.     return session;  
  15. }  


  上面就完成了SessionId的转变。 
  如果我们要完成一个点对点聊天的话客户端和服务器通信当然是必不可少,具体怎么向服务端发消息呢?在这里我使用里Pushlet的js中的p_publish()方法,示例代码如下: 

Java代码     收藏代码
  1. p_publish('answer','msg',encodeURIComponent('你好吗?'),'clientid','ce8954e8557fa9db8c1b2d6774e471a6');  


  上面方法第一个参数是:执行的操作命令command,后面是都是传给服务器的参数,是以一个参数名+参数值的形式。参数值如果存在中文的话需要使用encodeURIComponent()方法来进行编码再传输。 
  在服务端我们要接收这一个客户端发过来的消息并进行处理,我需要怎么接收它呢?就需要在nl.justobjects.pushlet.core.Controller这个类中的doCommand()方法中进行处理了,找到这个方法中的eventType.equals(Protocol.E_PUBLISH)判断内,并加入自己的处理代码,代码如下: 

Java代码     收藏代码
  1. public void doCommand(Command aCommand) {  
  2.     try {  
  3.         ......  
  4.         } else if (eventType.equals(Protocol.E_PUBLISH)) {  
  5.             // Publish event  
  6.             doPublish(aCommand);  
  7.               
  8.             // get command  
  9.             System.out.println(aCommand.reqEvent.getField("p_subject"));  
  10.             // get clientid  
  11.             System.out.println(aCommand.reqEvent.getField("clientid"));  
  12.             // get msg  
  13.             System.out.println(new String(aCommand.reqEvent.getField("msg").getBytes("ISO8859-1"), "UTF-8"));  
  14.         } else if (eventType.equals(Protocol.E_LISTEN)) {  
  15.             // Listen to pushed events  
  16.             doListen(aCommand);  
  17.         }  
  18.   
  19.         ......  
  20.     } catch (Throwable t) {  
  21.         warn("Exception in doCommand(): " + t);  
  22.         t.printStackTrace();  
  23.     }  
  24. }  


  aCommand.reqEvent.getField("p_subject"))就可以获得客户端传过来的command命令,aCommand.reqEvent.getField("参数名")来获取传过来参数。当然在正常的开发中,所用到的命令会很多,我们可以把每一个命令做成一个个子类去实现,完成不同的需求。至于怎么将消息发送给客户端,将在下面的定点推送消息中讲解。 

2. 定点推送消息 
  上面已经讲述了每个客户端都会存在一个clientid,这个clientid可以定位每一个客户端,那么既然我们需要向某一个客户端推送消息,那必然要知道这个客户端的clientid,这里我建议开发者自己写一个管理器用于管理连接上来的客户端,用于保存这些客户端的信息。如果需要开发聊天程序的话则需要写一个聊天会话管理器,用于管理一对对的聊天会话Session。 
  言归正传,我们接着1内容中的最后说,当我们在doCommand()方法中获得了某一个客户端传过来的消息时,我们可能需要做的是将处理的结果返回给它,或将它的消息发给另外一个客户端,至于怎么做呢?我们直接看代码,还是上面的代码加入了些内容如下: 

Java代码     收藏代码
  1. public void doCommand(Command aCommand) {  
  2.     try {  
  3.         ......  
  4.         } else if (eventType.equals(Protocol.E_PUBLISH)) {  
  5.             // Publish event  
  6.             doPublish(aCommand);  
  7.               
  8.             // get command  
  9.             System.out.println(aCommand.reqEvent.getField("p_subject"));  
  10.             // get clientid  
  11.             System.out.println(aCommand.reqEvent.getField("clientid"));  
  12.             // get msg  
  13.             System.out.println(new String(aCommand.reqEvent.getField("msg").getBytes("ISO8859-1"), "UTF-8"));  
  14.               
  15.             // 发送给其他客户端  
  16.             Event event = Event.createDataEvent("/conversation"); // 监听的事件  
  17.             event.setField("cmd""say");  
  18.             event.setField("code"100);  
  19.             event.setField("msg"new String(aCommand.reqEvent.getField("msg").getBytes("ISO8859-1"), "UTF-8"));  
  20.             Dispatcher.getInstance().unicast(event, aCommand.reqEvent.getField("clientid"));// 指定的clientid  
  21.               
  22.             // 回传给自己  
  23.             event = Event.createDataEvent("/conversation"); // 监听的事件  
  24.             event.setField("cmd""answerResult");  
  25.             event.setField("code"100);  
  26.             Dispatcher.getInstance().unicast(event, session.getId());// 当前sessionid即为clientid  
  27.               
  28.         } else if (eventType.equals(Protocol.E_LISTEN)) {  
  29.             // Listen to pushed events  
  30.             doListen(aCommand);  
  31.         }  
  32.   
  33.         ......  
  34.     } catch (Throwable t) {  
  35.         warn("Exception in doCommand(): " + t);  
  36.         t.printStackTrace();  
  37.     }  
  38. }  


  上面代码就实现了将消息发给指定客户端,以及回传消息给自己。客户端要如何接收服务端传过来的消息,见如下代码: 

Java代码     收藏代码
  1. function onData(event) {  
  2.      alert(event.get("cmd"));  
  3.      alert(event.get("code"));  
  4.      alert(event.get("msg"));  
  5. }  


  页面上写上此js就可以接收到服务器传过来的内容,内容全都包含在event当中了。 
总结:到此为止我们对Pushlet中的定点推送消息和与浏览器参数交互有了比较深入的学习,大家可以按照这个方案开发自己想要的东西了。 

  另:还有我想说的就是,Pushlet这个框架是基于Servlet来做的,Servlet是需要在Tomcat这样的容器中运行的,这样一来它的客户端连接数必然受到限制。还有由于Pushlet的机制使得它并不是那么的实时。接下来我想说的是通过http长连接的方式来做。 

猜你喜欢

转载自topmanopensource.iteye.com/blog/1906526