隨筆-57  評論-117  文章-1  trackbacks-0

          基于HTTP的長連接,是一種通過長輪詢方式實現"服務器推"的技術,它彌補了HTTP簡單的請求應答模式的不足,極大地增強了程序的實時性和交互性。

           

          一、什么是長連接、長輪詢?

          用通俗易懂的話來說,就是客戶端不停的向服務器發送請求以獲取最新的數據信息。這里的“不停”其實是有停止的,只是我們人眼無法分辨是否停止,它只是一種快速的停下然后又立即開始連接而已。

           

          二、長連接、長輪詢的應用場景

          長連接、長輪詢一般應用與WebIM、ChatRoom和一些需要及時交互的網站應用中。其真實案例有:WebQQ、Hi網頁版、Facebook IM等。

          如果你對服務器端的反向Ajax感興趣,可以參考這篇文章 DWR 反向Ajax 服務器端推的方式:http://www.cnblogs.com/hoojo/category/276235.html

          歡迎大家繼續支持和關注我的博客:

          http://hoojo.cnblogs.com

          http://blog.csdn.net/IBM_hoojo

          也歡迎大家和我交流、探討IT方面的知識。

          email:hoojo_@126.com

           

          三、優缺點

          輪詢:客戶端定時向服務器發送Ajax請求,服務器接到請求后馬上返回響應信息并關閉連接。
          優點:后端程序編寫比較容易。
          缺點:請求中有大半是無用,浪費帶寬和服務器資源。
          實例:適于小型應用。


          長輪詢:客戶端向服務器發送Ajax請求,服務器接到請求后hold住連接,直到有新消息才返回響應信息并關閉連接,客戶端處理完響應信息后再向服務器發送新的請求。
          優點:在無消息的情況下不會頻繁的請求,耗費資源小。
          缺點:服務器hold連接會消耗資源,返回數據順序無保證,難于管理維護。
          實例:WebQQ、Hi網頁版、Facebook IM。

          長連接:在頁面里嵌入一個隱蔵iframe,將這個隱蔵iframe的src屬性設為對一個長連接的請求或是采用xhr請求,服務器端就能源源不斷地往客戶端輸入數據。
          優點:消息即時到達,不發無用請求;管理起來也相對方便。
          缺點:服務器維護一個長連接會增加開銷。
          實例:Gmail聊天


          Flash Socket:在頁面中內嵌入一個使用了Socket類的 Flash 程序JavaScript通過調用此Flash程序提供的Socket接口與服務器端的Socket接口進行通信,JavaScript在收到服務器端傳送的信息后控制頁面的顯示。
          優點:實現真正的即時通信,而不是偽即時。
          缺點:客戶端必須安裝Flash插件;非HTTP協議,無法自動穿越防火墻。
          實例:網絡互動游戲。

           

          四、實現原理

          所謂長連接,就是要在客戶端與服務器之間創建和保持穩定可靠的連接。其實它是一種很早就存在的技術,但是由于瀏覽器技術的發展比較緩慢,沒有為這種機制的實現提供很好的支持。所以要達到這種效果,需要客戶端和服務器的程序共同配合來完成。通常的做法是,在服務器的程序中加入一個死循環,在循環中監測數據的變動。當發現新數據時,立即將其輸出給瀏覽器并斷開連接,瀏覽器在收到數據后,再次發起請求以進入下一個周期,這就是常說的長輪詢(long-polling)方式。如下圖所示,它通常包含以下幾個關鍵過程:

          image

          1. 輪詢的建立
          建立輪詢的過程很簡單,瀏覽器發起請求后進入循環等待狀態,此時由于服務器還未做出應答,所以HTTP也一直處于連接狀態中。
          2. 數據的推送
          在循環過程中,服務器程序對數據變動進行監控,如發現更新,將該信息輸出給瀏覽器,隨即斷開連接,完成應答過程,實現“服務器推”。
          3. 輪詢的終止
          輪詢可能在以下3種情況時終止:
            3.1. 有新數據推送
             當循環過程中服務器向瀏覽器推送信息后,應該主動結束程序運行從而讓連接斷開,這樣瀏覽器才能及時收到數據。
            3.2. 沒有新數據推送
             循環不能一直持續下去,應該設定一個最長時限,避免WEB服務器超時(Timeout),若一直沒有新信息,服務器應主動向瀏覽器發送本次輪詢無新信息的正常響應,并斷開連接,這也被稱為“心跳”信息。
            3.3. 網絡故障或異常
             由于網絡故障等因素造成的請求超時或出錯也可能導致輪詢的意外中斷,此時瀏覽器將收到錯誤信息。
          4. 輪詢的重建
          瀏覽器收到回復并進行相應處理后,應馬上重新發起請求,開始一個新的輪詢周期。

           

          五、程序設計

          1、普通輪詢 Ajax方式

          客戶端代碼片段

          <%@ page language="java" import="java.util.*" pageEncoding="UTF-8" isELIgnored="false" %>
          <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
          <html>
              <head>
                  <meta http-equiv="pragma" content="no-cache">
                  <meta http-equiv="cache-control" content="no-cache">
                  <meta http-equiv="author" content="hoojo & http://hoojo.cnblogs.com">
                  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
                  <%@ include file="/tags/jquery-lib.jsp"%>
                  
                  <script type="text/javascript">
                      $(function () {
                      
                          window.setInterval(function () {
                          
                              $.get("${pageContext.request.contextPath}/communication/user/ajax.mvc", 
                                  {"timed": new Date().getTime()}, 
                                  function (data) {
                                      $("#logs").append("[data: " + data + " ]<br/>");
                              });
                          }, 3000);
                          
                      });
                  </script>
              </head>
              
              <body>
                  <div id="logs"></div>
              </body>
          </html>

          客戶端實現的就是用一種普通輪詢的結果,比較簡單。利用setInterval不間斷的刷新來獲取服務器的資源,這種方式的優點就是簡單、及時。缺點是鏈接多數是無效重復的;響應的結果沒有順序(因為是異步請求,當發送的請求沒有返回結果的時候,后面的請求又被發送。而此時如果后面的請求比前面的請求要先返回結果,那么當前面的請求返回結果數據時已經是過時無效的數據了);請求多,難于維護、浪費服務器和網絡資源。

          服務器端代碼

          @RequestMapping("/ajax")
          public void ajax(long timed, HttpServletResponse response) throws Exception {
               PrintWriter writer = response.getWriter();
               
               Random rand = new Random();
               // 死循環 查詢有無數據變化
               while (true) {
                   Thread.sleep(300); // 休眠300毫秒,模擬處理業務等
                   int i = rand.nextInt(100); // 產生一個0-100之間的隨機數
                   if (i > 20 && i < 56) { // 如果隨機數在20-56之間就視為有效數據,模擬數據發生變化
                       long responseTime = System.currentTimeMillis();
                       // 返回數據信息,請求時間、返回數據時間、耗時
                       writer.print("result: " + i + ", response time: " + responseTime + ", request time: " + timed + ", use time: " + (responseTime - timed));
                       break; // 跳出循環,返回數據
                   } else { // 模擬沒有數據變化,將休眠 hold住連接
                       Thread.sleep(1300);
                   }
               }
               
          }

          服務器端實現,這里就模擬下程序監控數據的變化。上面代碼屬于SpringMVC 中controller中的一個方法,相當于Servlet中的一個doPost/doGet方法。如果沒有程序環境適應servlet即可,將方法體中的代碼copy到servlet的doGet/doPost中即可。

          服務器端在進行長連接的程序設計時,要注意以下幾點:
          1. 服務器程序對輪詢的可控性

          由于輪詢是用死循環的方式實現的,所以在算法上要保證程序對何時退出循環有完全的控制能力,避免進入死循環而耗盡服務器資源。
          2. 合理選擇“心跳”頻率
          從圖1可以看出,長連接必須由客戶端不停地進行請求來維持,所以在客戶端和服務器間保持正常的“心跳”至為關鍵,參數POLLING_LIFE應小于WEB服務器的超時時間,一般建議在10~20秒左右。
          3. 網絡因素的影響
          在實際應用時,從服務器做出應答,到下一次循環的建立,是有時間延遲的,延遲時間的長短受網絡傳輸等多種因素影響,在這段時間內,長連接處于暫時斷開的空檔,如果恰好有數據在這段時間內發生變動,服務器是無法立即進行推送的,所以,在算法設計上要注意解決由于延遲可能造成的數據丟失問題。
          4. 服務器的性能
          在長連接應用中,服務器與每個客戶端實例都保持一個持久的連接,這將消耗大量服務器資源,特別是在一些大型應用系統中更是如此,大量并發的長連接有可能導致新的請求被阻塞甚至系統崩潰,所以,在進行程序設計時應特別注意算法的優化和改進,必要時還需要考慮服務器的負載均衡和集群技術。

          image

          上圖是返回的結果,可以看到先發出請求,不一定會最先返回結果。這樣就不能保證順序,造成臟數據或無用的連接請求。可見對服務器或網絡的資源浪費。

          2、普通輪詢 iframe方式

          <%@ page language="java" import="java.util.*" pageEncoding="UTF-8" isELIgnored="false" %>
          <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
          <html>
              <head>
                  <meta http-equiv="pragma" content="no-cache">
                  <meta http-equiv="cache-control" content="no-cache">
                  <meta http-equiv="expires" content="0">
                  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
                  <%@ include file="/tags/jquery-lib.jsp"%>
                  
                  <script type="text/javascript">
                      $(function () {
                      
                          window.setInterval(function () {
                              $("#logs").append("[data: " + $($("#frame").get(0).contentDocument).find("body").text() + " ]<br/>");
                              $("#frame").attr("src", "${pageContext.request.contextPath}/communication/user/ajax.mvc?timed=" + new Date().getTime());
                              // 延遲1秒再重新請求
                              window.setTimeout(function () {
                                  window.frames["polling"].location.reload();
                              }, 1000);
                          }, 5000);
                          
                      });
                  </script>
              </head>
              
              <body>
                  <iframe id="frame" name="polling" style="display: none;"></iframe>
                  <div id="logs"></div>
              </body>
          </html>

          這里的客戶端程序是利用隱藏的iframe向服務器端不停的拉取數據,將iframe獲取后的數據填充到頁面中即可。同ajax實現的基本原理一樣,唯一不同的是當一個請求沒有響應返回數據的情況下,下一個請求也將開始,這時候前面的請求將被停止。如果要使程序和上面的ajax請求一樣也可以辦到,那就是給每個請求分配一個獨立的iframe即可。下面是返回的結果:

          image

          其中紅色是沒有成功返回請求就被停止(后面請求開始)掉的請求,黑色是成功返回數據的請求。

          3、長連接iframe方式

          <%@ page language="java" import="java.util.*" pageEncoding="UTF-8" isELIgnored="false" %>
          <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
          <html>
              <head>
                  <meta http-equiv="pragma" content="no-cache">
                  <meta http-equiv="cache-control" content="no-cache">
                  <meta http-equiv="author" content="hoojo & http://hoojo.cnblogs.com">
                  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
                  <%@ include file="/tags/jquery-lib.jsp"%>
                  
                  <script type="text/javascript">
                      $(function () {
                      
                          window.setInterval(function () {
                              var url = "${pageContext.request.contextPath}/communication/user/ajax.mvc?timed=" + new Date().getTime();
                              var $iframe = $('<iframe id="frame" name="polling" style="display: none;" src="' + url + '"></iframe>');
                              $("body").append($iframe);
                          
                              $iframe.load(function () {
                                  $("#logs").append("[data: " + $($iframe.get(0).contentDocument).find("body").text() + " ]<br/>");
                                  $iframe.remove();
                              });
                          }, 5000);
                          
                      });
                  </script>
              </head>
              
              <body>
                  
                  <div id="logs"></div>
              </body>
          </html>

          這個輪詢方式就是把剛才上面的稍微改下,每個請求都有自己獨立的一個iframe,當這個iframe得到響應的數據后就把數據push到當前頁面上。使用此方法已經類似于ajax的異步交互了,這種方法也是不能保證順序的、比較耗費資源、而且總是有一個加載的條在地址欄或狀態欄附件(當然要解決可以利用htmlfile,Google的攻城師們已經做到了,網上也有封裝好的lib庫),但客戶端實現起來比較簡單。

          image

          如果要保證有序,可以不使用setInterval,將創建iframe的方法放在load事件中即可,即使用遞歸方式。調整后的代碼片段如下:

          <script type="text/javascript">
              $(function () {
                  (function iframePolling() {
                      var url = "${pageContext.request.contextPath}/communication/user/ajax.mvc?timed=" + new Date().getTime();
                      var $iframe = $('<iframe id="frame" name="polling" style="display: none;" src="' + url + '"></iframe>');
                      $("body").append($iframe);
                  
                      $iframe.load(function () {
                          $("#logs").append("[data: " + $($iframe.get(0).contentDocument).find("body").text() + " ]<br/>");
                          $iframe.remove();
                          
                          // 遞歸
                          iframePolling();
                      });
                  })();    
              });
          </script>

          這種方式雖然保證了請求的順序,但是它不會處理請求延時的錯誤或是說很長時間沒有返回結果的請求,它會一直等到返回請求后才能創建下一個iframe請求,總會和服務器保持一個連接。和以上輪詢比較,缺點就是消息不及時,但保證了請求的順序。

          4、ajax實現長連接

          <%@ page language="java" import="java.util.*" pageEncoding="UTF-8" isELIgnored="false" %>
          <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
          <html>
              <head>
                  <meta http-equiv="pragma" content="no-cache">
                  <meta http-equiv="cache-control" content="no-cache">
                  <meta http-equiv="expires" content="0">
                  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
                  <%@ include file="/tags/jquery-lib.jsp"%>
                  
                  <script type="text/javascript">
                      $(function () {
                      
                          (function longPolling() {
                          
                              $.ajax({
                                  url: "${pageContext.request.contextPath}/communication/user/ajax.mvc",
                                  data: {"timed": new Date().getTime()},
                                  dataType: "text",
                                  timeout: 5000,
                                  error: function (XMLHttpRequest, textStatus, errorThrown) {
                                      $("#state").append("[state: " + textStatus + ", error: " + errorThrown + " ]<br/>");
                                      if (textStatus == "timeout") { // 請求超時
                                              longPolling(); // 遞歸調用
                                          
                                          // 其他錯誤,如網絡錯誤等
                                          } else { 
                                              longPolling();
                                          }
                                      },
                                  success: function (data, textStatus) {
                                      $("#state").append("[state: " + textStatus + ", data: { " + data + "} ]<br/>");
                                      
                                      if (textStatus == "success") { // 請求成功
                                          longPolling();
                                      }
                                  }
                              });
                          })();
                          
                      });
                  </script>
              </head>
              
              <body>

          上面這段代碼就是才有Ajax的方式完成長連接,主要優點就是和服務器始終保持一個連接。如果當前連接請求成功后,將更新數據并且繼續創建一個新的連接和服務器保持聯系。如果連接超時或發生異常,這個時候程序也會創建一個新連接繼續請求。這樣就大大節省了服務器和網絡資源,提高了程序的性能,從而也保證了程序的順序。

          image

           

          六、總結

          現代的瀏覽器都支持跨域資源共享(Cross-Origin Resource Share,CORS)規范,該規范允許XHR執行跨域請求,因此基于腳本的和基于iframe的技術已成為了一種過時的需要。

          把Comet做為反向Ajax的實現和使用的最好方式是通過XMLHttpRequest對象,該做法提供了一個真正的連接句柄和錯誤處理。當然你選擇經由HTTP長輪詢使用XMLHttpRequest對象(在服務器端掛起的一個簡單的Ajax請求)的Comet模式,所有支持Ajax的瀏覽器也都支持該種做法。

          基于HTTP的長連接技術,是目前在純瀏覽器環境下進行即時交互類應用開發的理想選擇,隨著瀏覽器的快速發展,html5將為其提供更好的支持和更廣泛的應用。在html5中有一個websocket 可以很友好的完成長連接這一技術,網上也有相關方面的資料,這里也就不再做過多介紹。



          作者:hoojo
          出處:
          blog:http://blog.csdn.net/IBM_hoojo
                   http://hoojo.cnblogs.com
          本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。


          版權所有,轉載請注明出處 本文出自:
          分享道版權所有,歡迎轉載,轉載請注明出處,謝謝

          評論:
          # re: Web 通信 之 長連接、長輪詢(long polling) 2013-10-11 13:34 | tt
          還是websocket好用  回復  更多評論
            
          # re: Web 通信 之 長連接、長輪詢(long polling)[未登錄] 2013-11-05 19:36 | 黃土高坡
          “長連接:在頁面里嵌入一個隱蔵iframe,將這個隱蔵iframe的src屬性設為對一個長連接的請求或是采用xhr請求,服務器端就能源源不斷地往客戶端輸入數據” 這句話中“設為對一個長連接的請求”這里的長連接怎么實現的?是不是就是長輪詢?  回復  更多評論
            
          主站蜘蛛池模板: 德州市| 高州市| 英吉沙县| 贵南县| 河北省| 临江市| 巴彦淖尔市| 孟州市| 建水县| 化德县| 册亨县| 延边| 遂昌县| 东山县| 焦作市| 河池市| 英山县| 民权县| 黄浦区| 新巴尔虎左旗| 昌图县| 大姚县| 高密市| 锦州市| 黄浦区| 蒙山县| 临泉县| 四会市| 成安县| 小金县| 阳原县| 衡阳市| 米易县| 互助| 女性| 平遥县| 武义县| 商南县| 商丘市| 福鼎市| 南通市|