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、Cometdhttp://cometd.org/ 基於Bayeux協議實現,支持長輪詢、WebSocket等.. 2、Pushlethttp://www.pushlets.com/ 基於HTTP流的JSP/Semlet技術實現 3、Atmospherehttp://atmosphere.java.net/ 4、DWRhttp://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 接口

import java.util.Date;import java.util.Map;import org.eclipse.jetty.util.ajax.JSON;import org.eclipse.jetty.util.ajax.JSON.Output;import com.yixun.util.TimeUtil;public class Alarmrecord implements java.io.Serializable , Cloneable, JSON.Convertible{ // Fields private Long recordid; private Readerinfo readerinfo; private Cardinfo cardinfo; private Position position; private Alarminfo alarminfo; private String alarmdetail; private Date recordtime; private String alarmType; private String alarmState; /** * @see org.eclipse.jetty.util.ajax.JSON$Convertible#fromJSON(java.util.Map) * @author simon * create on Apr 14, 2012 11:58:41 AM */ public void fromJSON(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 */ public void toJSON(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 void update(Object o) { // 對 "/alarm" 頻道廣播消息 if( null!= getBayeux().getChannel("/alarm") && o instanceof Alarmrecord ){ getBayeux().getChannel("/alarm").publish(getServerSession(), (Alarmrecord)o,null); } }



4、前臺這樣獲取數據:

if (handshake.successful === true) { cometd.batch(function() { cometd.subscribe('/alarm', function(message) { //對接收到的消息進行顯示處理 var alarm = 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 接口

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章