Web服务器推送技术介绍及Cometd使用例子

传统模式的 Web 系统以客户端发出请求、服务器端响应的方式工作。不能满足很多现实应用的需求,譬如:

监控系统:后台硬件温度、电压发生变化;
即时通信系统:其它用户登录、发送信息;
即时报价系统:后台数据库内容发生变化;
即时信息系统:微博、说说实时推送

    目前主流的是采取如下几种方式来实现以上需求:

    Ajax轮询:异步响应机制,即通过不间断的客户端 Ajax 请求,去发现服务端的变化。这种方式由于是客户端主动连接的,所以会有一定程度的延时,并且服务器的压力也不小。

    长轮询:原理是客户端发出一个http长连接请求,然后等待服务器的响应,服务器接到请求之后,并不立即发送出数据,而是hold住这个Connecton。这个处理是非阻塞的,所以服务器可以继续处理其他请求。在某个时刻,比如服务器有新数据了,服务器再主动把这个消息推送出去,即通过之前建立好的连接将数据推送给客户端。客户端收到返回。这个时候就可以处理数据,然后再次发起新的长连接。服务器压力一般,实时性很高。Servlet 3.0开始已经支持该技术。sina微博就是使用的原生Servlet 3实现的消息推送。

    套接字:可以利用 Flash 的 XMLSocket 类或者 Java 的 Applet 来建立 Socket 连接,实现全双工的服务器推送,然后通过 Flash 或者Applet 与 JavaScript 通信的接口来实现最终的数据推送。但是这种方式需要 Flash 或者 JVM 的支持,同样不太合适于终端用户。

    HTML5的WebSocket:这种方式其实与套接字一样,但是这里需要单独强调一下:它是不需要用户而外安装任何插件的。HTML5 提供了一个 WebSocket 的 JavaScript 接口,可以直接与服务端建立Socket 连接,实现全双工通信,这种方式的服务器推送就是完全意义上的服务器推送了,没有半点模拟的成分,只是现阶段支持 HTML5 的浏览器并不多,而且一般老版本的各种浏览器基本都不支持。不过 HTML5 是一套非常好的标准,在将来,当HTML5 流行起来以后将是我们实现服务器推送技术的不二选择。

    Ajax轮训压力大,套接字不适用,HTML5目前支持不大多,这样看来长轮询是我们的必然之选。 可是使用 servlet 3 实现自己要做的事很多。没有有什么可供选择的框架呢?当然是有了,如下:



1、Cometd
 
http://cometd.org/
 
    基于Bayeux协议实现,支持长轮询、WebSocket等..
 
 2、Pushlet
 
http://www.pushlets.com/
 
    基于HTTP流的JSP/Semlet技术实现
 
 3、Atmosphere
 
http://atmosphere.java.net/
 
 4、DWR
 
http://directwebremoting.org/dwr/index.html

        据我的了解,使用最多的应该是 DWR 和 Cometd了,其中Cometd功能强大使用又简单。我参与的一个项目中使用Cometd 做前台地图上人员轨迹的实时推送,几百个人员的实时轨迹推送都是没问题的。用。Cometd前台有2个实现版本 Jquery 和 Dojo,也可以用JS但是会复杂点。(PS Cometd 官方文档相当使用,遇到问题就读一遍,肯定能找到你想要的。 http://docs.cometd.org/reference/)

  如果你安装Maven了,以下2个简单的命令就能让你体验一把推送效果

1、mvn archetype:generate -DarchetypeCatalog=http://cometd.org (选择任意一个)
2、进入刚才创建的项目目录
3、mvn install jetty:run
4、前台访问 http://localhost:8080/刚才创建的项目名

    下面下载包我在项目中使用的实例。实例下载,请猛击这里 传送门

        你可能想向前台传送的数据是一个类而不仅仅是字符串,那么你可以使用Jackson让cometd帮你做自动的转换,而你的Java代码中是直接向客户端输出对象。甚至是List都可以。你需要如下操作:1、向前端输出的对象必须实现 Serializable , Cloneable 和 JSON.Convertible 接口

importjava.util.Date;
importjava.util.Map;
importorg.eclipse.jetty.util.ajax.JSON;
importorg.eclipse.jetty.util.ajax.JSON.Output;
importcom.yixun.util.TimeUtil;
publicclassAlarmrecordimplementsjava.io.Serializable  , Cloneable, JSON.Convertible{
    // Fields
    privateLong recordid;
    privateReaderinfo readerinfo;
    privateCardinfo cardinfo;
    privatePosition position;
    privateAlarminfo alarminfo;
    privateString alarmdetail;
    privateDate recordtime;
    privateString alarmType;
    privateString alarmState;
    /**
     * @see org.eclipse.jetty.util.ajax.JSON$Convertible#fromJSON(java.util.Map)
     * @author simon
     * create on Apr 14, 2012  11:58:41 AM
     */
    publicvoidfromJSON(Map map) {
        this.recordid = (Long) map.get("recordid");
        this.readerinfo = (Readerinfo) map.get("readerinfo");
        this.cardinfo = (Cardinfo) map.get("cardinfo");
        this.position = (Position) map.get("position");
        this.alarminfo = (Alarminfo) map.get("alarminfo");
        this.alarmdetail = (String) map.get("alarmdetail");
        this.alarmType = (String) map.get("alarmType");
        this.alarmState = (String) map.get("alarmState");
        this.recordtime = (Date) map.get("recordtime");
    }
 
    /**
     * @see org.eclipse.jetty.util.ajax.JSON$Convertible#toJSON(org.eclipse.jetty.util.ajax.JSON.Output)
     * @author simon
     * create on Apr 14, 2012  11:58:46 AM
     */
    publicvoidtoJSON(Output out) {
        out.add("recordid", recordid);
        out.add("readerinfo", readerinfo);
        out.add("cardinfo", cardinfo);
        out.add("position", position);
        out.add("alarminfo", alarminfo);
        out.add("alarmdetail", alarmdetail);
        out.add("recordtime", TimeUtil.format(recordtime,"yyyy-MM-dd HH:mm:ss"));
        out.add("alarmType", alarmType);
        out.add("alarmState", alarmState);
    }
}


2、Web.xml 中,org.cometd.server.CometdServlet 下添加 使用Jetty json 的参数。如下图:

<!-- 报警推送 -->
<servlet>
    <servlet-name>cometd</servlet-name>
    <servlet-class>org.cometd.server.CometdServlet</servlet-class>
    <init-param>
        <!-- 使用jetty json 模块 -->
        <param-name>jsonContext</param-name>
        <param-value>
            org.cometd.server.JettyJSONContextServer
        </param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>cometd</servlet-name>
    <url-pattern>/cometd/*</url-pattern>
</servlet-mapping>
 
<servlet>
    <servlet-name>initializer</servlet-name>
    <servlet-class>
        com.yixun.epolice.action.comet.BayeuxInitializer
    </servlet-class>
    <load-on-startup>2</load-on-startup>
</servlet>
<filter>
    <filter-name>cross-origin</filter-name>
    <filter-class>
        org.eclipse.jetty.servlets.CrossOriginFilter
    </filter-class>
</filter>
<filter-mapping>
    <filter-name>cross-origin</filter-name>
    <url-pattern>/cometd/*</url-pattern>
</filter-mapping>
<filter>
    <filter-name>continuation</filter-name>
    <filter-class>
        org.eclipse.jetty.continuation.ContinuationFilter
    </filter-class>
</filter>
<filter-mapping>
    <filter-name>continuation</filter-name>
    <url-pattern>/cometd/*</url-pattern>
</filter-mapping>
   



3、OK,这样你后台就可以直接这样写来向前台写数据了,cometd会帮你做对象到Json的转换。

public voidupdate(Object o) {
        // 对 "/alarm" 频道广播消息
        if(null!= getBayeux().getChannel("/alarm") && oinstanceofAlarmrecord ){
                getBayeux().getChannel("/alarm").publish(getServerSession(), (Alarmrecord)o,null);
        }
    }



4、前台这样获取数据:

if(handshake.successful ===true)
            {
                cometd.batch(function()
                {
                    cometd.subscribe('/alarm',function(message)
                    {
                        //对接收到的消息进行显示处理
                        varalarm = message.data;
                        if(len<1){//如果初始时表中没有行
                            $("#tableClass tbody").append("<tr bgcolor='#FFFFFF'>"+
                             "<td align='center'> <input type='checkbox' value='"+alarm.recordid+"'name='selecteditems'/></td>"+
                             "<td nowrap='nowrap' align='center'>"+alarm.cardinfo.cardid+"</td>"+
                             "<td nowrap='nowrap' align='center'>"+alarm.alarminfo.information+"</td>"+
                             "<td nowrap='nowrap' align='center'>"+alarm.alarmdetail+"</td>"+
                             "<td nowrap='nowrap' align='center'>"+alarm.position.positionName+"</td>"+
                             "<td nowrap='nowrap' align='center'>"+alarm.recordtime+"</td>"+
                             "<td nowrap='nowrap' align='center'>"+alarmState+"</td><tr>");
                        }
                   cometd.publish('/service/alarm', { });
                });
            }

5、另外有一点要说的是 如果你向前台写的对象 AlarmRecord中包含其他对象如Positon ,那么Position对象也要实现 Serializable , Cloneable 和 JSON.Convertible 接口

你可能感兴趣的:(Web服务器推送技术介绍及Cometd使用例子)