1. 程式人生 > >[翻譯] flask-SocketIO

[翻譯] flask-SocketIO

key location cit connect nts 可用 命令 間接 lib

最近開發工作需要用到websocket去替代老辦法輪詢,因為我們的web系統使用flask搭建,所以使用flask-SocketIO作為我們的websocket方案,因此順手翻譯官方文檔
***
Flask-SocketIO 賦予了Flask應用在客戶端和服務器之間使用低延遲的雙向通訊的能力。客戶端可使用任意的SocketIO官方提供的clients庫(包括 Javascript, C++, Java及Swift)或者所有兼容的第三方client庫與server端建立一個長久連接。

安裝

使用pip安裝:
pip install flask-socketio

必備

Flask-SocketIO兼容ython 2.7 和 Python 3.3+. 這個包中異步services所依賴的可從以下三種中選擇:
1.eventlet 是最佳選擇, 支持長輪詢和WebSocket傳輸.
2.gevent 在一系列不同的配置中被支持. 長輪詢被gevent完全支持,但不同於eventlet, gevent 沒有原生的WebSocket支持. 想要增加對WebSocket的支持有兩種辦法. 安裝gevent-websocket 增加gevent對WebSocket的支持或是使用帶有WebSocket功能的uWSGI web server.使用gevent也是一個高效的選項,但不如eventlet.
3.基於Werkzeug的Flask 開發環境server也是可以的,不過註意它的性能比不上上面兩項,所以它應該只在簡單的開發工作流中使用,它只支持長輪詢.

這一擴展(即flask-SocketIO)自動檢查哪一個異步框架被安裝了,從而決定使用哪一個作為它的異步框架。最好是使用eventlet,其次gevent。對於gevent中的WebSocket支持,首選uWSGI,然後是gevent-websocket。如果既沒有安裝eventlet也沒有安裝gevent,則使用Flask開發服務器。

如果使用了多進程,一個消息隊列將被進程用來協調操作(例如廣播),支持的隊列有redis,rabbitMQ和其它被 Kombu支持的消息隊列。
在客戶端一側,官方Socket.IO Javascript client 庫可創建連接到server的連接. 同樣也有Swift, Java and 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)

init_app()風格的初始化同樣支持。註意Web服務器的啟動方式。 socketio.run()函數封裝了Web服務器的啟動,代替了app.run()標準的Flask開發服務器啟動。當應用處於debug模式時,Werkzeug開發服務器仍在socketio.run()中使用並正確配置。在生產模式下使用eventlet Web服務器(如果可用),否則使用gevent Web服務器。如果沒有安裝eventlet和gevent,則使用Werkzeug development Web服務器。
同樣也支持基於click的命令行接口(Flask 0.11引入)。該擴展提供了適用於啟動Socket.IO服務器的flask run命令的新版本。用法舉例:
$ FLASK_APP=my_app.py flask run


應用必須向加載Socket.IO庫的客戶端提供一個頁面,並建立一個連接:

<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>

接收消息

當使用SocketIO時,消息被雙方作為事件接收。在客戶端使用Javascript回調。使用Flask-SocketIO,服務器需要為這些事件註冊處理程序,類似於視圖函數處理route的方式。
下面的栗子是一個處理未命名事件的服務端事件處理程序

@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

在上面的例子中,客戶端回調函數將被兩個參數“1”和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()使用傳入消息的名稱空間。可以使用可選的命名空間參數來指定不同的命名空間:

@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的另一個非常有用的功能是消息的廣播。 Flask-SocketIO支持使用broadcast = True和optional(可選參數)來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,不需要特別指定。

聊天室

對於許多應用程序來說,有必要將用戶分成可以一起處理的子集。最好的例子是有多個房間的聊天應用程序,用戶從房間或房間接收消息,而不是從其他人的其他房間接收消息。 Flask-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()函數接受一個可選的房間參數,使得消息被發送到給定房間中的所有客戶端。

所有的客戶端在連接時被分配一個房間,用連接的會話ID命名,可以從request.sid中獲得。一個給定的客戶可以加入任何房間,可以給任何名字。當一個客戶端斷開連接時,它將從它所在的所有房間中移除。上下文無關的socketio.send()和socketio.emit()函數也接受一個房間參數來廣播給房間中的所有客戶端。
由於所有的客戶端都被分配了一個私人房間,所以為了向一個客戶端發送消息,客戶端的會話ID可以被用作房間參數。

連接事件

Flask-SocketIO也調度連接和斷開事件。以下示例顯示如何為其註冊處理程序:

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

@socketio.on(‘disconnect‘, namespace=‘/chat‘)
def test_disconnect():
    print(‘Client disconnected‘)

連接事件處理程序可以選擇返回False來拒絕連接。這樣就可以在這個時候驗證客戶端。 請註意,連接和斷開連接事件是在每個使用的命名空間上單獨發送的。

基於類的命名空間

作為上述基於裝飾器的事件處理程序的替代方法,屬於命名空間的事件處理程序可以創建為類的方法。 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.SocketIO類中幾個方法的版本,當沒有給出命名空間參數時,默認為適當的命名空間。
如果事件在基於類的名稱空間中有一個處理程序,並且還有基於裝飾器的函數處理程序,則只調用裝飾的函數處理程序。

錯誤處理

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

錯誤處理程序以Exception對象作為參數
當前請求的消息和數據參數也可以使用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