使用flask_socketio實現服務端向客戶端定時推送


  websocket連接是客戶端與服務器之間永久的雙向通信通道,直到某方斷開連接。

  雙向通道意味着在連接時,服務端隨時可以發送消息給客戶端,反之亦然,這在一些需要即時通訊的場景比如多人聊天室非常重要。

  flask_socketio實現了對websocket的封裝,它可以讓運行flask應用的服務端和客戶端建立全雙工通道。

  flask_socketio是一個python庫,是flask框架的擴展。

一、安裝

pip install flask-socketio

 

二、實現對flask的封裝

from flask import Flask, render_template
from flask_socketio import SocketIO,emit
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app)

if __name__ == '__main__':
    socketio.run(app, debug=True)

  socketio.run()函數封裝了flask的web服務器的啟動

 

三、服務端向客戶端推送

  socketio的兩個函數send()和emit()都可以實現消息發送,前者用於無名事件,后者用於命名的事件。

  事件是消息的名稱。如果把消息比做信件,事件就是貼在信封上的標識,這個標識規定了信件送往客戶端或服務端的某個函數。

from flask import Flask, render_template
from flask_socketio import SocketIO,emit
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app)

@socketio.on('connect', namespace='/test_conn')
def test_connect():
        socketio.emit('server_response',
                      {'data': ‘connected’},namespace='/test_conn')

if __name__ == '__main__':
    socketio.run(app, debug=True)

  比如上面socketio.on('connect',namespace='/test_conn')中的connect就是soketio的內置事件,當客戶端與服務端連接之后,前端和后端都會收到一個名為‘connect’的事件,服務端接到這個事件就會執行test_connect函數中的內容了。

  再說namespace,namespace可以標志多個事件,在官方文檔的解釋是“Namespaces allow a client to open multiple connections to the server that are multiplexed on a single socket.”。當一個客戶端連接服務器的不同命名域的時候,可以在同一個socket連接里完成。我的理解是一個namespace就定義了一個后端websocket連接的接口,客戶端與服務器通過三次握手建立socket連接后,連接不同的服務器接口,socket的連接並不會斷開。這可以類比於http的路由(但是完全不同哦,因為傳輸協議完全不一樣),在http連接范疇,當用戶登錄后,訪問服務器不同的路由並不會改變它的登錄狀態。一個后端接口可以接受多個客戶端的socket連接,如果在后端的emit中定義‘broadcast=True’,那么所有連接到這個命名域的客戶端都會收到這個消息,命名域之間也可以通過發送消息指定命名域的方式來相互通信。

  再看soketio.emit,第一個參數'server_response'是服務端發送這個消息的事件名,在客戶端要建立一個接受這個事件的函數處理,后面的字典就是消息內容,namespace='/test_conn'表示這個消息還是發送到同一個信道(test_conn)中。emit發送信息只能從前端發到后端或者從后端發向前端,如果在在前端emit(‘event’,{data})再寫socket.on('event', {data})是收不到的。

 

四、定時推送

  實驗的目的是服務端定時發送一個隨機數到客戶端,並且客戶端可以及時顯示。

  一開始,我在socketio裝飾的函數中寫了一個while循環

from flask import Flask, render_template from flask_socketio import SocketIO,emit import random async_mode = None app = Flask(__name__) app.config['SECRET_KEY'] = 'secret!' socketio = SocketIO(app) @app.route('/') def index(): return render_template('index.html') @socketio.on('connect', namespace='/test_conn') def test_connect(): while True: socketio.sleep(5) t = random.randint(1, 100) socketio.emit('server_response', {'data': t},namespace='/test_conn') if __name__ == '__main__': socketio.run(app, debug=True)

  事實證明這樣是行不通的,雖然看上去,雖然服務端陷入while的死循環中,但是emit函數每次都會執行,所以理論上客戶端應該可以定時收到服務端的隨機數。但是結果是客戶端根本接收不到,連soketio.on函數都沒有觸發運行。

  原因應該是當服務端陷入死循環,會影響與客戶端之間的websocket連接,總之寫while true需謹慎

  在flask_socketio的示例程序中,我找到了用后台線程進行while循環以解決這個問題的方法。

from flask import Flask, render_template from flask_socketio import SocketIO,emit from threading import Lock import random async_mode = None app = Flask(__name__) app.config['SECRET_KEY'] = 'secret!' socketio = SocketIO(app) thread = None thread_lock = Lock() @app.route('/') def index(): return render_template('index.html') @socketio.on('connect', namespace='/test_conn') def test_connect(): global thread with thread_lock: if thread is None: thread = socketio.start_background_task(target=background_thread) def background_thread(): while True: socketio.sleep(5) t = random.randint(1, 100) socketio.emit('server_response', {'data': t},namespace='/test_conn') if __name__ == '__main__': socketio.run(app, debug=True)

 

五、客戶端

  index.html的內容如下

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title></title>
    <script type="text/javascript" src="//code.jquery.com/jquery-1.4.2.min.js"></script>
    <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/socket.io/1.3.5/socket.io.min.js"></script>
</head>
<body>
<h1 id="t"></h1>
<script type="text/javascript"> $(document).ready(function() { namespace = '/test_conn'; var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port + namespace, {transports: ['websocket']}); socket.on('server_response', function(res) { console.log(res.data); $('#t').text(res.data); }); }); </script>
</body>
</html>

  注意客戶端也要導入socketio的庫,然后用io.connect建立命名域的socket連接。

  如果不加{transports: ['websocket']},實際上建立的是長輪詢。長輪詢或websocket都是由客戶端發起的

  最后在瀏覽器輸入http://127.0.0.1:5000就可以了

 

六、結果

 

  開發者工具的console可以查看日志

 


免責聲明!

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



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