1. 程式人生 > >使用flask_socketio實現服務端向客戶端定時推送

使用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.”。我的理解是一個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可以檢視日誌