SSE(Server-sent events)技術在web端消息推送和實時聊天中的使用


最近在公司閑着沒事研究了幾天,終於搞定了SSE從理論到實際應用,中間還是有一些坑的。

1.SSE簡介

SSE(Server-sent events)翻譯過來為:服務器發送事件。是基於http協議,和WebSocket的全雙工通道(web端和服務端相互通信)相比,SSE只是單通道(服務端主動推送數據到web端),但正是由於此特性,在不需要客戶端頻繁發送消息給服務端,客戶端卻需要實時或頻繁顯示服務端數據的業務場景中可以使用。如:新郵件提示,在瀏覽網頁時提示有新信息或新博客,監控系統實時顯示數據。。。

在web端消息推送功能中,由於傳統的http協議需要客戶端主動發送請求,服務端才會響應;基本的ajax輪尋技術便是如此,但是此方法需要前端不停的發送ajax請求給后端服務,無論后端是否更新都要執行相應的查詢,無疑會大大增加服務器壓力,浪費不必要的資源。而SSE解決了這種問題,不需前端主動請求,后端如果有更新便會主動推送消息給web端。

在SSE中,瀏覽器發送一個請求給服務端,通過響應頭中的Content-Typetext/event-stream;等 向客戶端證明這是一個長連接,發送的是流數據,這時客戶端不會關閉連接,一直等待服務端發送數據。

關於SSE的前端用法請自行百度或參考一下連接:

http://www.ruanyifeng.com/blog/2017/05/server-sent_events.html

2.python框架flask中SSE的包flask_sse的使用

坑點:剛開始根據,自信的以為在服務器返回數據時只要是response頭部添加這三個字段便實現了SSE功能,但是在flask啟動自帶服務器后,發現瀏覽器總是觸發error事件,並且從新連接。這樣的話和ajax輪詢沒有任何區別。

后來找到flask框架的flask_sse文檔 http://flask-sse.readthedocs.io/en/latest/quickstart.html  其中發現:

Server-sent events do not work with Flask’s built-in development server, because it handles HTTP requests one at a time. The SSE stream is intended to be an infinite stream of events, so it will never complete. If you try to run this code on with the built-in development server, the server will be unable to take any other requests once you connect to this stream. Instead, you must use a web server with asychronous workers. Gunicorn can work with gevent to use asychronous workers: see gunicorn’s design documentation.

  flask內置服務器不適合SSE功能,一次只能處理一個請求。所以只能使用具有異步功能的服務器來完成此項功能。所以本人想在不引入任何包的情況下完成此功能是不可能的了。

在官方給出的flask_sse 文檔中,使用 gunicorn(WSGI協議的一個容器,和uWSGI一樣的功能) + gevent 作為異步功能的服務器。

ubuntu系統中安裝:pip install flask-sse gunicorn gevent

由於官方文檔中給出的實例代碼是MTV(model-template-view)模式,前后端代碼雜糅在一起,看着不舒服,於是改成了restful風格的代碼。

下面給出restful風格的flask_sse實現的實時聊天(消息推送)功能。

 

后端主要文件

sse.py

 1 #coding:utf8
 2 # 將程序轉換成可以使用gevent框架的異步程序
 3 from gevent import monkey
 4 monkey.patch_all()
 5 
 6 from flask import Flask, send_from_directory, redirect, url_for, request, jsonify
 7 from flask_sse import sse
 8 
 9 app = Flask(__name__)
10 #redis路徑
11 app.config["REDIS_URL"] = "redis://localhost"
12 #app注冊sse的藍圖,並且訪問路由是/stream1
13 app.register_blueprint(sse, url_prefix='/stream1')
14 
15 #重定向到發送消息頁面
16 @app.route('/')
17 def index():
18     return redirect(url_for('.index', _external=True) + 'upload/'+'send_messages.html')
19 
20 #接收send_messages.html文件中接口發送的數據,並且通過sse實時推送給用戶
21 @app.route('/messages',methods=['POST'])
22 def send_messages():
23     channel=request.values.get('channel')
24     message=request.values.get('message')
25 
26     #關於channel的使用==> http://flask-sse.readthedocs.io/en/latest/advanced.html
27     #如channel是channel_bob,則只有channel_bob.html才能接收數據
28     #sse推送消息
29     sse.publish({"message": message}, type='social', channel=channel)
30     return jsonify({'code': 200, 'errmsg': 'success', 'data': None})
31 
32 @app.route('/upload/<path:path>')
33 def send_file(path):
34     return send_from_directory('upload/', path)
35 
36 if __name__=='__main__':
37     app.run()

 

前端接收消息文件

channel_bob.html

 1 <!DOCTYPE html>
 2 <html>
 3 <head>
 4     <title>Flask-SSE Quickstart</title>
 5 </head>
 6 <body>
 7 <h1>Channel:channel_bob</h1>
 8 <div id="get_message"></div>
 9 <script src="jquery-3.1.1.js" type="text/javascript" charset="utf-8"></script>
10 <script>
11     $(function () {
12 //只接收channel為channel_bob的消息
13         var source = new EventSource("/stream1?channel=channel_bob");
14         source.addEventListener('social', function (event) {
15             var data = JSON.parse(event.data);
16             $('#get_message').append(data.message+'&nbsp;&nbsp;&nbsp;&nbsp;');
17         }, false);
18         source.addEventListener('error', function (event) {
19             console.log('reconnected service!')
20         }, false);
21     })
22 </script>
23 </body>
24 </html>

 

前端發送消息文件

send_messages.html

 1 <!DOCTYPE html>
 2 <html>
 3 <head>
 4     <meta charset="UTF-8">
 5     <title></title>
 6 </head>
 7 <body>
 8 channel:<input type="text" id="channel" value=""/>
 9 <div>You can choise these channels: channel_bob,channel_tom,channel_public</div>
10 <br/>
11 message:<input type="text" id="message" value=""/>
12 <br/>
13 <button id="button">send message</button>
14 <div id="success"></div>
15 <script src="jquery-3.1.1.js" type="text/javascript" charset="utf-8"></script>
16 <script type="text/javascript">
17     <!--發送消息頁面,發送給三個不同的channel,點擊發送按鈕后,對於的channel頁面會接收到數據-->
18     $(function () {
19         $("#button").click(function () {
20             var channel = $('#channel').val();
21             var message = $('#message').val();
22             var json_data = {
23                 'channel': channel,
24                 'message': message
25             }
26             var http_url = 'http://127.0.0.1:5000/';
27             $.ajax({
28                 url: http_url + "messages",
29                 type: 'post',
30                 dataType: "json",
31                 data: json_data,
32                 success: function (data) {
33                     if (data.code == 200) {
34                         $('#success').text('Send message success!')
35                     }
36                 },
37                 error: function (jqXHR, textStatus, errorThrown) {
38                     console.log(textStatus)
39                     hide_popover('#user_deatil_submit', '程序錯誤,請聯系管理員')
40                 }
41             });
42         });
43     })
44 </script>
45 </body>
46 </html>

 

項目上傳到github上,有詳細注釋。

https://github.com/Rgcsh/sse_chait

 

坑點:

1.uWSGI配置時,在sse_chait.ini配置文件中,socket參數是給在搭建nginx+uWSGI服務時用的,http參數是uWSGI服務(瀏覽器直接訪問網址)時用的

2.在服務啟動時,如果使用uWSGI+gevent啟動服務時,要在sse.py頂部添加

from gevent import monkey
monkey.patch_all()

和sse_chait.ini添加

gevent = 100

3.真正的SSE長連接,是一個連接持續工作,並非http請求一樣,收到回復就斷開連接,如果每次收到響應后,便觸發error事件,說明開發的SSE功能有問題。

真正的SSE連接應該如下,響應時間和請求頭,響應頭如下

 

參考網址:

http://flask-sse.readthedocs.io/en/latest/index.html

https://www.cnblogs.com/ajianbeyourself/p/3970603.html

www.bubuko.com/infodetail-1028284.html

https://www.cnblogs.com/franknihao/p/7202253.html

http://gunicorn.readthedocs.io/en/latest/getstart.html

http://heipark.iteye.com/blog/1847421

www.ruanyifeng.com/blog/2017/05/server-sent_events.html

 

我的博客即將搬運同步至騰訊雲+社區,邀請大家一同入駐:https://cloud.tencent.com/developer/support-plan


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM