Java 實現 Comet 風格的 Web 應用的分析

         技術文檔鏈接地址以下:javascript

       http://www.ibm.com/developerworks/cn/web/wa-cometjava/html

      Comet的別稱反向 Ajax 或服務器端推技術(server-side push),我理解的定義是:使用http長鏈接從後臺主動推送數據到前臺的技術。java

      如今ajax很是流行,他解決了網頁所有刷新才能更新內容的方式,但它在一些使用場景中並不適用,好比,股票走勢的圖實時更新,我的通知的實時更新。這些須要實時更新的模塊使用ajax的定時刷新方式,體驗不好,當時我就想有沒有一種方式,若是後臺數據發生變化主動將變化信息傳給前臺相應的模塊中去呢,我google之,發現了Comet。它的使用特色是,一般的ajax請求當數據從後臺返回數據後與後臺的鏈接就會關閉,而Comet則一直創建着鏈接,後臺程序則保存了全部請求者的HttpServletResponse,若是後臺數據發生變化就會經過這個鏈接傳到前臺,這就會出現一個問題,若是100個用戶須要推送數據,後臺代碼必然須要保存着100個用戶的HttpServletResponse,若是是1000個用戶甚至更多呢,這是一個問題,還有一個問題就是雖然前臺刷新的次數少了,但後臺須要處理的就相應增多,這樣是否符合最優原則,第三個問題是他依賴於運行程序的容器。tomcat,glassfish等的處理方式有些區別,至於Comet是否值得使用還得經過實驗得出結論。jquery

    我用百度的echarts作了一個簡單的Comet長鏈接例子,在後臺Java程序中我設置了一個定時器,後臺會定時向前臺推送數據,實現了基本功能。長鏈接不關閉對瀏覽器來講也是很大的開銷。下面是例子的代碼。web

(1) 接受後臺請求的servletajax

package tbktest;

import java.io.IOException;
import java.util.Timer;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.catalina.CometEvent;
import org.apache.catalina.CometProcessor;



public class CometServlet extends HttpServlet implements CometProcessor {
    private static final long serialVersionUID = 1L;
    private static final Integer TIMEOUT = 60 * 1000;  
    Timer timer = new Timer();
    public CometServlet() {
        super();
    }


    public void event(CometEvent event) throws IOException, ServletException {
            HttpServletRequest request = event.getHttpServletRequest();  
            HttpServletResponse response = event.getHttpServletResponse();  
            
            try {
                    if (event.getEventType() == CometEvent.EventType.BEGIN) {  
                        request.setAttribute("org.apache.tomcat.comet.timeout", TIMEOUT);  
                        log("Begin for session: " + request.getSession(true).getId());  
                        MessageSender pMessageSender = new MessageSender();
                        pMessageSender.setConnection(response);

                        timer.schedule(pMessageSender, 5000,50000);
                          
                    } else if (event.getEventType() == CometEvent.EventType.ERROR) {  
                        log("Error for session: " + request.getSession(true).getId());  
                        event.close();  
                    } else if (event.getEventType() == CometEvent.EventType.END) {  
                        log("End for session: " + request.getSession(true).getId());  
                        event.close();  
                    } else if (event.getEventType() == CometEvent.EventType.READ) {  
                        throw new UnsupportedOperationException(  
                                "This servlet does not accept data");  
                    }  
                
            } catch (Exception e) {
                e.printStackTrace();
            }
            
    
        
    }


}

(2) servlet所用到的類apache

package tbktest;

import java.io.OutputStream;
import java.util.ArrayList;
import java.util.TimerTask;

import javax.servlet.ServletResponse;

public class MessageSender extends TimerTask {
    private static int mOne = 1;
    private static int mTwo = 1;
    private static int mThree = 1;
    private static int mFour = 1;
    private static int mFive = 1;
    private static int mSix = 1;
    private static int mSeven = 1;
    private static int mEigt = 1;
    private static int mNigh = 1;
    private static int mTen = 1;
    private static int mTel = 1;
    private static int mEs = 1;
    
    protected boolean running = true;  
  
    protected final ArrayList messages = new ArrayList();  
    
    protected  ArrayList<ServletResponse> connection = new ArrayList<ServletResponse>();  
  
  
    public synchronized void setConnection(ServletResponse connection) {  
        this.connection.add(connection);
        notify();  
    }  
  
    // 發送信息  
    public void send() {  
        // 同步隊列,加入發送信息  
        synchronized (messages) { 
             mOne = mOne+1;
             mTwo = mTwo+1;
             mThree = mThree+1;
             mFour = mFour+1;
             mFive = mFive+1;
             mSix = mSix+1;
             mSeven = mSeven+1;
             mEigt = mEigt+1;
             mNigh = mNigh+1;
             mTen = mTen+1;
             mTel = mTel+1;
             mEs = mEs+1;
             
             messages.add(mOne);
             messages.add(mTwo);
             messages.add(mThree);
             messages.add(mFour);
             messages.add(mFive);
             messages.add(mSix);
             messages.add(mSeven);
             messages.add(mEigt);
             messages.add(mNigh);
             messages.add(mTen);
             messages.add(mTel);
             messages.add(mEs);
            // 喚醒  
            messages.notify();  
        }  
    }  
  
    public void run() {  
        send();
        // 線程啓動  
        log("start");  
  
            if (messages.size() == 0) {  
                try {  
                    synchronized (messages) {  
                        log("MessageSender wait[空閒狀態,線程等待]");  
                        // 釋放鎖  
                        messages.wait();  
                    }  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                    // Ignore  
                }  
            }  
            String pendingMessages = null;  
            synchronized (messages) {  
                  
                // 導出發送的信息至數組  
                pendingMessages = messages.toString();  
                // 清空信息隊列  
                messages.clear();  
            }  
            try {  
                if (connection == null) {  
                    try {  
                        synchronized (this) {  
                            // 等待注入HTTP RESPONSE  
                            wait();  
                        }  
                    } catch (InterruptedException e) {  
                        // Ignore  
                        e.printStackTrace();  
                    }  
                }  
                  
                for(int i=0;i<connection.size();i++){
                     OutputStream out = connection.get(i).getOutputStream();  
                         final String forecast = pendingMessages;  
                         out.write(forecast.getBytes());  
                         out.flush();  
                         connection.get(i).flushBuffer();  
                         System.out.println(pendingMessages);
                }
                // 輸出流操做  
               
            } catch (Exception e) {  
                log("IOExeption sending message", e);  
            }   
        }
  
  
    // 中止  
    public void stop() {  
        running = false;  
    }  
  
    // 日誌  
    private void log(Object obj) {  
        System.out.println(obj);  
    }  
  
    // 日誌  
    private void log(Object obj, Throwable e) {  
        System.out.println(obj);  
        e.printStackTrace();  
    }  
}
View Code

(3) html頁面須要引入百度的echarts相關js文件數組

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>ECharts</title>
    <!--Step:1 Import a module loader, such as esl.js or require.js-->
    <!--Step:1 引入一個模塊加載器,如esl.js或者require.js-->
    <script src="js/esl.js"></script>
     <script src="js/jquery-1.9.0.min.js"></script>
</head>

<body>
    <!--Step:2 Prepare a dom for ECharts which (must) has size (width & hight)-->
    <!--Step:2 爲ECharts準備一個具有大小(寬高)的Dom-->
    <div id="main" style="height:500px;border:1px solid #ccc;padding:10px;"></div>
    
    <script type="text/javascript">
    $(document).ready(function(){ 
            try {
                    var request = new XMLHttpRequest();
                } catch (e) {
                    alert("Browser doesn't support window.XMLHttpRequest");
                }
                request.open("GET", "CometServlet", true);
                request.send(null);                          
                var pos = 0;
                request.onreadystatechange = function () {
                    if (request.readyState === 3) {
                         var mdata = new Array();
                        mdata = request.responseText;
                        require.config({
                            paths:{ 
                                echarts:'./js/echarts',
                                'echarts/chart/bar' : './js/echarts',
                                'echarts/chart/line': './js/echarts'
                            }
                        });
                        
                        // Step:4 require echarts and use it in the callback.
                        // Step:4 動態加載echarts而後在回調函數中開始使用,注意保持按需加載結構定義圖表路徑
                        require(
                            [
                                'echarts',
                                'echarts/chart/bar',
                                'echarts/chart/line'
                            ],
                            function(ec) {
                                var myChart = ec.init(document.getElementById('main'));
                                var option = {
                                    tooltip : {
                                        trigger: 'axis'
                                    },
                                    legend: {
                                        data:['蒸發量','降水量']
                                    },
                                    toolbox: {
                                        show : true,
                                        feature : {
                                            mark : true,
                                            dataView : {readOnly: false},
                                            magicType:['line', 'bar'],
                                            restore : true,
                                            saveAsImage : true
                                        }
                                    },
                                    calculable : true,
                                    xAxis : [
                                        {
                                            type : 'category',
                                            data : ['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月']
                                        }
                                    ],
                                    yAxis : [
                                        {
                                            type : 'value',
                                            splitArea : {show : true}
                                        }
                                    ],
                                    series : [
                                        {
                                            name:'蒸發量',
                                            type:'bar',
                                            data:mdata
                                        },
                                        {
                                            name:'降水量',
                                            type:'bar',
                                            data:[]
                                        }
                                    ]
                                };
                                
                                myChart.setOption(option);
                            }
                        );
                    }
                    
                };

    //$.ajax({
       // type : "get",
       // url: "CometServlet",
      // dataType:"text",
       // success: function(data) {
            // = data;
            
            
   // });
    });
    // Step:3 conifg ECharts's path, link to echarts.js from current page.
    // Step:3 爲模塊加載器配置echarts的路徑,從當前頁面連接到echarts.js,定義所需圖表路徑
    
    </script>
</body>
</html>
View Code
相關文章
相關標籤/搜索