基於HTTP的長鏈接,是一種經過長輪詢方式實現"服務器推"的技術,它彌補了HTTP簡單的請求應答模式的不足,極大地加強了程序的實時性和交互性。javascript
1、什麼是長鏈接、長輪詢?html
用通俗易懂的話來講,就是客戶端不停的向服務器發送請求以獲取最新的數據信息。這裏的「不停」實際上是有中止的,只是咱們人眼沒法分辨是否中止,它只是一種快速的停下而後又當即開始鏈接而已。html5
2、長鏈接、長輪詢的應用場景java
長鏈接、長輪詢通常應用與WebIM、ChatRoom和一些須要及時交互的網站應用中。其真實案例有:WebQQ、Hi網頁版、Facebook IM等。jquery
若是你對服務器端的反向Ajax感興趣,能夠參考這篇文章 DWR 反向Ajax 服務器端推的方式:http://www.cnblogs.com/hoojo/category/276235.htmlweb
歡迎你們繼續支持和關注個人博客:ajax
http://blog.csdn.net/IBM_hoojo後端
也歡迎你們和我交流、探討IT方面的知識。跨域
email:hoojo_@126.com
3、優缺點
輪詢:客戶端定時向服務器發送Ajax請求,服務器接到請求後立刻返回響應信息並關閉鏈接。
優勢:後端程序編寫比較容易。
缺點:請求中有大半是無用,浪費帶寬和服務器資源。
實例:適於小型應用。
長輪詢:客戶端向服務器發送Ajax請求,服務器接到請求後hold住鏈接,直到有新消息才返回響應信息並關閉鏈接,客戶端處理完響應信息後再向服務器發送新的請求。
優勢:在無消息的狀況下不會頻繁的請求,耗費資源小。
缺點:服務器hold鏈接會消耗資源,返回數據順序無保證,難於管理維護。
實例:WebQQ、Hi網頁版、Facebook IM。長鏈接:在頁面裏嵌入一個隱蔵iframe,將這個隱蔵iframe的src屬性設爲對一個長鏈接的請求或是採用xhr請求,服務器端就能源源不斷地往客戶端輸入數據。
優勢:消息即時到達,不發無用請求;管理起來也相對方便。
缺點:服務器維護一個長鏈接會增長開銷。
實例:Gmail聊天
Flash Socket:在頁面中內嵌入一個使用了Socket類的 Flash 程序JavaScript經過調用此Flash程序提供的Socket接口與服務器端的Socket接口進行通訊,JavaScript在收到服務器端傳送的信息後控制頁面的顯示。
優勢:實現真正的即時通訊,而不是僞即時。
缺點:客戶端必須安裝Flash插件;非HTTP協議,沒法自動穿越防火牆。
實例:網絡互動遊戲。
4、實現原理
所謂長鏈接,就是要在客戶端與服務器之間建立和保持穩定可靠的鏈接。其實它是一種很早就存在的技術,可是因爲瀏覽器技術的發展比較緩慢,沒有爲這種 機制的實現提供很好的支持。因此要達到這種效果,須要客戶端和服務器的程序共同配合來完成。一般的作法是,在服務器的程序中加入一個死循環,在循環中監測 數據的變更。當發現新數據時,當即將其輸出給瀏覽器並斷開鏈接,瀏覽器在收到數據後,再次發起請求以進入下一個週期,這就是常說的長輪詢(long- polling)方式。以下圖所示,它一般包含如下幾個關鍵過程:
1. 輪詢的創建
創建輪詢的過程很簡單,瀏覽器發起請求後進入循環等待狀態,此時因爲服務器還未作出應答,因此HTTP也一直處於鏈接狀態中。
2. 數據的推送
在循環過程當中,服務器程序對數據變更進行監控,如發現更新,將該信息輸出給瀏覽器,隨即斷開鏈接,完成應答過程,實現「服務器推」。
3. 輪詢的終止
輪詢可能在如下3種狀況時終止:
3.1. 有新數據推送
當循環過程當中服務器向瀏覽器推送信息後,應該主動結束程序運行從而讓鏈接斷開,這樣瀏覽器才能及時收到數據。
3.2. 沒有新數據推送
循環不能一直持續下去,應該設定一個最長時限,避免WEB服務器超時(Timeout),若一直沒有新信息,服務器應主動向瀏覽器發送本次輪詢無新信息的正常響應,並斷開鏈接,這也被稱爲「心跳」信息。
3.3. 網絡故障或異常
因爲網絡故障等因素形成的請求超時或出錯也可能致使輪詢的意外中斷,此時瀏覽器將收到錯誤信息。
4. 輪詢的重建
瀏覽器收到回覆並進行相應處理後,應立刻從新發起請求,開始一個新的輪詢週期。
5、程序設計
一、普通輪詢 Ajax方式
客戶端代碼片斷
1 <%@ page language="java" import="java.util.*" pageEncoding="UTF-8" isELIgnored="false" %> 2 3 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 4 5 <html> 6 7 <head> 8 9 <meta http-equiv="pragma" content="no-cache"> 10 11 <meta http-equiv="cache-control" content="no-cache"> 12 13 <meta http-equiv="author" content="hoojo & http://hoojo.cnblogs.com"> 14 15 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 16 17 <%@ include file="/tags/jquery-lib.jsp"%> 18 19 20 21 <script type="text/javascript"> 22 23 $(function () { 24 25 26 27 window.setInterval(function () { 28 29 30 31 $.get("${pageContext.request.contextPath}/communication/user/ajax.mvc", 32 33 {"timed": new Date().getTime()}, 34 35 function (data) { 36 37 $("#logs").append("[data: " + data + " ]<br/>"); 38 39 }); 40 41 }, 3000); 42 43 44 45 }); 46 47 </script> 48 49 </head> 50 51 52 53 <body> 54 55 <div id="logs"></div> 56 57 </body> 58 59 </html>
客戶端實現的就是用一種普通輪詢的結果,比較簡單。利用setInterval不間斷的刷新來獲取服務器的資源,這種方式的優勢就是簡單、及時。缺 點是連接多數是無效重複的;響應的結果沒有順序(由於是異步請求,當發送的請求沒有返回結果的時候,後面的請求又被髮送。而此時若是後面的請求比前面的請 求要先返回結果,那麼當前面的請求返回結果數據時已是過期無效的數據了);請求多,難於維護、浪費服務器和網絡資源。
服務器端代碼
1 @RequestMapping("/ajax") 2 3 public void ajax(long timed, HttpServletResponse response) throws Exception { 4 5 PrintWriter writer = response.getWriter(); 6 7 8 9 Random rand = new Random(); 10 11 // 死循環 查詢有無數據變化 12 13 while (true) { 14 15 Thread.sleep(300); // 休眠300毫秒,模擬處理業務等 16 17 int i = rand.nextInt(100); // 產生一個0-100之間的隨機數 18 19 if (i > 20 && i < 56) { // 若是隨機數在20-56之間就視爲有效數據,模擬數據發生變化 20 21 long responseTime = System.currentTimeMillis(); 22 23 // 返回數據信息,請求時間、返回數據時間、耗時 24 25 writer.print("result: " + i + ", response time: " + responseTime + ", request time: " + timed + ", use time: "26 + (responseTime - timed) ); 27 break; // 跳出循環,返回數據 28 29 } else { // 模擬沒有數據變化,將休眠 hold住鏈接 30 31 Thread.sleep(1300); 32 33 } 34 35 } 36 37 38 39 }
服務器端實現,這裏就模擬下程序監控數據的變化。上面代碼屬於SpringMVC 中controller中的一個方法,至關於Servlet中的一個doPost/doGet方法。若是沒有程序環境適應servlet便可,將方法體中 的代碼copy到servlet的doGet/doPost中便可。
服務器端在進行長鏈接的程序設計時,要注意如下幾點:
1. 服務器程序對輪詢的可控性
因爲輪詢是用死循環的方式實現的,因此在算法上要保證程序對什麼時候退出循環有徹底的控制能力,避免進入死循環而耗盡服務器資源。
2. 合理選擇「心跳」頻率
從圖1能夠看出,長鏈接必須由客戶端不停地進行請求來維持,因此在客戶端和服務器間保持正常的「心跳」至爲關鍵,參數POLLING_LIFE應小於WEB服務器的超時時間,通常建議在10~20秒左右。
3. 網絡因素的影響
在實際應用時,從服務器作出應答,到下一次循環的創建,是有時間延遲的,延遲時間的長短受網絡傳輸等多種因素影響,在這段時間內,長連 接處於暫時斷開的空檔,若是剛好有數據在這段時間內發生變更,服務器是沒法當即進行推送的,因此,在算法設計上要注意解決因爲延遲可能形成的數據丟失問 題。
4. 服務器的性能
在長鏈接應用中,服務器與每一個客戶端實例都保持一個持久的鏈接,這將消耗大量服務器資源,特別是在一些大型應用系統中更是如此,大量併發的長鏈接有可能導 致新的請求被阻塞甚至系統崩潰,因此,在進行程序設計時應特別注意算法的優化和改進,必要時還須要考慮服務器的負載均衡和集羣技術。
上圖是返回的結果,能夠看到先發出請求,不必定會最早返回結果。這樣就不能保證順序,形成髒數據或無用的鏈接請求。可見對服務器或網絡的資源浪費。
二、普通輪詢 iframe方式
1 <%@ page language="java" import="java.util.*" pageEncoding="UTF-8" isELIgnored="false" %> 2 3 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 4 5 <html> 6 7 <head> 8 9 <meta http-equiv="pragma" content="no-cache"> 10 11 <meta http-equiv="cache-control" content="no-cache"> 12 13 <meta http-equiv="expires" content="0"> 14 15 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 16 17 <%@ include file="/tags/jquery-lib.jsp"%> 18 19 20 21 <script type="text/javascript"> 22 23 $(function () { 24 25 26 27 window.setInterval(function () { 28 29 $("#logs").append("[data: " + $($("#frame").get(0).contentDocument).find("body").text() + " ]<br/>"); 30 31 $("#frame").attr("src", "${pageContext.request.contextPath}/communication/user/ajax.mvc?timed=" + new Date().getTime()); 32 33 // 延遲1秒再從新請求 34 35 window.setTimeout(function () { 36 37 window.frames["polling"].location.reload(); 38 39 }, 1000); 40 41 }, 5000); 42 43 44 45 }); 46 47 </script> 48 49 </head> 50 51 52 53 <body> 54 55 <iframe id="frame" name="polling" style="display: none;"></iframe> 56 57 <div id="logs"></div> 58 59 </body> 60 61 </html>
這裏的客戶端程序是利用隱藏的iframe向服務器端不停的拉取數據,將iframe獲取後的數據填充到頁面中便可。同ajax實現的基本原理一 樣,惟一不一樣的是當一個請求沒有響應返回數據的狀況下,下一個請求也將開始,這時候前面的請求將被中止。若是要使程序和上面的ajax請求同樣也能夠辦 到,那就是給每一個請求分配一個獨立的iframe便可。下面是返回的結果:
其中紅色是沒有成功返回請求就被中止(後面請求開始)掉的請求,黑色是成功返回數據的請求。
三、長鏈接iframe方式
1 <%@ page language="java" import="java.util.*" pageEncoding="UTF-8" isELIgnored="false" %> 2 3 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 4 5 <html> 6 7 <head> 8 9 <meta http-equiv="pragma" content="no-cache"> 10 11 <meta http-equiv="cache-control" content="no-cache"> 12 13 <meta http-equiv="author" content="hoojo & http://hoojo.cnblogs.com"> 14 15 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 16 17 <%@ include file="/tags/jquery-lib.jsp"%> 18 19 20 21 <script type="text/javascript"> 22 23 $(function () { 24 25 26 27 window.setInterval(function () { 28 29 var url = "${pageContext.request.contextPath}/communication/user/ajax.mvc?timed=" + new Date().getTime(); 30 31 var $iframe = $('<iframe id="frame" name="polling" style="display: none;" src="' + url + '"></iframe>'); 32 33 $("body").append($iframe); 34 35 36 37 $iframe.load(function () { 38 39 $("#logs").append("[data: " + $($iframe.get(0).contentDocument).find("body").text() + " ]<br/>"); 40 41 $iframe.remove(); 42 43 }); 44 45 }, 5000); 46 47 48 49 }); 50 51 </script> 52 53 </head> 54 55 56 57 <body> 58 59 60 61 <div id="logs"></div> 62 63 </body> 64 65 </html>
這個輪詢方式就是把剛纔上面的稍微改下,每一個請求都有本身獨立的一個iframe,當這個iframe獲得響應的數據後就把數據push到當前頁面 上。使用此方法已經相似於ajax的異步交互了,這種方法也是不能保證順序的、比較耗費資源、並且老是有一個加載的條在地址欄或狀態欄附件(固然要解決可 以利用htmlfile,Google的攻城師們已經作到了,網上也有封裝好的lib庫),但客戶端實現起來比較簡單。
若是要保證有序,能夠不使用setInterval,將建立iframe的方法放在load事件中便可,即便用遞歸方式。調整後的代碼片斷以下:
1 <script type="text/javascript"> 2 3 $(function () { 4 5 (function iframePolling() { 6 7 var url = "${pageContext.request.contextPath}/communication/user/ajax.mvc?timed=" + new Date().getTime(); 8 9 var $iframe = $('<iframe id="frame" name="polling" style="display: none;" src="' + url + '"></iframe>'); 10 11 $("body").append($iframe); 12 13 14 15 $iframe.load(function () { 16 17 $("#logs").append("[data: " + $($iframe.get(0).contentDocument).find("body").text() + " ]<br/>"); 18 19 $iframe.remove(); 20 21 22 23 // 遞歸 24 25 iframePolling(); 26 27 }); 28 29 })(); 30 31 }); 32 33 </script>
這種方式雖然保證了請求的順序,可是它不會處理請求延時的錯誤或是說很長時間沒有返回結果的請求,它會一直等到返回請求後才能建立下一個iframe請求,總會和服務器保持一個鏈接。和以上輪詢比較,缺點就是消息不及時,但保證了請求的順序。
四、ajax實現長鏈接
1 <%@ page language="java" import="java.util.*" pageEncoding="UTF-8" isELIgnored="false" %> 2 3 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 4 5 <html> 6 7 <head> 8 9 <meta http-equiv="pragma" content="no-cache"> 10 11 <meta http-equiv="cache-control" content="no-cache"> 12 13 <meta http-equiv="expires" content="0"> 14 15 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 16 17 <%@ include file="/tags/jquery-lib.jsp"%> 18 19 20 21 <script type="text/javascript"> 22 23 $(function () { 24 25 26 27 (function longPolling() { 28 29 30 31 $.ajax({ 32 33 url: "${pageContext.request.contextPath}/communication/user/ajax.mvc", 34 35 data: {"timed": new Date().getTime()}, 36 37 dataType: "text", 38 39 timeout: 5000, 40 41 error: function (XMLHttpRequest, textStatus, errorThrown) { 42 43 $("#state").append("[state: " + textStatus + ", error: " + errorThrown + " ]<br/>"); 44 45 if (textStatus == "timeout") { // 請求超時 46 47 longPolling(); // 遞歸調用 48 49 50 51 // 其餘錯誤,如網絡錯誤等 52 53 } else { 54 55 longPolling(); 56 57 } 58 59 }, 60 61 success: function (data, textStatus) { 62 63 $("#state").append("[state: " + textStatus + ", data: { " + data + "} ]<br/>"); 64 65 66 67 if (textStatus == "success") { // 請求成功 68 69 longPolling(); 70 71 } 72 73 } 74 75 }); 76 77 })(); 78 79 80 81 }); 82 83 </script> 84 85 </head> 86 <body> 87 </html> 88 89
上面這段代碼就是纔有Ajax的方式完成長鏈接,主要優勢就是和服務器始終保持一個鏈接。若是當前鏈接請求成功後,將更新數據而且繼續建立一個新的 鏈接和服務器保持聯繫。若是鏈接超時或發生異常,這個時候程序也會建立一個新鏈接繼續請求。這樣就大大節省了服務器和網絡資源,提升了程序的性能,從而也 保證了程序的順序。
6、總結
現代的瀏覽器都支持跨域資源共享(Cross-Origin Resource Share,CORS)規範,該規範容許XHR執行跨域請求,所以基於腳本的和基於iframe的技術已成爲了一種過期的須要。
把Comet作爲反向Ajax的實現和使用的最好方式是經過XMLHttpRequest對象,該作法提供了一個真正的鏈接句柄和錯誤處理。固然你 選擇經由HTTP長輪詢使用XMLHttpRequest對象(在服務器端掛起的一個簡單的Ajax請求)的Comet模式,全部支持Ajax的瀏覽器也 都支持該種作法。
基於HTTP的長鏈接技術,是目前在純瀏覽器環境下進行即時交互類應用開發的理想選擇,隨着瀏覽器的快速發展,html5將爲其提供更好的支持和更 普遍的應用。在html5中有一個websocket 能夠很友好的完成長鏈接這一技術,網上也有相關方面的資料,這裏也就再也不作過多介紹。