日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

客户端与服务器持续同步解析(轮询,comet,WebSocket)

發布時間:2025/3/21 编程问答 19 豆豆
生活随笔 收集整理的這篇文章主要介紹了 客户端与服务器持续同步解析(轮询,comet,WebSocket) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

作者:盼逆邵年??來源:博客園??發布時間:2012-02-10 20:42??閱讀:1943 次??原文鏈接???[收藏]??

在B/S模型的Web應用中,客戶端常常需要保持和服務器的持續更新。這種對及時性要求比較高的應用比如:股票價格的查詢,實時的商品價格,自動更新的twitter timeline以及基于瀏覽器的聊天系統(如GTalk)等等。由于近些年AJAX技術的興起,也出現了多種實現方式。本文將對這幾種方式進行說明,并用jQuery+tornado進行演示,需要說明的是,如果對tornado不了解也沒有任何問題,由于tornado的代碼非常清晰且易懂,選擇tornado是因為其是一個非阻塞的(Non-blocking IO)異步框架(本文使用2.0版本)。

?

在開始之前,為了讓大家有個清晰的認識,首先列出本文所要講到的內容大概。本文將會分以下幾部分:

  • 普通的輪詢(Polling)
  • Comet:基于服務器長連接的“服務器推”技術。這其中又分為兩種:
  • 基于AJAX和基于IFrame的流(streaming)方式
  • 基于AJAX的長輪詢(long-polling)方式
  • WebSocket
  • ?

    ?

    古老的輪詢

    輪詢最簡單也最容易實現,每隔一段時間向服務器發送查詢,有更新再觸發相關事件。對于前端,使用js的setInterval以AJAX或者JSONP的方式定期向服務器發送request。

    var polling = function(){
    $.post('/polling', function(data, textStatus){
    $("p").append(data+"<br>");
    });
    };
    interval = setInterval(polling, 1000);

    ?

    后端我們只是象征性地隨機生成一些數字,并且返回。在實際應用中可以訪問cache或者從數據庫中獲取內容。

    import random
    import tornado.web

    class PollingHandler(tornado.web.RequestHandler):
    def post(self):
    num = random.randint(1, 100)
    self.write(str(num))

    ?

    可以看到,采用polling的方式,效率是十分低下的,一方面,服務器端不是總有數據更新,所以每次問詢不一定都有更新,效率低下;另一方面,當發起請求的客戶端數量增加,服務器端的接受的請求數量會大量上升,無形中就增加了服務器的壓力。


    Comet:基于HTTP長連接的“服務器推”技術

    看到 這個標題有的人可能就暈了,其實原理還是比較簡單的。基于Comet的技術主要分為流(streaming)方式和長輪詢(long-polling)方式。
    ?

    首先看Comet這個單詞,很多地方都會說到,它是“彗星”的意思,顧名思義,彗星有個長長的尾巴,以此來說明客戶端發起的請求是長連的。即用戶發起請求后就掛起,等待服務器返回數據,在此期間不會斷開連接。流方式和長輪詢方式的區別就是:對于流方式,客戶端發起連接就不會斷開連接,而是由服務器端進行控制。當服務器端有更新時,刷新數據,客戶端進行更新;而對于長輪詢,當服務器端有更新返回,客戶端先斷開連接,進行處理,然后重新發起連接。
    ?

    會有同學問,為什么需要流(streaming)和長輪詢(long-polling)兩種方式呢?是因為:對于流方式,有諸多限制。如果使用AJAX方式,需要判斷XMLHttpRequest 的 readystate,即readystate==3時(數據仍在傳輸),客戶端可以讀取數據,而不用關閉連接。問題也在這里,IE 在 readystate 為 3 時,不能讀取服務器返回的數據,所以目前 IE 不支持基于 Streaming AJAX,而長輪詢由于是普通的AJAX請求,所以沒有瀏覽器兼容問題。另外,由于使用streaming方式,控制權在服務器端,并且在長連接期間,并沒有客戶端到服務器端的數據,所以不能根據客戶端的數據進行即時的適應(比如檢查cookie等等),而對于long polling方式,在每次斷開連接之后可以進行判斷。所以綜合來說,long polling是現在比較主流的做法(如fb,Plurk)。

    ?

    接下來,我們就來對流(streaming)和長輪詢(long-polling)兩種方式進行演示。

    ?

    流(streaming)方式

    從上圖可以看出每次數據傳送不會關閉連接,連接只會在通信出現錯誤時,或是連接重建時關閉(一些防火墻常被設置為丟棄過長的連接, 服務器端可以設置一個超時時間, 超時后通知客戶端重新建立連接,并關閉原來的連接)。

    流方式首先一種常用的做法是使用AJAX的流方式(如先前所說,此方法主要判斷readystate==3時的情況,所以不能適用于IE)。
    ?

    服務器端代碼像這樣:

    class StreamingHandler(tornado.web.RequestHandler):
    '''使用asynchronus裝飾器使得post方法變成無阻塞'''
    @tornado.web.asynchronous
    def post(self):
    self.get_data(callback=self.on_finish)

    def get_data(self, callback):
    if self.request.connection.stream.closed():
    return

    num = random.randint(1, 100) #生成隨機數
    callback(num) #調用回調函數

    def on_finish(self, data):
    self.write("Server says: %d" % data)
    self.flush()

    tornado.ioloop.IOLoop.instance().add_timeout(
    time.time()+3,
    lambda: self.get_data(callback=self.on_finish)
    )

    對于服務器端,仍然是生成隨機數字,由于要不斷輸出數據,于是在回調函數里延遲3秒,然后繼續調用get_data方法。在這里要注意的是,不能使用time.sleep(),由于tornado是單線程的,使用sleep方法會block主線程。因此要調用IOLoop的add_timeout方法(參數0:執行時間戳,參數1:回調函數)。于是服務器端會生成一個隨機數字,延遲3秒再生成隨機數字,循環往復。
    ?

    于是前端js就是:

    try {
    var request = new XMLHttpRequest();
    } catch (e) {
    alert("Browser doesn't support window.XMLHttpRequest");
    }

    var pos = 0;
    request.onreadystatechange = function () {
    if (request.readyState === 3) { //在 Interactive 模式處理
    var data = request.responseText;
    $("p").append(data.substring(pos)+"<br>");
    pos = data.length;
    }
    };
    request.open("POST", "/streaming", true);
    request.send(null);


    對于tornado來說,調用flush方法,會將先前write的所有數據都發送客戶端,也就是response的數據處于累加的狀態,所以在js腳本里,我們使用了pos變量作為cursor來存放每次flush數據結束位置。

    ?

    另外一種常用方法是使用IFrame的streaming方式,這也是早先的常用做法。首先我們在頁面里放置一個iframe,它的src設置為一個長連接的請求地址。Server端的代碼基本一致,只是輸出的格式改為HTML,用來輸出一行行的Inline Javascript。由于輸出就得到執行,因此就少了存儲游標(pos)的過程。服務器端代碼像這樣:

    class IframeStreamingHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    def get(self):
    self.get_data(callback=self.on_finish)

    def get_data(self, callback):
    if self.request.connection.stream.closed():
    return

    num = random.randint(1, 100)
    callback(num)

    def on_finish(self, data):
    self.write("<script>parent.add_content('Server says: %d<br />');</script>" % data)
    # 輸出的立刻執行,調用父窗口js函數add_content
    self.flush()

    tornado.ioloop.IOLoop.instance().add_timeout(
    time.time()+3,
    lambda: self.get_data(callback=self.on_finish)
    )


    在客戶端我們只需定義add_content函數:

    var add_content = function(str){
    $("p").append(str);
    };


    由此可以看出,采用IFrame的streaming方式解決了瀏覽器兼容問題。但是由于傳統的Web服務器每次連接都會占用一個連接線程,這樣隨著增加的客戶端長連接到服務器時,線程池里的線程最終也就會用光。因此,Comet長連接只有對于非阻塞異步Web服務器才會產生作用。這也是為什么選擇tornado的原因。

    使用iframe方式一個問題就是瀏覽器會一直處于加載狀態。


    長輪詢(long-polling)方式


    長輪詢是現在最為常用的方式,和流方式的區別就是服務器端在接到請求后掛起,有更新時返回連接即斷掉,然后客戶端再發起新的連接。于是Server端代碼就簡單好多,和上面的任務類似:

    class LongPollingHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    def post(self):
    self.get_data(callback=self.on_finish)

    def get_data(self, callback):
    if self.request.connection.stream.closed():
    return

    num = random.randint(1, 100)
    tornado.ioloop.IOLoop.instance().add_timeout(
    time.time()+3,
    lambda: callback(num)
    ) # 間隔3秒調用回調函數

    def on_finish(self, data):
    self.write("Server says: %d" % data)
    self.finish() # 使用finish方法斷開連接


    Browser方面,我們封裝成一個updater對象:

    var updater = {
    poll: function(){
    $.ajax({url: "/longpolling",
    type: "POST",
    dataType: "text",
    success: updater.onSuccess,
    error: updater.onError});
    },
    onSuccess: function(data, dataStatus){
    try{
    $("p").append(data+"<br>");
    }
    catch(e){
    updater.onError();
    return;
    }
    interval = window.setTimeout(updater.poll, 0);
    },
    onError: function(){
    console.log("Poll error;");
    }
    };


    要啟動長輪詢只要調用

    updater.poll();


    可以看到,長輪詢與普通的輪詢相比更有效率(只有數據更新時才返回數據),減少不必要的帶寬的浪費;同時,長輪詢又改進了streaming方式對于browser端判斷并更新不足的問題。

    ?

    WebSocket:未來方向

    以上不管是Comet的何種方式,其實都只是單向通信,直到WebSocket的出現,才是B/S之間真正的全雙工通信。不過目前WebSocket協議仍在開發中,目前Chrome和Safri瀏覽器默認支持WebSocket,而FF4和Opera出于安全考慮,默認關閉了WebSocket,IE則不支持(包括9),目前WebSocket協議最新的為“76號草案”。有興趣可以關注http://dev.w3.org/html5/websockets/。
    ?

    在每次WebSocket發起后,B/S端進行握手,然后就可以實現通信,和socket通信原理是一樣的。目前,tornado2.0版本也是實現了websocket的“76號草案”。詳細可以參閱文檔。我們還是只是在通信打開之后發送一堆隨機數字,僅演示之用。

    import tornado.websocket

    class WebSocketHandler(tornado.websocket.WebSocketHandler):
    def open(self):
    for i in xrange(10):
    num = random.randint(1, 100)
    self.write_message(str(num))

    def on_message(self, message):
    logging.info("getting message %s", message)
    self.write_message("You say:" + message)

    客戶端代碼也很簡單和直觀:

    var wsUpdater = {
    socket: null,
    start: function(){
    if ("WebSocket" in window) {
    wsUpdater.socket = new WebSocket("ws://localhost:8889/websocket");
    }
    else {
    wsUpdater.socket = new MozWebSocket("ws://localhost:8889/websocket");
    }
    wsUpdater.socket.onmessage = function(event) {
    $("p").append(event.data+"<br>");
    };
    }
    };
    wsUpdater.start();

    ?

    總結:本文對Browser和Server端持續同步的方式進行了介紹,并進行了演示。在實際生產中,有一些框架。包括Java的Pushlet,NodeJS的socket.io,大家請自行查閱資料。

    本文參考文章:

  • Browser 與 Server 持續同步的作法介紹 (Polling, Comet, Long Polling, WebSocket)?(可能要翻墻)
  • Comet:基于 HTTP 長連接的“服務器推”技術?
  • 總結

    以上是生活随笔為你收集整理的客户端与服务器持续同步解析(轮询,comet,WebSocket)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。