websocket鏈接是客戶端與服務器之間永久的雙向通訊通道,直到某方斷開鏈接。javascript
雙向通道意味着在鏈接時,服務端隨時能夠發送消息給客戶端,反之亦然,這在一些須要即時通信的場景好比多人聊天室很是重要。html
flask_socketio實現了對websocket的封裝,它可讓運行flask應用的服務端和客戶端創建全雙工通道。前端
flask_socketio是一個python庫,是flask框架的擴展。java
1、安裝python
pip install flask-socketio
2、實現對flask的封裝jquery
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服務器的啓動web
3、服務端向客戶端推送ajax
socketio的兩個函數send()和emit()均可以實現消息發送,前者用於無名事件,後者用於命名的事件。flask
事件是消息的名稱。若是把消息比作信件,事件就是貼在信封上的標識,這個標識規定了信件送往客戶端或服務端的某個函數。後端
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})是收不到的。
4、定時推送
實驗的目的是服務端定時發送一個隨機數到客戶端,而且客戶端能夠及時顯示。
一開始,我在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)
5、客戶端
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就能夠了
6、結果
開發者工具的console能夠查看日誌