最近在完成項目中須要用到實時技術,項目需求是將後端的一個文件內容實時讀取而後發送到前端.這裏主要涉及到兩個技術.一個是後端如何實時讀取一直在更新中的數據,另外一點是如何保證web先後端的通信,能將讀取到的數據實時傳送給前端.html
因爲主要是進行後端開發,前端涉及的少,趁這個機會恰好學習了一下前端的一些知識.
前端
最開始解決實時通信Google後使用了ajax的輪詢技術,若是說從要求上來看基本知足要求,代碼也十分簡單,核心代碼段以下:java
var getting = { url:"{% url '...' %}", type:'GET', dataType: 'text', success: function(data) { $("#output").val(data); //sleep(1000); $.ajax(getting) } } }; $.ajax(getting);
也就是ajax異步請求成功後再次調用本身發送get請求,這樣能夠知足"實時"獲取到後端一直在更新的文件的最新內容.可是,有個很嚴重的問題就是前端一直在get請求,致使極大的佔用帶寬,佔用服務器的處理資源.後來加入修改,將每次發送的請求間隔1s,發現仍然是十分浪費帶寬資源.python
後來發現有ajax長輪詢技術,就是在每次發送請求後,若是後臺有數據,則將數據返回,前端拿到後繼續發送請求,若是後臺沒有數據,就不會response.能夠很好的優化ajax的輪詢缺陷,可是感受該方法仍是不夠好.
web
傳統的request必須有一個response,對於實時通信,這樣是不好勁的體驗.服務器端在創建鏈接後更新到新數據時沒法主動給前端發送response,不夠理想.ajax
後來Google到一個稍微新一點的技術,websocket,剛開始覺得是socket的孿生表弟,後來才發現它倆關係跟java和JavaScript同樣.有一篇文章講的很好,入門瞭解一看便知,websocket基礎
編程
websocket相比較傳統http的優點很明顯,借阮老師的一張圖來看:
後端
一張圖就明白了它的優點有多大.接下來就是開幹api
先看看前端主要部分代碼:
瀏覽器
這樣,你就建立了一個websocket鏈接,瀏覽器是否支持須要你寫個判斷,我用的Chrome,直接支持websocket.其餘具體說明請問度娘或谷大哥.
var socket = new WebSocket("ws://" + window.location.host + "{% url '...' %}"); window.s = socket; window.s.onopen = function () { ... console.log('websocket conneted!') }; window.s.onmessage = function (event) { ... } }; window.s.onclose = function(){ ... }
後臺也須要接受,因爲我用的是Django,後來瞭解到Django channels也能夠實現實時通信,同時也發現了一個dwebsocket第三方庫也是能夠實現實時通信,其實都是用了websocket技術而已...看你我的喜愛,我使用了dwebsocket,由於Django channels安裝的很多,還須要加到INSTALLED_APPS中,我只是須要用到websocket技術而已,dwebsocket相比較之下很是輕便,直接pip install dwebsocket就能夠用,符合人生苦短,我用python的編程思想.其實我是懶
使用上很方便,若是爲一個單獨的視圖函數處理一個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方法則該屬性將一直增加。
好了,其實也就這點內容,接下來就開始邏輯實現了,我在服務器端部分代碼以下:
-
@method_decorator(accept_websocket)
-
def get(self, request):
-
if request.is_websocket():
-
-
for message
in request.websocket:
-
if ...:
-
...
-
else:
-
request.websocket.send(...)
-
-
else:
-
.....
-
return HttpResponse(
'點個贊')
這裏我用到了method_decorator,這個能夠忽略,由於我這個是視通函數,不是的話直接用@accept_websocket就行
注意這裏request.websocket.send發送的內容格式須要爲bytes,而不是str..同時須要判斷請求,用request.is_websocket()來進行判斷.這裏用for message in request.websocket比較好,前端發來的信息都會在裏面,分析源碼後感受比wait()方法要好用,源碼以下:
-
while
True:
-
message = self.wait()
-
yield message
-
if message
is
None:
-
break
會處理多條message.具體後臺的業務邏輯就能夠根據需求添加了
接下來還缺一個需求,就是如何判斷先後端是不是鏈接狀態,不然一方端口另外一方會沒法察覺,而繼續發送,以致於報錯.分析了一下這種長鏈接是哪方進行心跳包發送比較好,理論上來講都應該間隔性主動發送,可是,考慮到服務器端的帶寬資源性問題,以及重要性問題,仍是由客戶端主動按期發送比較合適.同時發現,當客戶端斷開鏈接,刷新退出瀏覽器時,for message in request.websocket中的message會返回None,根據message返回的值來處理邏輯問題.那樣,就剩下客戶端須要加入心跳包了
廢話少說,主要代碼以下:
function keepalive(ws) { var time = new Date(); if ((time.getTime() - last_health > 35000)) { //ws.close(); //clearInterval(window.heartbeat_timer) } else { if (ws.bufferedAmount == 0 && ws.readyState == 1) { ws.send('ping') } } }
time.getTime() - last_health > 35000表示如今時間與上次接受到服務器端數據時間相差大於35s,就ws.close()斷開鏈接,或者重連或者實現其餘業務邏輯均可...不然,判斷ws.bufferedAmount == 0 && ws.readyState == 1,表示沒有拔網線,即無阻塞,且是聯通狀態,發送心跳包,ping,服務器收到後會回覆個pong,隨我的喜愛.在onopen中就須要加入間隔性發送的代碼:
window.s.onopen = function () { window.heartbeat_timer = setInterval(function () {keepalive(window.s)}, 30000);
這裏將心跳包間隔設置爲30s,別忘了在onmessage中每次收到服務器消息都要刷新收到消息的時間.這樣,就實現了客戶端和服務器之間的心跳包重連.
對於如何用python進行文件的實時讀取併發送前端有時間會記錄下來.