使用flask_socketio實現服務端向客戶端定時推送
websocket連線是客戶端與伺服器之間永久的雙向通訊通道,直到某方斷開連線。
雙向通道意味著在連線時,服務端隨時可以傳送訊息給客戶端,反之亦然,這在一些需要即時通訊的場景比如多人聊天室非常重要。
flask_socketio實現了對websocket的封裝,它可以讓執行flask應用的服務端和客戶端建立全雙工通道。
flask_socketio是一個python庫,是flask框架的擴充套件。
一、安裝
pip install flask-socketio
二、實現對flask的封裝
from flask import Flask, render_templatefrom 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.”。我的理解是一個namespace就定義了一個websocket連線,我們可以將多個事件放到一個連線中處理,否則就會有多個物理通道在使用。
再看soketio.emit,第一個引數'server_response'是服務端傳送這個訊息的事件名,在客戶端要建立一個接受這個事件的函式處理,後面的字典就是訊息內容,namespace='/test_conn'表示這個訊息還是傳送到同一個通道(test_conn)中。
四、定時推送
實驗的目的是服務端定時傳送一個隨機數到客戶端,並且客戶端可以及時顯示。
一開始,我在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);
socket.on('server_response', function(res) {
console.log(res.data);
$('#t').text(res.data);
});
});
</script>
</body>
</html>
注意客戶端也要匯入socketio的庫,然後用io.connect建立命名域的socket連線。
最後在瀏覽器輸入http://127.0.0.1:5000就可以了
六、結果
開發者工具的console可以檢視日誌