技術文檔鏈接地址以下: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(); } }
(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>