posts - 156,  comments - 601,  trackbacks - 0
                                      Jetty cometd(Continuation)學習筆記

          關鍵字: Jetty, Cometd, Continuation, dojo

          前言:
          為了更容易的掌握Jetty cometd的使用方法,本筆記通過講解Jetty 6.0自帶的chat演示程序來輔助講解Jetty Cometd的實現。


          環境配置方法:
          服務器端:
              類庫清單:WEB-INF/lib
                  jetty-6.1.9.jar
                  jetty-util-6.1.9.jar
                  servlet-api-2.5-6.1.9.jar
                  (以上Jetty服務器自帶)
                  cometd-api-0.9.20080221.jar
                  cometd-bayeux-6.1.9.jar

              web.xml配置:
               
             <!-- 配置ContinuationCometdServlet, 這個是必須的。配置后就可以支持comted -->
            
          <servlet>
              
          <servlet-name>cometd</servlet-name>
              
          <servlet-class>org.mortbay.cometd.continuation.ContinuationCometdServlet</servlet-class>
              
          <!-- 對隊列的內容進行過濾 -->
              
          <init-param>
                
          <param-name>filters</param-name>
                
          <param-value>/WEB-INF/filters.json</param-value>
              
          </init-param>
              
          <!-- 超時設置The server side poll timeout in milliseconds (default 250000). This is how long the server will
          hold a reconnect request before responding. 
          -->
              
          <init-param>
                
          <param-name>timeout</param-name>
                
          <param-value>120000</param-value>
              
          </init-param>
              
          <!-- The client side poll timeout in milliseconds (default 0). How long a client will wait between
          reconnects 
          -->
              
          <init-param>
                
          <param-name>interval</param-name>
                
          <param-value>0</param-value>
              
          </init-param>
              
          <!-- the client side poll timeout if multiple connections are detected from the same browser
          (default 1500). 
          -->
              
          <init-param>
                
          <param-name>multiFrameInterval</param-name>
                
          <param-value>1500</param-value>
              
          </init-param>
              
          <!-- 0=none, 1=info, 2=debug -->
              
          <init-param>
                
          <param-name>logLevel</param-name>
                
          <param-value>0</param-value>
              
          </init-param>
              
          <!-- If "true" then the server will accept JSON wrapped in a comment and will generate JSON wrapped
          in a comment. This is a defence against Ajax Hijacking. 
          -->
              
          <init-param>
                
          <param-name>JSONCommented</param-name>
                
          <param-value>true</param-value>
              
          </init-param>

              
          <init-param>
                
          <param-name>alwaysResumePoll</param-name>
                
          <param-value>false</param-value> <!-- use true for x-site cometd -->
              
          </init-param>
              
          <load-on-startup>1</load-on-startup>
            
          </servlet>

            
          <servlet-mapping>
              
          <servlet-name>cometd</servlet-name>
              
          <url-pattern>/cometd/*</url-pattern>
            
          </servlet-mapping> 

          filters.json內容如下:

          格式如下:
              {
                  "channels": "/**", --要過濾的隊列(支持通配符)
                  "filter":"org.mortbay.cometd.filter.NoMarkupFilter", --使用的過濾器,實現接口dojox.cometd.DataFilter
                  "init"    : {} --初始化的值,調用 DataFilter.init方法傳入
              }

          示例內容如下:

          [
            {
              
          "channels""/**",
              
          "filter"  : "org.mortbay.cometd.filter.NoMarkupFilter",
              
          "init"    : {}
            }
          ,

            {
              
          "channels""/chat/*",
              
          "filter"   : "org.mortbay.cometd.filter.RegexFilter",
              
          "init"    : [
                            [ "[fF].ck","dang" ],
                            [ 
          "teh ","the "]
                          ]
            },
           
            {
              
          "channels""/chat/**",
              
          "filter"   : "org.mortbay.cometd.filter.RegexFilter",
              
          "init"    : [
                            [ 
          "[Mm]icrosoft""Micro\\$oft" ],
                            [ 
          ".*tomcat.*", null ]
                          ]
            }
          ]



          這時,服務器端的配置就已經完成的,基本的cometd功能就可以使用了。
          客戶端通過dojox.cometd.init("http://127.0.0.2:8080/cometd");就可以進行連接。

          代碼開發:

          接下來,我們要準備客戶端(使用dojo來實現)

          一共三個文件
          index.html
          chat.js
          chat.css(不是必須)

          下面來看一下這兩個文件的內容(加入注釋)
          index.html

          <html>
          <head>
              
          <title>Cometd chat</title>
              
          <script type="text/javascript" src="../dojo/dojo/dojo.js"></script><!-- dojo類庫 -->
              
          <script type="text/javascript" src="../dojo/dojox/cometd.js.uncompressed.js"></script><!-- dojo-cometd類庫 -->
              
          <script type="text/javascript" src="chat.js"></script><!-- chat js文件,控制cometd的連接,消息的發送與接收 -->
              
          <link rel="stylesheet" type="text/css" href="chat.css">
          </head>
          <body>
          <h1>Cometd Chat</h1>

          <div id="chatroom">
           
          <div id="chat"></div>
           
          <div id="input">
             
          <div id="join" ><!-- 未登錄時,顯示的登錄名和登錄按鈕 -->
               Username:
          &nbsp;<input id="username" type="text"/>
          <
          input id="joinB" class="button" type="submit" name="join" value="Join"/>
             
          </div>
             
          <div id="joined" class="hidden"><!-- 登錄后,顯示的消息框和發送,退出按鈕(默認為隱藏) -->
               Chat:
          &nbsp;<input id="phrase" type="text"></input>
               
          <input id="sendB" class="button" type="submit" name="join" value="Send"/>
               
          <input id="leaveB" class="button" type="submit" name="join" value="Leave"/>
             
          </div>
            
          </div>
           
          </div>

          </body>

          chat.js文件
            1 //引入所需要的類
            2 dojo.require("dojox.cometd");
            3 dojo.require("dojox.cometd.timestamp");
            4 
            5 //定義一個room類
            6 var room = {
            7     //定義屬性
            8     _last: ""//最后發送消息的人員(如果不是本人,則顯示為空) 
            9     _username: null//當前的用戶名
           10     _connected: true//當前的連接狀態 true已經連接, false表示未連接
           11     groupName: "whimsical"//組名(未知)
           12 
           13     //登錄操作
           14     join: function(name){
           15 
           16         if(name == null || name.length==0 ){
           17             alert('Please enter a username!');
           18         }else{
           19 
           20             dojox.cometd.init(
          new String(document.location).replace(/http:\/\/[^\/]*/,'').replace(/\/examples\/.*$/,'')+"/cometd");
           21             // dojox.cometd.init("http://127.0.0.2:8080/cometd");
           22             this._connected = true;
           23 
           24             this._username = name;
           25             dojo.byId('join').className='hidden';
           26             dojo.byId('joined').className='';
           27             dojo.byId('phrase').focus();
           28 
           29             // subscribe and join
           30             dojox.cometd.startBatch();
           31             dojox.cometd.subscribe("/chat/demo", room, "_chat", { groupName: this.groupName});
           32             dojox.cometd.publish("/chat/demo", { 
           33                 user: room._username,
           34                 join: true,
           35                 chat : room._username+" has joined"
           36             }, { groupName: this.groupName });
           37             dojox.cometd.endBatch();
           38 
           39             // handle cometd failures while in the room
           40             room._meta = dojo.subscribe("/cometd/meta"thisfunction(event){
           41                 console.debug(event);   
           42                 if(event.action == "handshake"){
           43                     room._chat({ data: {
           44                         join: true,
           45                         user:"SERVER",
           46                         chat:"reinitialized"
           47                     } });
           48                     dojox.cometd.subscribe("/chat/demo", room, "_chat", { groupName: this.groupName });
           49                 }else if(event.action == "connect"){
           50                     if(event.successful && !this._connected){
           51                         room._chat({ data: {
           52                             leave: true,
           53                             user: "SERVER",
           54                             chat: "reconnected!"
           55                         } });
           56                     }
           57                     if(!event.successful && this._connected){
           58                         room._chat({ data: {
           59                             leave: true,
           60                             user: "SERVER",
           61                             chat: "disconnected!"
           62                         } });
           63                     }
           64                     this._connected = event.successful;
           65                 }
           66             }, {groupName: this.groupName });
           67         }
           68     },
           69 
           70     //離開操作
           71     leave: function(){
           72         if(!room._username){
           73             return;
           74         }
           75 
           76         if(room._meta){
           77             dojo.unsubscribe(room._meta, nullnull, { groupName: this.groupName });
           78         }
           79         room._meta=null;
           80 
           81         dojox.cometd.startBatch();
           82         dojox.cometd.unsubscribe("/chat/demo", room, "_chat", { groupName: this.groupName });
           83         dojox.cometd.publish("/chat/demo", { 
           84             user: room._username,
           85             leave: true,
           86             chat : room._username+" has left"
           87         }, { groupName: this.groupName });
           88         dojox.cometd.endBatch();
           89 
           90         // switch the input form
           91         dojo.byId('join').className='';
           92         dojo.byId('joined').className='hidden';
           93         dojo.byId('username').focus();
           94         room._username = null;
           95         dojox.cometd.disconnect();
           96     },
           97 
           98     //發送消息
           99     chat: function(text){
          100         if(!text || !text.length){
          101             return false;
          102         }
          103         dojox.cometd.publish("/chat/demo", { user: room._username, chat: text}, { groupName: this.groupName });
          104     },
          105 
          106     //從服務器收到消息后,回調的方法
          107     _chat: function(message){
          108         var chat=dojo.byId('chat');
          109         if(!message.data){
          110             console.debug("bad message format "+message);
          111             return;
          112         }
          113         var from=message.data.user;
          114         var special=message.data.join || message.data.leave;
          115         var text=message.data.chat;
          116         if(!text){ return; }
          117 
          118         if!special && from == room._last ){
          119             from="";
          120         }else{
          121             room._last=from;
          122             from+=":";
          123         }
          124 
          125         if(special){
          126             chat.innerHTML += "<span class=\"alert\"><span class=\"from\">"+from+"&nbsp;
          </span><span class=\
          "text\">"+text+"</span></span><br/>";
          127             room._last="";
          128         }else{
          129             chat.innerHTML += "<span class=\"from\">"+from+"&nbsp;</span><span class=\"text\">"+text+"</span><br/>";
          130         } 
          131         chat.scrollTop = chat.scrollHeight - chat.clientHeight;    
          132     },
          133     
          134     //初始操作
          135     _init: function(){
          136         dojo.byId('join').className='';
          137         dojo.byId('joined').className='hidden';
          138         dojo.byId('username').focus();
          139 
          140         var element=dojo.byId('username');
          141         element.setAttribute("autocomplete","OFF"); 
          142         dojo.connect(element, "onkeyup"function(e){ //支持回車,登錄  
          143             if(e.keyCode == dojo.keys.ENTER){
          144                 room.join(dojo.byId('username').value);
          145                 return false;
          146             }
          147             return true;
          148         });
          149 
          150         dojo.connect(dojo.byId('joinB'), "onclick"function(e){ //綁定 room.join方法到 Join按扭
          151             room.join(dojo.byId('username').value);
          152             e.preventDefault();
          153         });
          154 
          155         element=dojo.byId('phrase');//取得消息框
          156         element.setAttribute("autocomplete","OFF");
          157         dojo.connect(element, "onkeyup"function(e){ //支持回車發送消息 
          158             if(e.keyCode == dojo.keys.ENTER){
          159                 room.chat(dojo.byId('phrase').value);
          160                 dojo.byId('phrase').value='';
          161                 e.preventDefault();
          162             }
          163         });
          164 
          165     dojo.connect(dojo.byId('sendB'), "onclick"function(e){  //綁定 room.chat方法到 sendB按扭 
          166             room.chat(dojo.byId('phrase').value);
          167             dojo.byId('phrase').value='';
          168     });
          169         dojo.connect(dojo.byId('leaveB'), "onclick", room, "leave"); //綁定 room.leave方法到 leaveB按扭 
          170     } 
          171 };
          172 
          173 //頁面裝載時,調用room._init方法
          174 dojo.addOnLoad(room, "_init");
          175 //頁面關閉時,調用 room.leave方法
          176 dojo.addOnUnload(room,"leave");
          177 
          178 //vim:ts=4:noet:

          補充:服務器端如何監控消息隊列,以及進行訂閱,發送消息操作

          要進行 監控消息隊列,以及進行訂閱,發送消息操作的關鍵就是取得 Bayeux接口實現類 的實例

          可以通過 ServletContextAttributeListener 這個監聽器接口,通過attributeAdded方式加入

          實現方法如下:

           1 public class BayeuxStartupListener implements ServletContextAttributeListener
           2 {
           3     public void initialize(Bayeux bayeux)
           4     {
           5         synchronized(bayeux)
           6         {
           7             if (!bayeux.hasChannel("/service/echo"))
           8             {
           9                                 //取得 bayeux實例               
          10             }
          11         }
          12     }
          13     
          14     public void attributeAdded(ServletContextAttributeEvent scab)
          15     {
          16         if (scab.getName().equals(Bayeux.DOJOX_COMETD_BAYEUX))
          17         {
          18             Bayeux bayeux=(Bayeux) scab.getValue();
          19             initialize(bayeux);
          20         }
          21     }
          22 
          23     public void attributeRemoved(ServletContextAttributeEvent scab)
          24     {
          25 
          26     }
          27 
          28     public void attributeReplaced(ServletContextAttributeEvent scab)
          29     {
          30 
          31     }
          32 }

          取到 Bayeux實例后,就可以借助BayeuxService類幫我們實現消息隊列的監聽,訂閱消息以及發送消息
           1     public void initialize(Bayeux bayeux)
           2     {
           3         synchronized(bayeux)
           4         {
           5             if (!bayeux.hasChannel("/service/echo"))
           6             {
           7                                 //取得 bayeux實例  
           8                                 new ChatService(bayeux);             
           9             }
          10         }
          11     }

          具體方法請看下面這段代碼:
           1 //定義 ChatService類,繼承 BayeuxService
           2 public static class ChatService extends BayeuxService {
           3 
           4     ConcurrentMap<String,Set<String>> _members = new ConcurrentHashMap<String,Set<String>>();
           5     
           6     public ChatService(Bayeux bayeux)
           7     {
           8         super(bayeux, "chat");//必須,把 Bayeux傳入到 BayeuxService對象中
           9         subscribe("/chat/**""trackMembers"); //訂閱隊列,收到消息后,會回調trackMembers方法
          10         /*
          11             subscribe支持回調的方法如下:
          12             # myMethod(Client fromClient, Object data)
          13                     # myMethod(Client fromClient, Object data, String id)
          14                     # myMethod(Client fromClient, String channel, Object data,String id)
          15             # myMethod(Client fromClient, Message message)
          16             
          17                 參數:
          18                     Client fromClient 發送消息的客戶端
          19                     Object data 消息內容
          20                     id The id of the message 
          21                     channel 隊列名稱
          22                     Message message 消息對象。繼承于Map
          23         
          24         */
          25     }
          26     
          27     //發布消息到隊列
          28     public void sendMessage(String message) {
          29             Map<String,Object> mydata = new HashMap<String, Object>();
          30             mydata.put("chat", message);
          31             
          32             Client sender = getBayeux().newClient("server");
          33             
          34             getBayeux().getChannel("/chat/demo"false).publish(sender, mydata, "0"/*null*/);
          35 
          36     }
          37     
          38     //發送消息給指定的client(非廣播方式)
          39     public void sendMessageToClient(Client joiner, String message) {
          40             Map<String,Object> mydata = new HashMap<String, Object>();
          41             mydata.put("chat", message);
          42            
          43             send(joiner, "/chat/demo", mydata, "0"/*null*/);
          44     }    
          45     
          46     //訂閱消息回調方法
          47     public void trackMembers(Client joiner, String channel, Map<String,Object> data, String id)
          48     {
          49             //解釋消息內容,如果消息內容中 有 join這個字段且值為true
          50         if (Boolean.TRUE.equals(data.get("join")))
          51         {
          52                 //根據隊列,取得當前登錄的人員
          53             Set<String> m = _members.get(channel);
          54             if (m==null)
          55             {
          56                     //如果為空,則創建一個新的Set實現
          57                 Set<String> new_list=new CopyOnWriteArraySet<String>();
          58                 m=_members.putIfAbsent(channel,new_list);
          59                 if (m==null)
          60                     m=new_list;
          61             }
          62             
          63             final Set<String> members=m;
          64             final String username=(String)data.get("user");
          65             
          66             members.add(username);
          67                         //為該client增加事件,Remove事件。當用戶退出時,觸發該方法。            
          68             joiner.addListener(new RemoveListener(){
          69                 public void removed(String clientId, boolean timeout)
          70                 {
          71                     members.remove(username);
          72                 }
          73             });
          74 
          75                         //為該client增加事件,消息的發送和接收事件。當用戶退出時,觸發該方法。
          76             joiner.addListener(new MessageListener() {
          77                                 public void deliver(Client fromClient, Client toClient, Message message) {
          78                                     System.out.println("message from " + fromClient.getId() + " to "
          79                                             + toClient.getId() + " message is " + message.getData());
          80                                 }      
          81             });
          82 
          83             Map<String,Object> mydata = new HashMap<String, Object>();
          84             mydata.put("chat""members=" + members);
          85             //把已經登錄的人員信息列表,發送回給消息發送者
          86             send(joiner,channel,mydata,id);
          87          
          88         }
          89     }
          90 }
          91 

           附:部分使用到的API文檔

          dojox.cometd.subscribe

          dojo.require("dojox.cometd._base");
          defined in dojox/cometd/_base.js

          dojox.cometd.subscribe() handles all the hard work of telling the server that we want to be notified when events are published on a particular topic. subscribe accepts a function to handle messages and returns a dojo.Deferred object which has an extra property added to it which makes it suitable for passing to dojox.cometd.unsubscribe() as a “subscription handle” (much like the handle object that dojo.connect() produces and which dojo.disconnect() expects).

          Note that of a subscription is registered before a connection with the server is established, events sent before the connection is established will not be delivered to this client. The deferred object which subscribe returns will callback when the server successfuly acknolwedges receipt of our “subscribe” request.

          Usage

          var foo=dojox.cometd.subscribe(channel: String, objOrFunc: Object, funcName: String, props: Object?); (view source)
          parametertypedescription
          channel String  
          objOrFunc Object an object scope for funcName or the name or reference to a function to be called when messages are delivered to the
          funcName String the second half of the objOrFunc/funcName pair for identifying a callback function to notifiy upon channel message delivery
          props Object Optional.

          Examples

          Example 1

          Simple subscribe use-case

          dojox.cometd.init("http://myserver.com:8080/cometd");
          // log out all incoming messages on /foo/bar
          dojox.cometd.subscribe("/foo/bar", console, "debug");

          Example 2

          Subscribe before connection is initialized

          dojox.cometd.subscribe("/foo/bar", console, "debug");
          dojox.cometd.init("http://myserver.com:8080/cometd");

          Example 3

          Subscribe an unsubscribe

          dojox.cometd.init("http://myserver.com:8080/cometd");
          var h = dojox.cometd.subscribe("/foo/bar", console, "debug");
          dojox.cometd.unsubscribe(h);

          Example 4

          Listen for successful subscription:

          dojox.cometd.init("http://myserver.com:8080/cometd");
          var h = dojox.cometd.subscribe("/foo/bar", console, "debug");
          h.addCallback(function(){
          console.debug("subscription to /foo/bar established");
          });

          dojox.cometd.publish

          dojo.require("dojox.cometd._base");
          defined in dojox/cometd/_base.js
          publishes the passed message to the cometd server for delivery on the specified topic

          Usage

          var foo=dojox.cometd.publish(channel: String, data: Object, props: Object?); (view source)
          parametertypedescription
          channel String the destination channel for the message
          data Object a JSON object containing the message "payload" properties: Optional. Other meta-data to be mixed into the top-level of the message
          props Object Optional.





          Good Luck!
          Yours Matthew!
          posted on 2008-11-20 20:04 x.matthew 閱讀(13941) 評論(10)  編輯  收藏 所屬分類: Rest
          主站蜘蛛池模板: 图片| 华池县| 松溪县| 武夷山市| 栾城县| 清涧县| 嘉鱼县| 扬州市| 红河县| 漳州市| 栾城县| 五原县| 泰安市| 冕宁县| 巢湖市| 黄石市| 格尔木市| 石家庄市| 根河市| 汉沽区| 平谷区| 阳高县| 日喀则市| 靖西县| 南安市| 斗六市| 巴青县| 太仓市| 南投县| 天全县| 穆棱市| 石嘴山市| 汶上县| 德钦县| 三都| 漳平市| 安顺市| 永安市| 仙游县| 蒙城县| 上杭县|