1 2 | 特別說明,下面解決方法是在按網上最容易查到的一個範例參考使用時,遇到了問題,反覆使用仍沒有解決情況下,找到了下面文章,發現是後台的while true問題,特別編輯理髮一下,同時感謝原作者,引用地址如下,避免大家遇到同樣問題。 [https://www.cnblogs.com/luozx207/p/9714487.html](https://www.cnblogs.com/luozx207/p/9714487.html) |
特別請大家注意,版本相容問題:

websocket連接是客戶端與伺服器之間永久的雙向通訊通道,直到某方斷開連接。
雙向通道意味著在連接時,伺服端隨時可以發送訊息給客戶端,反之亦然,這在一些需要即時通訊的場景例如多人聊天室非常重要。
flask_socketio實現了對websocket的封裝,它可以讓執行flask應用的伺服端和客戶端建立全雙工通道。
flask_socketio是一個python庫,是flask框架的擴展。
一、安裝
1 | pip install flask-socketio |
二、實現對flask的封裝
1 2 3 4 5 6 7 8 | 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()都可以實現訊息發送,前者用於無名事件,後者用於命名的事件。
事件是訊息的名稱。如果把訊息比做信件,事件就是貼在信封上的標識,這個標識規定了信件送往客戶端或伺服端的某個函式。
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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迴圈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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迴圈以解決這個問題的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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的內容如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <!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就可以了