「服務器推」技術之使用HTTP長輪詢的Comet

功能特性

  • 推送消息廣播。 javascript

  • 推送定向消息。 html

  • 提供鏈接上線前、上線、下線前、下線、發送消息等多種可處理事件。 java

  • 消息緩存機制,確保長輪詢工做模式下不丟失消息。 web

  • 客戶端正常下線,服務端可當即感知。 apache

  • 客戶端異常中止工做,服務端可定時檢查並感知。 編程

  • 以註冊通道應用的方式,讓開發者對框架功能進行擴展,實現本身的應用。 瀏覽器

框架特性

  • 獨立小巧,不依賴於第三方包。 緩存

  • 與應用緊密集成,無需獨立應用或服務器。 tomcat

  • 與Session無關的鏈接機制,爲開發人員提供最大程度的功能可控性。 服務器

  • 面向事件編程,客戶端與服務器端均爲事件驅動開發模式,提供了良好的可擴展性機制。

  • 各項性能參數都可配置。

  • 支持多種主流瀏覽器,並支持Air應用環境。

服務器支持狀況

Tomcat六、Tomcat7

瀏覽器支持狀況

支持XMLHTTPRequest對象的瀏覽器都可支持長輪詢工做模式,但不必定可以支持長鏈接。

瀏覽器/平臺 版本 長輪詢 長鏈接
Internet Explorer 6,7,8,9,10,11 X
FireFox 3.0+(更底版本未知)
Chrome 7.0+(更底版本未知)
Safari 5+(更底版本未知)
Opera 11.10+(更底版本未知) X
Air 1.5+(更底版本未知)
IOS(Iphone/Ipad) 3.1+(更底版本未知)
Android 未測試 未知 未知
BlackBerry 未測試 未知 未知

使用方法

    下載相關的jar包(根據本身使用的tomcat6仍是7選擇)和相關js並放到本身項目當中

     comet4j-tomcat6.jar     comet4j-tomcat7.jar     comet4j.js

    Comet 流是按照長輪詢的實現思路進一步發展的產物。令長輪詢將事件通知發送回客戶端後再也不關閉鏈接,而是一直保持直到超時事件發生才從新創建新的鏈接,這種變體咱們就稱爲 Comet 流。客戶端可使用 XmlHttpRequest 對象中的 readyState 屬性來判斷是 Receiving 仍是 Loaded。Comet 流在服務端和客戶端都須要維持一個比較長時間的鏈接狀態,這一點在客戶端不算什麼太大的負擔,可是服務端是要同時對多個客戶端服務的,按照經典 Request-Response 交互模型,每個請求都佔用一個 Web 線程不釋放的話,Web 容器的線程則會很快消耗殆盡,而這些線程大部分時間處於空閒等待的狀態。因此建議選擇comet4j-tomcat7.jar ,由於Servlet 3.0的新特性異步處理只有在tomcat7.0及以上才支持,這樣咱們Web 線程不須要同步的、一對一的處理客戶端請求,能作到一個 Web 線程處理多個客戶端請求。並且就算之後用HTML5的websocket也須要至少是tomcat7.0。

    修改tomcat服務器配置文件

     由於Comet4J工做在NIO方式下(自tomcat6.0後提供的Advanced NIO 技術以便一個 Servlet 線程能處理多個 Http Request),因此咱們須要調整服務器鏈接器配置,更換爲NIO鏈接器。 打開server.xml文件將找到原先的鏈接器配置:

<Connector port="8080" protocol="HTTP/1.1" 
               connectionTimeout="20000" 
               redirectPort="8443" />

    更改成:

<Connector URIEncoding="UTF-8" connectionTimeout="20000" 
            port="8080" 
            protocol="org.apache.coyote.http11.Http11NioProtocol" 
            redirectPort="8443"/>

  在web.xml中加載Comet4J框架

<listener>
    <listener-class>org.comet4j.core.CometAppListener</listener-class>
</listener>
<servlet>
    <display-name>CometServlet</display-name>
    <servlet-name>CometServlet</servlet-name>
    <servlet-class>org.comet4j.core.CometServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>CometServlet</servlet-name>
    <url-pattern>/conn</url-pattern>
</servlet-mapping>

    客戶端

<!DOCTYPE>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Comet4J Hello World</title>
<script type="text/javascript" src="js/comet4j.js"></script>
<script type="text/javascript">
function init(){
        var kbDom = document.getElementById('kb');
        JS.Engine.start('conn');
        JS.Engine.on({
                hello : function(kb){//偵聽一個channel
                        kbDom.innerHTML = kb;
                }
        });
        JS.Engine.on(
            'start',
            function(cId,channelList,engine){
                alert('鏈接已創建,鏈接ID爲:' + cId);
        });
}
</script>
</head>
<body onload="init()">
        項目組成員:<span id="kb">...</span>
</body>
</html>

    JS.Engine類是一個靜態類,它負責服務器的鏈接與斷開以及把服務器推過來的消息轉化爲事件分發。

    JS.Engine.start(String str)和JS.Engine.stop(String str)分別控制鏈接和斷開動做。

    start方法須要傳入一個字符串參數,用來指定您配置的Comet4J鏈接地址。好比按前面準備工做的配置了CometServlet的地址爲/conn,那麼鏈接就用:

JS.Engine.start('conn');

    stop方法能夠傳參與不傳參,傳參爲對斷開鏈接的說明。

JS.Engine.stop();
JS.Engine.stop('主動斷開');

    JS.Engine.on方法來註冊事件處理。當使用start方法成功創建鏈接已後JS.Engine會發出"start"事件,當斷開後會發出「stop」事件,當收到某個通道推送過來的信息時也會發出與通道標識同名的事件。

JS.Engine.on({
    hello : function(kb){//創建hello信道標識,當客戶端對該信道推送消息時客戶端就會接收並響應
        kbDom.innerHTML = kb;
    }
});
JS.Engine.on(
    'start',
    function(cId,channelList,engine){
        alert('鏈接已創建,鏈接ID爲:' + cId);
});
JS.Engine.on('stop',function(cause, cId, url, engine){    
    alert('鏈接已斷開,鏈接ID爲:' + cId + ',斷開緣由:' + cause + ',斷開的鏈接地址:'+ url);
});

    on方法註冊事件可使用 on({name1:function(){...},name2:function(){...}})或者on('name',function(){...})兩種方式。

    特別注意:以上代碼在事件處理函數中使用了alert僅爲說明函數功能,實際使用中,在事件處理函數中切勿使用alert、prompt、confirm等能夠中斷腳本運行的函數,由於Engine須要實時的保持工做狀態。

    服務端

    服務端由一個Jar包組成,其中最重的是CometContext和CometEngine兩個類。

    Comet Context 類

    CometContext是一個單態類,經過其getInstance方法來得到實例,它主要負責框架的一些初始化工做保存着一些參數的配置值,除此以外它還有一個更重要的職責——負責註冊應用通道標識。若是您想使用框架來實現本身的應用,那麼您必須要爲本身的應用分配一個惟一的通道標識,並將此通道標識在WEB容器啓動時使用CometContext的registChannel方法進行註冊,這樣,客戶端才能夠正確接受此應用所推送的消息。

    Comet Engine 類

    CometContext,它除了負責對鏈接的處理以外,對於開發人員而言,更加經常使用的多是它所提供的sendTo或sendToAll方法來向客戶端發送消息。sendTo方法是將消息推送給全部指定信道的客戶端,而sendToAll方法是將消息推送給指定信道的一個或多個鏈接ID的客戶端。

String channel = "hello";
String someConnectionId = "1125-6634-888";
engine.sendToAll(channel , "我來了!");
engine.sendTo(channel , engine.getConnection(someConnectionId),「Hi,我是XXX」);

    CometEngine另一個很重要的地方在於,它是框架工做的事件引擎的集散地,它提供了BeforeConnectEvent、BeforeDropEvent、ConnectEvent、DropEvent、MessageEvent等事件。經過對這些事件的處理來實現具體的功能:

class JoinListener extends ConnectListener {        
    @Override        
    public boolean handleEvent(ConnectEvent anEvent) {                
        CometConnection conn = anEvent.getConn();                
        CometContext.getInstance().getEngine().sendTo("hello", conn.getId(),"歡迎上線");        
    }
}
CometEngine engine = CometContext.getInstance().getEngine();
engine.addConnectListener(new JoinListener());

    測試代碼:

class HelloWorld implements ServletContextListener {
        //定義信道標識
        private static final String CHANNEL = "hello";
        public void contextInitialized(ServletContextEvent arg0) {
                //獲取CometContext實例
                CometContext cc = CometContext.getInstance();
                註冊hello信道
                cc.registChannel(CHANNEL);
                //模擬推送 開啓線程
                Thread helloAppModule = new Thread(new HelloAppModule(), "Sender App Module");
                //設置爲守護線程
                helloAppModule.setDaemon(true);
                helloAppModule.start();
        }
        class HelloAppModule implements Runnable {
                public void run() {
                        while (true) {
                                try {
                                        //線程休眠模擬推送
                                        Thread.sleep(1000);
                                } catch (Exception ex) {
                                        ex.printStackTrace();
                                }
                                CometEngine engine = CometContext.getInstance().getEngine();
                                engine.sendToAll(CHANNEL, getName());
                        }
                }
        }
        
        public String getName(){
            String[]names = new String[]{"馮春雷","吳兆元","夏勝安","李瑞東","田東東","張文良","劉權","邵帥","肖小良","任銀","王繼偉"};
            int number = new Random().nextInt(10) + 1;
            return names[number];
        }
        public void contextDestroyed(ServletContextEvent arg0) {
        }
}

運行項目代碼:

成功!!!

相關文章
相關標籤/搜索