Flask-SocketIO的使用

姜森
2023-12-01

Flask-SocketIO为Flask应用程序提供对客户端和服务器之间的低延迟双向通信的访问。客户端应用程序可以使用Javascript,C++,Java和Swift中任何SocketIO官方客户端和任何兼容客户端来建立与服务器的永久连接。

安装

pip install flask-socketio

依赖

Flask-SocketIO兼容Python2.7和Python3.3+,该软件的异步服务可以从以下三种选择中选择:

  • eventlet:高性能选项,支持长轮询和WebSocket传输
  • gevent:支持多种不同的配置。long-polling传输完全由gevent包支持,但与eventlet不同,gevent没有原生的WebSocket支持。为了增加对WebSocket的支持,目前有两种选择:安装gevent-websocket软件包会将WebSocket支持添加到gevent中,或者可以使用随WebSocket功能一起提供的uWSGI Web容器。gevent也是一项高性能选项,但略低于eventlet。
  • 基于Werkzeug的Flask开发服务器也可以使用,但需要注意的是它缺少其他两个选项的性能,所以它只能用于简化开发流程。该选项仅支持长轮询传输

该扩展根据安装的内容自动检测使用哪个异步框架,优先考虑eventlet,然后是gevent。对于gevent中的WebSocket支持,首选uWSGI,然后是gevent-websocket。如果既没有安装eventlet,也没有安装gevent,则使用Flask开发服务器。

如果使用多个进程,则进程使用消息队列服务来协调诸如广播等操作。受支持的队列包括:Redis、RabbitMQ和Kombu软件包支持的任何其他消息队列。

初始化

举个简单的栗子:

from flask import Flask
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", port=5000)

init_app()初始化的样式也被支持。请注意Web服务器的启动方式,该socketIO.run()功能封装了Web服务器的启动并取代了app.run()标准的Flask开发服务器的启动。当我们的服务器程序启动了,相应的客户端程序必须提供一个页面加载SocketIO库:

<script type="text/javascript" src="http://cdn.bootcss.com/socket.io/1.5.1/socket.io.min.js"></script>
<script type="text/javascript" charset="utf-8">
    var socket = io.connect('http://localhost:5000');
    socket.on('connect', function() {
        socket.emit('my event', {"data": 'I\'m connected!'});
    });
    socket.on('my response',function(message){
        console.log(message)
    })
</script>

第一个路由

下面写一个简单的路由,使服务器与客户端进行连接:

from flask_socketio import emit

@socketio.on("connect")
def handle_connect():
    print("server has connected")
    emit("my response", "server has connected")

下面再定义一个my event路由,接收客户端发送过来的消息:

from flask_socketio import emit

@socketio.on("my event")
def handle_event(message):
    print("服务器已经接收到消息:" + message["data"])
    emit("my response", "服务器已经接收到消息:" + message["data"])

当客户端与服务器端进行相连时,服务器端将会先后打印如下信息:

server has connected
服务器已经接收到消息:I'm connected!

客户端将会先后打印如下信息:

server has connected
服务器已经接收到消息:I'm connected!

接收消息

使用SocketIO时,消息由双方作为事件接收。在客户端使用JavaScript回调。使用Flask-SocketIO时,服务器需要为这些时间注册处理程序,类似于视图函数处理路由的方式。

  • 未命名事件:对于客户端使用send发送的匿名消息,flask使用message来进行接收
    以下实例创建一个服务器端未命名事件处理程序:
@socketio.on('message')
def handle_message(message):
    print('接收到消息:' + message)
  • 自定义命名事件:flask可以自定义路由的名称,这样的路由称之为自定义命名事件
@socketio.on("my event")
def handle_event(arg1, arg2, arg3):
    print(arg1, arg2, arg3)

Flask-SocketIO还支持SocketIO命名空间,它允许客户端在同一个物理套接字上多路复用同一个连接:

@socketio.on("my event",namespace="/test")
def handle_my_event(message):
    print("test", message)

有时我们还需要动态添加一些路由,很显然装饰器已经不符合我们的需求。我们还可以使用on_event方法:

def custom_func(message):
    print("custom", message)
    
socketio.on_event("custom func", custom_func, namespace="/test")

客户端可以要求确认回复,用于确认收到他们的消息。从处理函数返回的任何值都将作为客户端的回调函数的参数传递给客户端:

@socketio.on("message")
def handle_message(message):
    return "已经收到了消息:" + str(message)

发送消息

在第一个路由中,我们使用了emit()函数来发送消息给客户端,并且在上面的接收消息中知道客户端可以使用send来发送未命名事件消息,既然客户端可以使用send函数,那么服务器端当然也可以。所以,我们可以使用emit()send()两种函数来发送消息给客户端。值得注意的是:send()emit()的分别在于分别用于未命名事件与自定义命名事件。

举个简单的发送消息的栗子:

from flask_socketio import emit, send

@socketio.on('message')
def handle_message(message):
    send(message)
    
@socketio.on('my event')
def handle_event(event):
    emit('my response', event)

注意的是:对于服务器使用send发送的匿名消息,客户端同样使用用message来进行接收

当有命名空间时,send()emit()默认使用传入消息的命名空间,当然也可以使用namespace参数来指定不同的命名空间:

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

如果需要发送一个包含多个参数的时间,请发送一个元组:

@socketio.on('my event')
def handle_message(message):
    emit('my response', (message, "aa"))

广播

SocketIO的另一个有用的功能是广播消息。Flask-SocketIO也支持这个特性,在send()emit()函数中设置可选参数broadcast=True即可:

@socketio.on('my event')
def handle_my_event(data):
    emit('my response', data, broadcast=True)

当使用broadcast=True的情况下发送消息,连接到命名空间的所有客户端都将收到该消息。当不使用命名空间时,连接到全局名称的客户端将收到此消息。注意,广播消息不会回调

房间

对于许多应用程序来说,有必要将用户分组到可以一起处理的子集中。最好的栗子就是具有多个房间的聊天应用,用户可以从他们所在的房间接收消息,但不能从其他用户所在的其他房间接收消息。Flask-SocketIO通过join_room()leave_room()方法来支持房间的功能

from flask_socketio import join_room

@socketio.on('join')
def join(data):
    username = data['username']
    room = data['room']
    join_room(room)
    send(f'{username} has entered the room', to=room)
    
@socketio.on('leave')
def leave(data):
    username = data['username']
    room = data['room']
    leave_room(room)
    send(f'{username} has left the room', to=room)

所有的客户端在连接时都会分配一个房间,以连接的会话ID命名,会话ID可以通过request.sid获取。当我们想给指定客户端发送消息时,我们可以emit()send()函数,将会话ID传递到函数中的to参数里面就可以实现。当我们想给房间里的每个客户端都发送一条一样的消息时,我们可以将房间ID传递到room参数,或to参数里面就可以实现了

连接与断开连接

Flask-SocketIO还分派连接和断开连接事件,如下:

@socketio.on('connect')
def on_connect():
    pass
    
@socketio.on('disconnect')
def on_disconnect():
    pass

连接事件处理程序可以返回False来拒绝连接,也可以引发ConnectRefusedError。如下,如果是未认证的,那么将拒绝连接!

from flask_socketio import ConnectRefusedError

@socketio.on('connect')
def on_connect():
    if not self.authenticate(request.args):
        raise ConnectionRefusedError('unauthorized!')

基于类的命名空间

作为上述基于装饰器的事件处理程序的替代,可以将属于命名空间的事件处理程序创建为类的方法。flask_socketio.Namespace被设置为基类来创建基于类的命名空间:

from socketio import Namespace

class MyCustomNamespace(Namespace):
    
    def on_connect(self):
        emit('connect_success', {'msg': 'connect success'})
        
    def on_disconnect(self):
        pass
        
    def on_edit(self, data):
        article_id = data.get('article_id')
        join_room(article_id)
        emit('edit_success', {'msg': f'{article_id} edit success'})
        
socketio.on_namespace(MyCustomNamespace('/'))

使用基于类的命名空间时,服务器接收到的任何事件都被发送到一个以on_前缀作为事件名的方法。例如,事件“my_event”将由一个名为“on_my_event”的方法处理。如果接收的事件在命名空间类中没有定义相应的方法,则忽略该事件。

更多Flask-SocketIO的用法,请看https://flask-socketio.readthedocs.io/en/latest/

自此,Over~~~

 类似资料: