• Flask-[实现websocket]-(2): flask-socketio文档学习


    一、简单项目的构建

    flask_websocket

            |---static

                    |---js

                            |---jquery-3.7.0.min.js

                            |---socket.io_4.3.1.js

            |---templates

                    |---home

                            |---group_chat.html

                            |---index.html

            |---app.py

    1.1、python环境

    python3.9.0

    1.2、依赖包

    Flask==2.1.0
    eventlet==0.33.3     #使用这个性能会最优
    Flask-SocketIO==5.3.4


    1.3、js文件下载

    https://code.jquery.com/jquery-3.7.0.min.js

    https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.3.1/socket.io.min.js

     

    1.4、app.py 配置

    1. from flask import Flask, render_template
    2. from flask_socketio import SocketIO
    3. app = Flask(__name__)
    4. app.config['SECRET_KEY'] = 'secret!'
    5. socketio = SocketIO(app,cors_allowed_origins='*')
    6. if __name__ == '__main__':
    7. socketio.run(app)

    二、官方文档的学习

    2.1、namespace:名称空间

    概述:名称空间,事件函数在处理请求时,只会处理相同名称空间的websocket请求。跟路由分发很像,把处理请求范围缩小在指定区域中。一般用在不同功能,大方向的不同功能。

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

    1、如果不设置名称空间,是由默认的名称空间的,就是\

    2、在fbv使用时,可以不指定默认的名称空间,在cbv使用时,必须指定名称空间

    2.1.1、后端简单的例子:

    1、使用默认的全局名称空间

    1. @socketio.on('message')
    2. def handle_message(data):
    3. #data: 前端发送过来的数据
    4. print('received message: ' + data)

    2、定义了名称空间的

    1. @socketio.on('message',namespace='/default')
    2. def namespace_message(data):
    3. '''
    4. :param data:前端发起websocket,传递给服务端的数据
    5. :return:
    6. '''
    7. print('接收到前端发送过来的数据:',data)

    虽然这两个事件函数,监听的是同一个事件,但是它们的名称空间不一样,所以数据的收发不会混乱。

    2.1.1、前端js例子

    1、js连接默认的名称空间

    1. html>
    2. <html>
    3. <head>
    4. <script src="/static/js/socket.io_4.3.1.js">script>
    5. <script src="/static/js/jquery-3.7.0.min.js">script>
    6. <script type="text/javascript">
    7. var socket = io('http://'+document.domain+':'+location.port);
    8. // 1、监听服务器端,给message事件的响应
    9. socket.on('message', function(message) {
    10. // 处理从服务器接收到的响应数
    11. alert(message)
    12. });
    13. //3、 发送'message'事件给服务器d
    14. function sendMessage() {
    15. var message = {"type":"user","id":1};
    16. //向后端获取id=1的用户的数据
    17. socket.emit('message',message); //可以直接返回字典等数据
    18. }
    19. script>
    20. head>
    21. <body>
    22. <div id="show">
    23. div>
    24. <button onclick="sendMessage()">Send Messagebutton>
    25. body>
    26. html>

    2、js连接指定的名称空间

    1. html>
    2. <html>
    3. <head>
    4. <script src="/static/js/socket.io_4.3.1.js">script>
    5. <script src="/static/js/jquery-3.7.0.min.js">script>
    6. <script type="text/javascript">
    7. const namespace = '/default';
    8. var socket = io('http://'+document.domain+':'+location.port+namespace);
    9. // 1、监听服务器端,给message事件的响应
    10. socket.on('message', function(message) {
    11. // 处理从服务器接收到的响应数
    12. alert(message)
    13. });
    14. //3、 发送'message'事件给服务器d
    15. function sendMessage() {
    16. var message = {"type":"user","id":1};
    17. //向后端获取id=1的用户的数据
    18. socket.emit('message',message); //可以直接返回字典等数据
    19. }
    20. script>
    21. head>
    22. <body>
    23. <div id="show">
    24. div>
    25. <button onclick="sendMessage()">Send Messagebutton>
    26. body>
    27. html>

    2.2、事件

    概述:在名称空间下,就开始匹配事件名,最终是基于事件名进行消息的收发的。事件名可以找到指定的函数来处理这个事件请求的。还是跟路由分发类似,先找到一个大范围,再从这个范围中找指定的某个路由,将请求交给路由对应的函数处理。

    2.2.1、自带的4个事件

    1、connect   事件

            用来控制是否允许客户端建立连接的,一般会在这里验证用户的token,token验证通过才允许连接;或群聊中显示谁进入群聊了。

    发起连接时,怎么携带上数据:

    1. <script type="application/javascript">
    2. //1、发起连接
    3. const namespace = '/test'
    4. const socket = io('http://'+document.domain+':'+location.port+namespace,
    5. {query:{'token':'123456','group':group,'name':name}}
    6. );
    7. script>
    1. @socketio.on('connect',namespace='/chat')
    2. def chat_connect():
    3. '''
    4. 控制群里用户进入群连接
    5. :return:
    6. '''
    7. token = request.args.get('token')
    8. name = request.args.get('name')
    9. group = request.args.get('group')
    10. print('发起连接时携带的数据:',request.args)
    11. if token:
    12. join_room(group)
    13. emit('group_recv',{'name':name,'msg':f'进入’{group}‘群聊','type':'connect'},room=group)
    14. else:
    15. return False

    2、disconnect   事件

            一般是用于统计活着的连接数,或者群聊中显示谁退出群聊。

    3、message 事件

            可以传递字典、字符串等数据结构。

    4、json 事件

            可以传递字典、字符串等数据结构。在使用过程中。

    json和message事件,好像在使用上没有区别,看文档也没有看出啥不同之处。

    2.2.2、事件函数的写法

    1、显示指定事件名

    使用默认的名称空间时:

    1. @socketio.on('my_event')
    2. def handle_my_custom_event(json):
    3. print('received json: ' + str(json))

    指定名称空间时:

    1. @socketio.on('my_event',namespace='/test')
    2. def handle_my_custom_event(json):
    3. print('received json: ' + str(json))

    2、使用函数名作为事件名

    使用默认的名称空间时:

    1. @socketio.event
    2. def my_custom_event(data):
    3. print('received data: ',data)

    指定名称空间时:

    1. @socketio.event(namespace='/test')
    2. def my_custom_event(data):
    3. print('received data: ',data)

    2.2.3、自定义事件名称

    自定义的事件名称,message、json、connect、disconnect是保留名称,不能用于已命名事件。

    2.3、消息发送

    from flask_socketio import send,emit

    1、send  : 不能指定事件名,默认指定message事件,不能改。

    传递的参数:第一个是发送的数据,namespace=‘/名称空间’, broadcast=True 采用广播

    2、emit:需要指定事件名,可以指定自定义的事件。

    传递的参数:事件名称、传递发送的数据,namespace=‘/名称空间’, broadcast=True 采用广播

    在普通的视图函数中使用:(在事件函数中使用方法一样)

    1. @app.route('/test/push')
    2. def test_push():
    3. emit('json',{'code':200,'msg':'使用emit来主动推送广播'},namespace='/test',broadcast=True)
    4. send({'code':200,'msg':'使用send来主动推送广播'},namespace='/test',broadcast=True)
    5. return jsonify({"code":200,'msg':'ok'})

    注意:send和emit必须在 socketio.on 装饰的事件函数中使用,需要使用到request.sid ,在普通的视图函数中无法这两个方法。

    3、socketio.send() 和socketio.emit()  

    参数:不能传递broadcast=True,其他与send和emit没有区别。

    功能:发送的消息是广播消息。

    socketio.send() 是给message事件推送消息,而且推送的是广播消息。

    socketio.emit() 需要传递事件名称,推送是也是广播消息。

    注意:这个两个方法一般在视图函数中使用,给系统中同一个名称空间下的事件名广播消息。

    2.4、广播

    广播:广播的范围是同一个名称空间下的同一个事件进行消息广播。

    from flask_socketio import send,emit

    使用这两个时,要实现广播功能需要添加参数,broadcast=True。

    from flask_socketio import SocketIO

    socketio=SocketIO(app)

    socketio.send('data'), 给message事件广播消息。

    socketio.emit('xxx','data') , 给xxx事件广播消息。

    2.5、房间号

    概述:要实现群聊功能,需要房间的功能,将连接添加到某个房间中,这些连接就在一个房间中。一个连接向房间中发送消息,房间中的所有连接都会接收到该消息。房间很像广播,只是房间的把消息的范围限定在房间中(名称空间、事件名称、房间号都一样),而广播,只要名称空间和事件名一样就会接收到,消息范围会更大。

    from flask_socketio import join_room,leave_room

    join_room : 加入房间

    leave_room: 退出房间

    官方文档的例子:

    1、建立连接后,基于join事件,把连接添加到某个房间中

    2、建立连接后,基于leave事件,把连接从某个房间中移除

    1. from flask_socketio import join_room, leave_room
    2. @socketio.on('join')
    3. def on_join(data):
    4. username = data['username']
    5. room = data['room']
    6. join_room(room)
    7. send(username + ' has entered the room.', to=room)
    8. @socketio.on('leave')
    9. def on_leave(data):
    10. username = data['username']
    11. room = data['room']
    12. leave_room(room)
    13. send(username + ' has left the room.', to=room)

    在使用时,加上名称空间会更容易区分功能。

    1. from flask_socketio import join_room, leave_room
    2. @socketio.on('join',namespace='/chat')
    3. def on_join(data):
    4. username = data['username']
    5. room = data['room']
    6. join_room(room)
    7. send(username + ' has entered the room.', to=room)
    8. @socketio.on('leave',namespace='/chat')
    9. def on_leave(data):
    10. username = data['username']
    11. room = data['room']
    12. leave_room(room)
    13. send(username + ' has left the room.', to=room)
    14. @socketio.on('group',namespace='/chat')
    15. def handle_group(data):
    16. '''
    17. :param data: 用户在群聊中发送消息
    18. :return:
    19. '''
    20. print('chat群里发消息:',data)
    21. group = data.get('group')
    22. msg = data.get('msg')
    23. name = data.get('name')
    24. ret_data = {'msg':msg,'name':name,'type':'data'}
    25. emit('group_recv',ret_data,room=group)

    2.6、CBV的写法

    概述:使用CBV的写法 ,就必须传递上namespace,即使是默认的全局名称空间,也得传递'/' .

    2.6.1、官方例子

    1. from flask_socketio import Namespace, emit
    2. class MyCustomNamespace(Namespace):
    3. def on_connect(self):
    4. #方法名去掉on_,剩下的就是事件名称,connect事件
    5. pass
    6. def on_disconnect(self):
    7. #方法名去掉on_,剩下的就是事件名称,disconnect事件
    8. pass
    9. def on_my_event(self, data):
    10. #方法名去掉on_,剩下的就是事件名称,my_event事件
    11. emit('my_response', data)
    12. #注册该消费类,类似注册路由一样
    13. socketio.on_namespace(MyCustomNamespace('/test'))

    2.6.2、官方例子与js结合

    html文件

    1. html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <title>群聊title>
    6. <script type="application/javascript" src="/static/js/jquery-3.7.0.min.js">script>
    7. <script type="application/javascript" src="/static/js/socket.io_4.3.1.js">script>
    8. <script type="application/javascript">
    9. //传递给模板的数据,转成python数据结构
    10. const data = {{ data|tojson }};
    11. const group = 'group1'; //群名
    12. const name = 'lhz';//当前用户名
    13. //1、发起连接
    14. const socket = io('http://'+document.domain+':'+location.port+'/cbv',
    15. {query:{'token':'123456','group':group,'name':name}}
    16. );
    17. //2、监听my_event事件,服务端发送数据给my_event事件
    18. socket.on('my_event',function (data) {
    19. console.log('my_event事件监听到')
    20. console.log(data)
    21. });
    22. //3、前端主动发my_event事件到服务端
    23. function sendDataFunc() {
    24. const $sendData = $('#sendDataId');
    25. socket.emit('my_event',{'group':group,'msg':$sendData.val(),'name':name});
    26. }
    27. script>
    28. head>
    29. <body>
    30. {#1、展示群聊的消息#}
    31. <div id="showDataId">
    32. div>
    33. {#2、发送消息的输入框#}
    34. <div>
    35. <input id="sendDataId" type="text">
    36. <p><input type="button" value="发送给my_event事件" onclick="sendDataFunc()">p>
    37. div>
    38. body>
    39. html>

    cbv例子:

    1. @app.route('/cbv')
    2. def cbv_html():
    3. return render_template('test/test_cbv.html',data={'group':'123','name':'lhz'})
    4. class MyCustomNamespace(Namespace):
    5. def on_connect(self):
    6. # 方法名去掉on_,剩下的就是事件名称,connect事件
    7. print(request.args,'发起连接时携带的数据')
    8. def on_disconnect(self):
    9. # 方法名去掉on_,剩下的就是事件名称,disconnect事件
    10. print('退出连接')
    11. def on_my_event(self, data):
    12. # 方法名去掉on_,剩下的就是事件名称,my_event事件
    13. print('前端给my_event事件发送的消息',data)
    14. emit('my_event', {'code':200,'msg':'后端接收到并返回了','data':data.get('msg')})
    15. # 注册该消费类,类似注册路由一样
    16. socketio.on_namespace(MyCustomNamespace('/cbv'))

  • 相关阅读:
    基于matlab GUI的数字图像处理系统
    项目部署与服务器环境配置(CentOS版本)
    CISP考试有哪些备考技巧
    Flutter Cocoon 已达到 SLSA 2 级标准的要求
    【Tomcat专题】Tomcat如何打破双亲委派机制?
    基于Spring接口,集成Caffeine+Redis两级缓存
    使用MySQL和SQL Server生成最近七天的日期
    ‘vue’不是内部或外部命令,也不是可运行的程序或批处理文件
    Android入门第7天-TableLayout
    js 不同域iframe 与父页面消息通信
  • 原文地址:https://blog.csdn.net/weixin_46371752/article/details/133068939