Python使用WebSocket和SSE實現(xiàn)HTTP服務器消息推送方式
很多時候我們需要實時獲取最新數(shù)據(jù),但是傳統(tǒng)意義上的HTTP請求,必須由客戶端向服務端發(fā)起請求,服務端再返回相應的數(shù)據(jù)。
那如果我們需要獲取實時數(shù)據(jù),就要通過HTTP輪詢,客戶端不間斷的向服務器發(fā)起請求。
這樣不斷的的請求不但嚴重加大服務器的壓力,還可能因為網(wǎng)絡延遲而影響數(shù)據(jù)的時效性。
下面介紹兩種方法能夠很好的滿足業(yè)務的需求。
一、WebSocket
WebSocket是HTML5開始提供的一種在單個 TCP 鏈接上進行全雙工通信的協(xié)議。
- 優(yōu)點:雙工通信
- 缺點:需專門定義數(shù)據(jù)協(xié)議,解析數(shù)據(jù)流,且部分服務器支持不完善,后臺例如java spring boot 2.1.2 僅支持websocket 1.0(最高已達1.3)
1.客戶端代碼
python 3+ 代碼
#python 3+ import threading import websocket class Client: def __init__(self,url): self.url = url self.ws = None self.enable = True def on_message(self,response): self.enable = False print(response) def on_error(self,error): # print(ws) print(error) def on_close(self): self.enable = True print(ws) def on_open(self): print('open') def start_func(self): while self.enable: websocket.enableTrace(True) self.ws = websocket.WebSocketApp(self.url, on_message=self.on_message, # on_data=on_data, on_error=self.on_error, on_open=self.on_open, on_close=self.on_close, ) self.ws.run_forever(ping_interval=60, ping_timeout=5) if __name__ == "__main__": cli = Client(url = 'wss://api.zb.live/websocket' ) t1 = threading.Thread(target=cli.start_func_zb) t1.start()
javascript 代碼
var ws = new WebSocket("wss://echo.websocket.org"); ws.onopen = function(evt) { console.log("Connection open ..."); ws.send("Hello WebSockets!"); }; ws.onmessage = function(evt) { console.log( "Received Message: " + evt.data); ws.close(); }; ws.onclose = function(evt) { console.log("Connection closed."); };
2.服務端代碼
from websocket_server import WebsocketServer class WSSocketObj: def __init__(self,host=None,port = 8131): self.host = host if host else '127.0.0.1' self.port = port # 當新的客戶端連接時會提示 def new_client(self,client, server,): print("當新的客戶端連接時會提示:%s" % client['id']) dd = 122 server.send_message_to_all("Hey all, a new client has joined us") # 當舊的客戶端離開 def client_left(self,client, server): print("客戶端%s斷開" % client['id']) # 接收客戶端的信息。 def message_received(self,client, server, message): print("Client(%d) said: %s" % (client['id'], message)) # server.send_message_to_all(message) #發(fā)送消息給 全部客戶端 server.send_message(client, 'hello,client') # 發(fā)送消息給指定客戶端 def run_server(self): server = WebsocketServer(self.port, self.host) server.set_fn_new_client(self.new_client) server.set_fn_client_left(self.client_left) server.set_fn_message_received(self.message_received) server.run_forever() if __name__ == '__main__': WSSocketObj().run_server()
二、SSE(Server-Sent Events,服務器發(fā)送事件)
SSE ( Server-sent Events )通俗解釋起來就是一種基于HTTP的,以流的形式由服務端持續(xù)向客戶端發(fā)送數(shù)據(jù)的技術,是 WebSocket 的一種輕量代替方案。
- 優(yōu)點:開發(fā)簡單,和傳統(tǒng)的http開發(fā)幾乎無任何差別,客戶端開發(fā)簡單,有標準支持(EventSource)
- 缺點:和websocket相比,只能單工通信,建立連接后,只能由服務端發(fā)往客戶端,且占用一個連接,如需客戶端向服務端通信,需額外打開一個連接
1.客戶端代碼
python
# 第一種方式 def sse_client(): """ pip install sseclient-py 只對于返回標準SSE格式的請求可用 格式:event: {EVENT}\nretry: 10000\ndata: {DATA}\n\n :return: """ import requests # res = requests.request('get', url, json=data, stream=True, headers={'Accept': 'text/event-stream'}) client = requests.post(url, json=data, stream=True, headers={'Accept': 'text/event-stream'}) client = sseclient.SSEClient(client) for i in client.events(): print(i.data) # 第二種方式 def sse_with_requests(): headers = {"Accept": "text/event-stream"} r = requests.post(url, headers=headers, json=data, stream=True) r.encoding = 'utf-8' for chunk in r.iter_content(chunk_size=None, decode_unicode=True): # 處理接收到的數(shù)據(jù)塊 print("Received:", chunk)
javascript
第一種方式:
//判斷是否支持SSE if('EventSource' in window){ //初始化SSE var url="http:localhost:8000/stream"; var source=new EventSource(url); // 連接成功后會觸發(fā) open 事件 source.onopen=(event)=>{ console.log("開啟SSE"); } // 服務器發(fā)送信息到客戶端時,如果沒有 event 字段,默認會觸發(fā) message 事件 source.onmessage=(event)=>{ var data=event.data; $("body").append($("<p>").text(data)); } //監(jiān)聽like事件 source.addEventListener('like',function(event){ var data=event.data; $("body").append($("<p>").text(data)); },false); // 連接異常時會觸發(fā) error 事件并自動重連 source.onerror=(event)=>{ console.log(event); }
第二種方式:使用 addEventListener 方法來添加相應的事件處理方法
if (window.EventSource) { // 創(chuàng)建 EventSource 對象連接服務器 const source = new EventSource('http://localhost:2000'); // 連接成功后會觸發(fā) open 事件 source.addEventListener('open', () => { console.log('Connected'); }, false); // 服務器發(fā)送信息到客戶端時,如果沒有 event 字段,默認會觸發(fā) message 事件 source.addEventListener('message', e => { console.log(`data: ${e.data}`); }, false); // 自定義 EventHandler,在收到 event 字段為 slide 的消息時觸發(fā) source.addEventListener('slide', e => { console.log(`data: ${e.data}`); // => data: 7 }, false); // 連接異常時會觸發(fā) error 事件并自動重連 source.addEventListener('error', e => { if (e.target.readyState === EventSource.CLOSED) { console.log('Disconnected'); } else if (e.target.readyState === EventSource.CONNECTING) { console.log('Connecting...'); } }, false); } else { console.error('Your browser doesn\'t support SSE'); }
EventSource從父接口 EventTarget
中繼承了屬性和方法,其內置了 3 個 EventHandler
屬性、2 個只讀屬性和 1 個方法:
EventHandler 屬性
EventSource.onopen
在連接打開時被調用。EventSource.onmessage
在收到一個沒有 event 屬性的消息時被調用。EventSource.onerror
在連接異常時被調用。 只讀屬性EventSource.readyState
一個 unsigned short 值,代表連接狀態(tài)??赡苤凳?CONNECTING (0), OPEN (1), 或者 CLOSED (2)。EventSource.url
連接的 URL。 方法EventSource.close()
關閉連接
EventSource 對象的 onmessage
屬性的作用類似于 addEventListener( ‘ message ’ )
2.服務端代碼(基于Flask)
import json import time from flask import Flask, request from flask import Response from flask import render_template app = Flask(__name__) def get_message(): """this could be any function that blocks until data is ready""" time.sleep(1) s = time.ctime(time.time()) return json.dumps(['當前時間:' + s , 'a'], ensure_ascii=False) @app.route('/') def hello_world(): return render_template('index.html') @app.route('/stream') def stream(): user_id = request.args.get('user_id') print(user_id) def eventStream(): id = 0 while True: id +=1 # wait for source data to be available, then push it yield 'id: {}\nevent: add\ndata: {}\n\n'.format(id,get_message()) return Response(eventStream(), mimetype="text/event-stream") if __name__ == '__main__': app.run()
因為SSE是http請求,可是又限定是一個長鏈接,因此要設置MIME類型為text/event-stream。返回的為字符串。
消息的格式
服務器向瀏覽器發(fā)送的 SSE 數(shù)據(jù),必須是 UTF-8 編碼的文本;
每一次發(fā)送的信息,由若干個message組成,每一個message之間用\n\n分隔。每一個message內部由若干行組成
- 格式
[field]:value\n
其中在規(guī)范中為消息定義了 4 個字段
- id 表明id
- event 表明消息的事件類型
- data 消息的數(shù)據(jù)字段
- retry 客戶端重連的時間。只接受整數(shù),單位是毫秒。如果這個值不是整數(shù)則會被自動忽略
需要注意的是,id字段不是必須的,服務器有可能不會在消息中帶上 id 字段,這樣子客戶端就不會存在 Last-Event-ID這個屬性。所以為了保證數(shù)據(jù)可靠,我們需要在每條消息上帶上 id 字段。
一個很有意思的地方是,規(guī)范中規(guī)定以冒號開頭的消息都會被當作注釋,一條普通的注釋(:\n\n
)對于服務器來說只占 5個字符,但是發(fā)送到客戶端上的時候不會觸發(fā)任何事件,這對客戶端來說是非常友好的。所以注釋一般被用于維持服務器和客戶端的長連接。
3.SSE使用注意事項
1、SSE 如何保證數(shù)據(jù)完整性
客戶端在每次接收到消息時,會把消息的 id 字段作為內部屬性 Last-Event-ID 儲存起來。
SSE 默認支持斷線重連機制,在連接斷開時會 觸發(fā) EventSource 的 error 事件,同時自動重連。再次連接成功時EventSource 會把 Last-Event-ID 屬性作為請求頭發(fā)送給服務器,這樣服務器就可以根據(jù)這個 Last-Event-ID作出相應的處理。
這里需要注意的是,id 字段不是必須的,服務器有可能不會在消息中帶上 id 字段,這樣子客戶端就不會存在 Last-Event-ID這個屬性。所以為了保證數(shù)據(jù)可靠,我們需要在每條消息上帶上 id 字段。
2、減少開銷
在 SSE 的草案中提到,“text/event-stream” 的 MIME 類型傳輸應當在靜置 15秒后自動斷開。在實際的項目中也會有這個機制,但是斷開的時間沒有被列入標準中。
為了減少服務器的開銷,我們也可以有目的的斷開和重連。
簡單的辦法是服務器發(fā)送一個 關閉消息并指定一個重連的時間戳,客戶端在觸發(fā)關閉事件時關閉當前連接并創(chuàng)建 一個計時器,在重連時把計時器銷毀。
function connectSSE() { if (window.EventSource) { const source = new EventSource('http://localhost:2000'); let reconnectTimeout; source.addEventListener('open', () => { console.log('Connected'); clearTimeout(reconnectTimeout); }, false); source.addEventListener('pause', e => { source.close(); const reconnectTime = +e.data; const currentTime = +new Date(); reconnectTimeout = setTimeout(() => { connectSSE(); }, reconnectTime - currentTime); }, false); } else { console.error('Your browser doesn\'t support SSE'); } } connectSSE();
總結
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
Python3數(shù)據(jù)庫操作包pymysql的操作方法
這篇文章主要介紹了Python3數(shù)據(jù)庫操作包pymysql的操作方法,文章通過實例代碼相結合給大家介紹的非常詳細,需要的朋友可以參考下2018-07-07Python使用os.listdir()和os.walk()獲取文件路徑與文件下所有目錄的方法
今天小編就為大家分享一篇關于Python使用os.listdir()和os.walk()獲取文件路徑與文件下所有目錄的方法,小編覺得內容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-04-04windows下pycharm搭建spark環(huán)境并成功運行 附源碼
這篇文章主要介紹了windows下pycharm搭建spark環(huán)境并成功運行 附源碼,本文分步驟給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-04-04使用python BeautifulSoup庫抓取58手機維修信息
這篇文章主要介紹了一個使用python抓取58手機的精準商家信息,使用BeautifulSoup API的方法2013-11-11