websocket知識彙總以及websocket在Django中的實現(轉載)

        最近在完成項目中須要用到實時技術,項目需求是將後端的一個文件內容實時讀取而後發送到前端.這裏主要涉及到兩個技術.一個是後端如何實時讀取一直在更新中的數據,另外一點是如何保證web先後端的通信,能將讀取到的數據實時傳送給前端.html

        因爲主要是進行後端開發,前端涉及的少,趁這個機會恰好學習了一下前端的一些知識.
前端

一.Ajax輪詢

        最開始解決實時通信Google後使用了ajax的輪詢技術,若是說從要求上來看基本知足要求,代碼也十分簡單,核心代碼段以下:java

  
  
  
  
  1. var getting = {
  2. url:"{% url '...' %}",
  3. type:'GET',
  4. dataType: 'text',
  5. success: function(data) {
  6. $("#output").val(data);
  7.                         //sleep(1000);
  8. $.ajax(getting)
  9. }
  10. }
  11. };
  12. $.ajax(getting);

        也就是ajax異步請求成功後再次調用本身發送get請求,這樣能夠知足"實時"獲取到後端一直在更新的文件的最新內容.可是,有個很嚴重的問題就是前端一直在get請求,致使極大的佔用帶寬,佔用服務器的處理資源.後來加入修改,將每次發送的請求間隔1s,發現仍然是十分浪費帶寬資源.python

        後來發現有ajax長輪詢技術,就是在每次發送請求後,若是後臺有數據,則將數據返回,前端拿到後繼續發送請求,若是後臺沒有數據,就不會response.能夠很好的優化ajax的輪詢缺陷,可是感受該方法仍是不夠好.
web

        傳統的request必須有一個response,對於實時通信,這樣是不好勁的體驗.服務器端在創建鏈接後更新到新數據時沒法主動給前端發送response,不夠理想.ajax

二.棄用ajax輪詢,選擇websocket

        後來Google到一個稍微新一點的技術,websocket,剛開始覺得是socket的孿生表弟,後來才發現它倆關係跟java和JavaScript同樣.有一篇文章講的很好,入門瞭解一看便知,websocket基礎
編程

        websocket相比較傳統http的優點很明顯,借阮老師的一張圖來看:
後端

                          

一張圖就明白了它的優點有多大.接下來就是開幹api

        先看看前端主要部分代碼:
瀏覽器

  
  
  
  
  1. var socket = new WebSocket("ws://" + window.location.host + "{% url '...' %}");
  2. window.s = socket;
  3. window.s.onopen = function () {
  4.                 ...
  5. console.log('websocket conneted!')
  6. };
  7. window.s.onmessage = function (event) {
  8.                     ...
  9. }
  10. };
  11.             window.s.onclose = function(){
  12.                 ...
  13.             }
        這樣,你就建立了一個websocket鏈接,瀏覽器是否支持須要你寫個判斷,我用的Chrome,直接支持websocket.其餘具體說明請問度娘或谷大哥.

        後臺也須要接受,因爲我用的是Django,後來瞭解到Django channels也能夠實現實時通信,同時也發現了一個dwebsocket第三方庫也是能夠實現實時通信,其實都是用了websocket技術而已...看你我的喜愛,我使用了dwebsocket,由於Django channels安裝的很多,還須要加到INSTALLED_APPS中,我只是須要用到websocket技術而已,dwebsocket相比較之下很是輕便,直接pip install dwebsocket就能夠用,符合人生苦短,我用python的編程思想.其實我是懶

三.使用dwebsocket     

    一.使用方法   

        使用上很方便,若是爲一個單獨的視圖函數處理一個websocklet鏈接可使用accept_websocket裝飾器,它會將標準的HTTP請求路由到視圖中。使用require_websocke裝飾器只容許使用WebSocket鏈接,會拒絕正常的HTTP請求。
        在設置中添加設置MIDDLEWARE_CLASSES=dwebsocket.middleware.WebSocketMiddleware這樣會拒絕單獨的視圖實用websocket,必須加上accept_websocket 裝飾器。設置WEBSOCKET_ACCEPT_ALL=True能夠容許每個單獨的視圖實用websockets.....固然,在settings中這樣作徹底不必,由於我就是一個視圖函數來處理請求的,無需複雜化.直接在視圖函數處引入提供的裝飾器accept_websocket便可.

    二.一些屬性和方法

        1.request.is_websocket()
        若是是個websocket請求返回True,若是是個普通的http請求返回False,能夠用這個方法區分它們。

        2.request.websocket
        在一個websocket請求創建以後,這個請求將會有一個websocket屬性,用來給客戶端提供一個簡單的api通信,若是request.is_websocket()是False,這個屬性將是None。

        3.WebSocket.wait()
        返回一個客戶端發送的信息,在客戶端關閉鏈接以前他不會返回任何值,這種狀況下,方法將返回None

        4.WebSocket.read()
         若是沒有從客戶端接收到新的消息,read方法會返回一個新的消息,若是沒有,就不返回。這是一個替代wait的非阻塞方法

        5.WebSocket.count_messages()
         返回消息隊列數量

        6.WebSocket.has_messages()
         若是有新消息返回True,不然返回False

        7.WebSocket.send(message)
         向客戶端發送消息

        8.WebSocket.__iter__()
         websocket迭代器

必需要學習瞭解才知道咱們須要用到什麼.固然了,客戶端的也須要了解一下:

                                     

        這是客戶端的一些說明,在客戶端,websocket的兩個屬性:readyState和bufferedAmount,區別和說明以下:

                根據readyState屬性能夠判斷webSocket的鏈接狀態,該屬性的值能夠是下面幾種:
                0 :對應常量CONNECTING (numeric value 0),
                 正在創建鏈接鏈接,尚未完成。The connection has not yet been established.
                 1 :對應常量OPEN (numeric value 1),
                 鏈接成功創建,能夠進行通訊。The WebSocket connection is established and communication is possible.
                2 :對應常量CLOSING (numeric value 2)
                 鏈接正在進行關閉握手,即將關閉。The connection is going through the closing handshake.
                3 : 對應常量CLOSED (numeric value 3)
                 鏈接已經關閉或者根本沒有創建。The connection has been closed or could not be opened.

            根據bufferedAmount能夠知道有多少字節的數據等待發送,若websocket已經調用了close方法則該屬性將一直增加。

        好了,其實也就這點內容,接下來就開始邏輯實現了,我在服務器端部分代碼以下:

  
  
  
  
  1. @method_decorator(accept_websocket)
  2. def get(self, request):
  3. if request.is_websocket():
  4. for message in request.websocket:
  5. if ...:
  6.                     ...
  7. else:
  8. request.websocket.send(...)
  9. else:
  10.             .....
  11. return HttpResponse( '點個贊')

        這裏我用到了method_decorator,這個能夠忽略,由於我這個是視通函數,不是的話直接用@accept_websocket就行

        注意這裏request.websocket.send發送的內容格式須要爲bytes,而不是str..同時須要判斷請求,用request.is_websocket()來進行判斷.這裏用for message in request.websocket比較好,前端發來的信息都會在裏面,分析源碼後感受比wait()方法要好用,源碼以下:

  
  
  
  
  1. while True:
  2. message = self.wait()
  3. yield message
  4. if message is None:
  5. break
        會處理多條message.具體後臺的業務邏輯就能夠根據需求添加了

四.websocket心跳包機制

        接下來還缺一個需求,就是如何判斷先後端是不是鏈接狀態,不然一方端口另外一方會沒法察覺,而繼續發送,以致於報錯.分析了一下這種長鏈接是哪方進行心跳包發送比較好,理論上來講都應該間隔性主動發送,可是,考慮到服務器端的帶寬資源性問題,以及重要性問題,仍是由客戶端主動按期發送比較合適.同時發現,當客戶端斷開鏈接,刷新退出瀏覽器時,for message in request.websocket中的message會返回None,根據message返回的值來處理邏輯問題.那樣,就剩下客戶端須要加入心跳包了

        廢話少說,主要代碼以下:

  
  
  
  
  1. function keepalive(ws) {
  2. var time = new Date();
  3. if ((time.getTime() - last_health > 35000)) {
  4. //ws.close();
  5. //clearInterval(window.heartbeat_timer)
  6. } else {
  7. if (ws.bufferedAmount == 0 && ws.readyState == 1) {
  8. ws.send('ping')
  9. }
  10. }
  11. }

        time.getTime() - last_health > 35000表示如今時間與上次接受到服務器端數據時間相差大於35s,就ws.close()斷開鏈接,或者重連或者實現其餘業務邏輯均可...不然,判斷ws.bufferedAmount == 0 && ws.readyState == 1,表示沒有拔網線,即無阻塞,且是聯通狀態,發送心跳包,ping,服務器收到後會回覆個pong,隨我的喜愛.在onopen中就須要加入間隔性發送的代碼:

  
  
  
  
  1. window.s.onopen = function () {
  2. window.heartbeat_timer = setInterval(function () {keepalive(window.s)}, 30000);

        這裏將心跳包間隔設置爲30s,別忘了在onmessage中每次收到服務器消息都要刷新收到消息的時間.這樣,就實現了客戶端和服務器之間的心跳包重連.

五.後記

        對於如何用python進行文件的實時讀取併發送前端有時間會記錄下來.

        



















原文連接

相關文章
相關標籤/搜索