Flask-SocketIO 簡單使用指南

yongxinz發表於2018-11-01

歡迎關注我的微信公眾號 AlwaysBeta,更多精彩內容等你來。

Flask-SocketIO 簡單使用指南

Flask-SocketIO 使 Flask 應用程式能夠訪問客戶端和伺服器之間的低延遲雙向通訊。客戶端應用程式可以使用 Javascript,C ++,Java 和 Swift 中的任何 SocketIO 官方客戶端庫或任何相容的客戶端來建立與伺服器的永久連線。

安裝

直接使用 pip 來安裝:

pip install flask-socketio
複製程式碼

要求

Flask-SocketIO 相容 Python 2.7 和 Python 3.3+。可以從以下三個選項中選擇此程式包所依賴的非同步服務:

  • eventlet 效能最佳,支援長輪詢和 WebSocket 傳輸。
  • gevent 在許多不同的配置中得到支援。gevent 包完全支援長輪詢傳輸,但與 eventlet 不同,gevent 沒有本機 WebSocket 支援。要新增對 WebSocket 的支援,目前有兩種選擇:安裝 gevent-websocket 包為 gevent 增加 WebSocket 支援,或者可以使用帶有 WebSocket 功能的 uWSGI Web 伺服器。gevent 的使用也是一種高效能選項,但略低於 eventlet。
  • 也可以使用基於 Werkzeug 的 Flask 開發伺服器,但需要注意的是,它缺乏其他兩個選項的效能,因此它只應用於簡單的開發環境。此選項僅支援長輪詢傳輸。

擴充套件會根據安裝的內容自動檢測要使用的非同步框架。優先考慮 eventlet,然後是 gevent。對於 gevent 中的WebSocket 支援,首選 uWSGI,然後是 gevent-websocket。如果既未安裝 eventlet 也未安裝 gevent,則使用 Flask 開發伺服器。

如果使用多個程式,則程式使用訊息佇列服務來協調諸如廣播之類的操作。支援的佇列是 RedisRabbitMQ以及 Kombu 軟體包支援的任何其他訊息佇列 。

在客戶端,官方 Socket.IO Javascript 客戶端庫可用於建立與伺服器的連線。還有使用 Swift,Java 和 C ++ 編寫的官方客戶端。非官方客戶端也可以工作,只要它們實現 Socket.IO協議

初始化

以下程式碼示例演示如何將 Flask-SocketIO 新增到 Flask 應用程式:

from flask import Flask, render_template
from flask_socketio import SocketIO

app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app)

if __name__ == '__main__':
    socketio.run(app, host='0.0.0.0', debug=True)
複製程式碼

以上程式碼即完成了一個簡單的 Web 伺服器。

socketio.run()函式封裝了 Web 伺服器的啟動,並替換了app.run()標準的 Flask 開發伺服器啟動。

當應用程式處於除錯模式時,Werkzeug 開發伺服器仍然在內部使用和配置正確socketio.run()

在生產模式中,如果可用,則使用 eventlet Web 伺服器,否則使用 gevent Web 伺服器。如果未安裝 eventlet 和gevent,則使用 Werkzeug 開發 Web 伺服器。

基於 Flask 0.11 中引入的單擊的命令列介面。此擴充套件提供了適用於啟動 Socket.IO 伺服器的新版本命令。用法示例:flask run

$ FLASK_APP=my_app.py flask run
複製程式碼

或者直接使用下面方式,也可以啟動專案:

$ python2.7 app.py
複製程式碼

連線事件

Flask-SocketIO 排程連線和斷開事件。以下示例顯示如何為它們註冊處理程式:

@socketio.on('connect', namespace='/test')
def test_connect():
    emit('my response', {'data': 'Connected'})

@socketio.on('disconnect', namespace='/test')
def test_disconnect():
    print('Client disconnected')
複製程式碼

連線事件處理程式可以選擇返回False以拒絕連線。這樣就可以在此時對客戶端進行身份驗證。

請注意,連線和斷開連線事件將在使用的每個名稱空間上單獨傳送。

接收訊息

使用 SocketIO 時,雙方都會將訊息作為事件接收。在客戶端使用 Javascript 回撥。使用 Flask-SocketIO,伺服器需要為這些事件註冊處理程式,類似於檢視函式處理路由的方式。

以下示例為未命名的事件建立伺服器端事件處理程式:

@socketio.on('message')
def handle_message(message):
    print('received message: ' + message)
複製程式碼

上面的示例使用字串訊息。另一種型別的未命名事件使用 JSON 資料:

@socketio.on('json')
def handle_json(json):
    print('received json: ' + str(json))
複製程式碼

最靈活的方式是使用自定義事件名稱,在開發過程中最常用的也是這種方式。

事件的訊息資料可以是字串,位元組,整數或 JSON:

@socketio.on('my event')
def handle_my_custom_event(json):
    print('received json: ' + str(json))
複製程式碼

自定義命名事件也可以支援多個引數:

@socketio.on('my event')
def handle_my_custom_event(arg1, arg2, arg3):
    print('received args: ' + arg1 + arg2 + arg3)
複製程式碼

Flask-SocketIO 支援 SocketIO 名稱空間,允許客戶端在同一物理套接字上覆用多個獨立連線:

@socketio.on('my event', namespace='/test')
def handle_my_custom_namespace_event(json):
    print('received json: ' + str(json))
複製程式碼

如果未指定名稱空間,'/'則使用具有名稱的預設全域性名稱空間 。

對於裝飾器語法不方便的情況,on_event可以使用該方法:

def my_function_handler(data):
    pass

socketio.on_event('my event', my_function_handler, namespace='/test')
複製程式碼

客戶端可以請求確認回叫,確認收到他們傳送的訊息。處理函式返回的任何值都將作為回撥函式中的引數傳遞給客戶端:

@socketio.on('my event')
def handle_my_custom_event(json):
    print('received json: ' + str(json))
    return 'one', 2
複製程式碼

在上面的示例中,將使用兩個引數呼叫客戶端回撥函式,'one'2。如果處理程式函式未返回任何值,則將呼叫客戶端回撥函式而不帶引數。

傳送訊息

如上一節所示定義的 SocketIO 事件處理程式可以使用send()emit() 函式將回復訊息傳送到連線的客戶端。

以下示例將收到的事件退回給傳送它們的客戶端:

from flask_socketio import send, emit

@socketio.on('message')
def handle_message(message):
    send(message)

@socketio.on('json')
def handle_json(json):
    send(json, json=True)

@socketio.on('my event')
def handle_my_custom_event(json):
    emit('my response', json)
複製程式碼

注意如何send()emit()分別用於無名和命名事件。

當有名稱空間的工作,send()emit()預設使用傳入訊息的名稱空間。可以使用可選namespace引數指定不同的名稱空間:

@socketio.on('message')
def handle_message(message):
    send(message, namespace='/chat')

@socketio.on('my event')
def handle_my_custom_event(json):
    emit('my response', json, namespace='/chat')
複製程式碼

要傳送具有多個引數的事件,請傳送元組:

@socketio.on('my event')
def handle_my_custom_event(json):
    emit('my response', ('foo', 'bar', json), namespace='/chat')
複製程式碼

SocketIO 支援確認回撥,確認客戶端收到了一條訊息:

def ack():
    print 'message was received!'

@socketio.on('my event')
def handle_my_custom_event(json):
    emit('my response', json, callback=ack)
複製程式碼

使用回撥時,Javascript 客戶端會收到一個回撥函式,以便在收到訊息時呼叫。客戶端應用程式呼叫回撥函式後,伺服器將呼叫相應的伺服器端回撥。如果使用引數呼叫客戶端回撥,則這些回撥也作為伺服器端回撥的引數提供。

廣播

SocketIO 的另一個非常有用的功能是廣播訊息。SocketIO 支援通過此功能broadcast=True可選引數send()emit()

@socketio.on('my event')
def handle_my_custom_event(data):
    emit('my response', data, broadcast=True)
複製程式碼

在啟用廣播選項的情況下傳送訊息時,連線到名稱空間的所有客戶端都會接收它,包括髮件人。如果未使用名稱空間,則連線到全域性名稱空間的客戶端將收到該訊息。請注意,不會為廣播訊息呼叫回撥。

在此處顯示的所有示例中,伺服器響應客戶端傳送的事件。但對於某些應用程式,伺服器需要是訊息的發起者。這對於向客戶端傳送通知在伺服器中的事件(例如在後臺執行緒中)非常有用。socketio.send()socketio.emit()方法可用於廣播到所有連線的客戶端:

def some_function():
    socketio.emit('some event', {'data': 42})
複製程式碼

請注意,socketio.send()socketio.emit()在上下文理解上和send()emit()功能不同。另請注意,在上面的用法中沒有客戶端上下文,因此broadcast=True是預設的,不需要指定。

房間

對於許多應用程式,有必要將使用者分組為可以一起定址的子集。最好的例子是具有多個房間的聊天應用程式,其中使用者從他們所在的房間接收訊息,而不是從其他使用者所在的其他房間接收訊息。SocketIO 支援通過房間的概念join_room()leave_room()功能:

from flask_socketio import join_room, leave_room

@socketio.on('join')
def on_join(data):
    username = data['username']
    room = data['room']
    join_room(room)
    send(username + ' has entered the room.', room=room)

@socketio.on('leave')
def on_leave(data):
    username = data['username']
    room = data['room']
    leave_room(room)
    send(username + ' has left the room.', room=room)
複製程式碼

send()emit()函式接受一個可選room導致被髮送到所有的都在定房客戶端的訊息的說法。

所有客戶端在連線時都會被分配一個房間,以連線的會話ID命名,可以從中獲取request.sid。給定的客戶可以加入任何房間,可以給出任何名稱。當客戶端斷開連線時,它將從其所在的所有房間中刪除。無上下文socketio.send()socketio.emit()函式也接受一個room引數,以廣播給房間中的所有客戶端。

由於為所有客戶端分配了個人房間,為了向單個客戶端傳送訊息,客戶端的會話 ID 可以用作房間引數。

錯誤處理

Flask-SocketIO還可以處理異常:

@socketio.on_error()        # Handles the default namespace
def error_handler(e):
    pass

@socketio.on_error('/chat') # handles the '/chat' namespace
def error_handler_chat(e):
    pass

@socketio.on_error_default  # handles all namespaces without an explicit error handler
def default_error_handler(e):
    pass
複製程式碼

錯誤處理函式將異常物件作為引數。

還可以使用request.event變數檢查當前請求的訊息和資料引數,這對於事件處理程式外部的錯誤記錄和除錯很有用:

from flask import request

@socketio.on("my error event")
def on_my_event(data):
    raise RuntimeError()

@socketio.on_error_default
def default_error_handler(e):
    print(request.event["message"]) # "my error event"
    print(request.event["args"])    # (data,)
複製程式碼

基於類的名稱空間

作為上述基於裝飾器的事件處理程式的替代,屬於名稱空間的事件處理程式可以建立為類的方法。flask_socketio.Namespace作為基類提供,用於建立基於類的名稱空間:

from flask_socketio import Namespace, emit

class MyCustomNamespace(Namespace):
    def on_connect(self):
        pass

    def on_disconnect(self):
        pass

    def on_my_event(self, data):
        emit('my_response', data)

socketio.on_namespace(MyCustomNamespace('/test'))
複製程式碼

使用基於類的名稱空間時,伺服器接收的任何事件都將排程到名為帶有on_字首的事件名稱的方法。例如,事件my_event將由名為的方法處理on_my_event。如果收到的事件沒有在名稱空間類中定義的相應方法,則忽略該事件。基於類的名稱空間中使用的所有事件名稱必須使用方法名稱中合法的字元。

為了方便在基於類的名稱空間中定義的方法,名稱空間例項包括類中的幾個方法的版本,flask_socketio.SocketIOnamespace沒有給出引數時,這些方法 預設為正確的名稱空間。

如果事件在基於類的名稱空間中具有處理程式,並且還有基於裝飾器的函式處理程式,則僅呼叫修飾的函式處理程式。

測試

以上是作為官網文件的翻譯,下面來說說寫完了程式碼之後,應該怎麼來除錯。

<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/socket.io/1.3.6/socket.io.min.js"></script>
<script type="text/javascript" charset="utf-8">
    var socket = io.connect('http://' + document.domain + ':' + location.port);
    socket.on('connect', function() {
        socket.emit('my event', {data: 'I\'m connected!'});
    });
</script>
複製程式碼

使用 JavaScript 來連線服務端,這裡說一個我遇到的問題,最開始使用的是 jsbin 來測試,但怎麼都連不到後端,原因就是 jsbin 是 HTTPS 的,而我的請求是 HTTP,於是還是老老實實寫了一個 HTML 檔案,原始碼可以直接在 Github 下載。

<!DOCTYPE HTML>
<html>
<head>
    <title>Flask-SocketIO Test</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>
    <script type="text/javascript" charset="utf-8">
        $(document).ready(function() {
            namespace = '/test';
            var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port + namespace);

            socket.on('connect', function() {
                socket.emit('my_event', {data: 'I\'m connected!'});
            });
            
            socket.on('my_response', function(msg) {
                $('#log').append('<br>' + $('<div/>').text('Received #' + msg.count + ': ' + msg.data).html());
            });

            $('form#emit').submit(function(event) {
                socket.emit('my_event', {data: $('#emit_data').val()});
                return false;
            });
        });
    </script>
</head>
<body>
    <h1>Flask-SocketIO Test</h1>
    <p>Async mode is: <b>{{ async_mode }}</b></p>
    <h2>Send:</h2>
    <form id="emit" method="POST" action='#'>
        <input type="text" name="emit_data" id="emit_data" placeholder="Message">
        <input type="submit" value="Echo">
    </form>
    <h2>Receive:</h2>
    <div id="log"></div>
</body>
</html>
複製程式碼

有了這個頁面之後,就可以直接在瀏覽器中輸入 http://127.0.0.1:5000 訪問服務端了,更多功能可以隨意折騰。



相關文件:

github.com/miguelgrinb…

flask-socketio.readthedocs.io/en/latest/

windrocblog.sinaapp.com/?p=1628

zhuanlan.zhihu.com/p/31118736

letus.club/2016/04/10/…