• flask_socketio收发消息


    此过程结合flask-socketio 和 sockeio.js 讲解

    1.初始 flask-socketio

    https://blog.csdn.net/eleanoryss/article/details/109600154

    这个链接是介绍 websocket 和 HTTP 长连接的差别

    websocket 说白一点就是,建立客户端和服务端双向通讯通道, 服务器可以主动向客户端发消息。

    https://www.jianshu.com/p/d81397edd2b1

    上边这个链接是一个对官方文档的翻译。

    2.安装

    python 3.9
    socket.io.js 3.1.3
    https://cdnjs.cloudflare.com/ajax/libs/socket.io/3.1.3/socket.io.js
    
    

    pip install flask-socketio

    依赖安装

    pip install eventlet

    3.初始化项目

    from flask_socketio import SocketIO
    import random
    async_mode = None
    app = Flask(__name__)
    app.config['SECRET_KEY'] = 'secret!'
    socketio = SocketIO(app)
    
    if __name__ == '__main__':
        socketio.run(app)
    # 或者 set flask_app=app  app指的是项目
    # flask run 
    
    # 两种方式都可以
    # socketio = SocketIO()
    # socketio.init_app(app)
    

    4. 客户端js

    相关文档

    https://socket.io/get-started/chat#Integrating-Socket-IO
    cdn 资源
    https://cdnjs.com/libraries/socket.io

    4.接收消息和发送消息

    客户端

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title></title>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/3.1.3/socket.io.js" integrity="sha512-2RDFHqfLZW8IhPRvQYmK9bTLfj/hddxGXQAred2wNZGkrKQkLGj8RCkXfRJPHlDerdHHIzTFaahq4s/P4V6Qig==" crossorigin="anonymous"></script>
    </head>
    <body>
    <script type="text/javascript">
        $(document).ready(function() {
            
            name_space = "/test";
            var socket = io.connect("http://127.0.0.1:5000");
            
            //  连接成功后发送消息
            socket.on("connect", function(){
                //  发送普通消息
                socket.send("connect once");
            })
            
    	   // 接收后台发送至ceishi的消息
            socket.on("ceshi", function(data){
                console.log("有名" + data)
            })
    
            // 接收后台消息
            socket.on("message", function(data){
                console.log("无名"+data)
            })
    
        });
    </script>
    </body>
    </html>
    

    后台

    @socketio.on("message")
    def message(msg):
        print("message", msg)
        socketio.send(msg, broadcast=True)
        socketio.emit('ceshi', "测试")
        
        
    #  接受 前端向json 接口发送的数据 对应前端 --> socket.emit("json", {"hello": "world"})
    @socketio.on("json")
    def json_msg(msg):
        print("json", msg)
        socketio.send(msg, broadcast=True)
    

    讲解:

    之前看文档不太明白,测试了好久.

    socketio 发送消息分为 send 和 emit

    ​ send(): 发送至未命名的一般默认为message, 一会来讲解message是什么

    ​ emit(): 发送到指定接受的活动上.

    socketio.on

    你可以认为这个是socketio 接收函数的接口, 用在客户端<前端>就相当于 websocket 的 ws.onmessage 接受后台传过来的数据, 用在后台同样

    socketio.emit("活动名", 消息, namespace<命名空间>)

    在上边代码有一句:

    socketio.emit('ceshi', "测试") # 这句话的意思是, 发送至活动ceshi 一条消息为 "测试"
    

    在客户端接收的地方为:

    socket.on("ceshi", function(data){
                console.log("有名" + data)
            }) # 接受后台发送到 ceshi 活动的信息
    

    后台同样:

    @socketio.on("message") # 接受发送到 message 活动的消息
    @socketio.on("json") # 接受发送到 json 活动的消息 
    @socketio.on("connect") # 当客户端连接的时候触发
    @socketio.on("disconnect") # 当客户端断开连接的时候触发
    # 除外你还可以自定义名字
    
    socketio.send

    不指定活动名发送, 一般默认发送到 message 中

    namespace: 下边讲解

    客户端代码:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title></title>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/3.1.3/socket.io.js" integrity="sha512-2RDFHqfLZW8IhPRvQYmK9bTLfj/hddxGXQAred2wNZGkrKQkLGj8RCkXfRJPHlDerdHHIzTFaahq4s/P4V6Qig==" crossorigin="anonymous"></script>
    </head>
    <body>
    <script type="text/javascript">
        $(document).ready(function() {
            
            name_space = "/test";
            // 连接后台
            var socket = io.connect("http://127.0.0.1:5000");
            
            //  连接成功后发送消息
            socket.on("connect", function(){
                //  发送普通消息
                socket.send("connect once");
                // 发送json数据
                socket.emit("json", {"hello": "world"})
            })
    
            socket.on("ceshi", function(data){
                console.log("有名" + data)
            })
    
            // 接受后台消息
            socket.on("message", function(data){
                console.log("无名"+data)
            })
        });
    </script>
    </body>
    </html>
    

    后台代码:

    #encoding:utf-8
    #!/usr/bin/env python
    from flask import Flask, render_template
    from flask_socketio import SocketIO
    import random
    async_mode = None
    app = Flask(__name__)
    app.config['SECRET_KEY'] = 'secret!'
    socketio = SocketIO(app)
    
     
    @app.route('/')
    def index():
        return render_template('socketio.html')
     
    
    # 默认接受send 发送过来的, 同时也可以接受 emit("message", "测试")
    @socketio.on("message")
    def message(msg):
        print("message", msg)
        socketio.send(msg, broadcast=True)
        socketio.emit('ceshi', "测试")
    
    
    #  接受 前端向json 接口发送的数据 对应前端 --> socket.emit("json", {"hello": "world"})
    @socketio.on("json")
    def json_msg(msg):
        print("json", msg)
        socketio.send(msg, broadcast=True)
        
    if __name__ == '__main__':
        socketio.run(app, debug=True)
    

    *** 至此你可以通过将接受的数据添加到页面中的某个div,简单实现一个在线聊天

    broadcast = True

    这是 send 和 emit 的参数,指广播, 加上这个之后所有在指定命名空间中的客户端都会收到消息

    5. namespace

    namespace

    命名空间: 相当于 路由的name

    ​ 用来区分逻辑的,

    var socket = io.connect("http://127.0.0.1:5000"+"/ceshi"); # 指定连接 ceshi 命名空间
    

    后台使用:

    @socketio.on("ceshi", namespace="/ceshi")
    
    socketio.emit("ceshi", "message", namespace="/cesi")
    1.也可以在发送消息中使用指明发送的命名空间, 加上后只会发送到连接到指定命名空间
    2.如果连接指明了命名空间, 那么发送的时候不知明,则连接到指定空间的,不会接受消息
    

    6.room 房间

    在客户端方面房间的概念, 用来对用户分组,以便在某个命名空间下进一步的通信频道分离. 将客户端加入/迁出房间的操作在服务器端实现, join_room() 和 leave_room() , 还可以使用 close_room() 来删除房间, rooms () 函数返回房间客户端列表.

    为了保持简单,CatChat中并没有添加房间功能,我们这里仅介绍实现的基本方法。首先,你需要创建一个Room模型存储房间数据。房间可以使用任意的字符串或数字作为标识,所以可以使用主键列作为标
    识,另外再创建一个name列用于存储房间的显示名称。同时,我们还要在程序中提供房间的创建、编辑和删除操作。在房间聊天页面,我们可以在客户端的connect事件监听函数中使用emit()函数触发服务器端自定义的join事件;同样,用户单击离开按钮离开房间后在客户端disconnect事件处理函数中使用emit()函数触发服务器端定义的leave事件:

    socket.on('connect', function() {
       socket.emit('join');
    });
    socket.on('disconnect', function() {
       socket.emit('leave');
    });
    

    JavaScript

    Copy

    在服务器端,自定义的join和leave事件分别用来将用户加入和移出
    房间,这两个自定义事件的处理函数如下所示:

    from flask_socketio import join_room, leave_room
    
    
    @socketio.on('join')
    def on_join(data):
        username = data['username']
        room = data['room']
        join_room(room)
        # 发送至客户端 status 接口, 只发送给在room房间的用户
        emit('status', username + ' has entered the room.', room=room) # 只有指定房间内的用户会收到消息, 这个room 名可以是字符串等唯一标识符
    
    
    @socketio.on('leave')
    def on_leave(data):
        username = data['username']
        room = data['room']
        leave_room(room)
        emit('status', username + ' has left the room.', room=room)
    

    Python

    1. 经测试发现,我在左边窗口点击加入, 左边窗口会收到房间里发送的消息,右边不会。

    2. 再次点击右边的,哪左边右边都会收到消息,因为两个都在房间里了。

    7.sid 用户连接唯一标识

    可以指定sid 发送消息, 但是sid 每次连接都会变,所以可以跟用户表绑定, 每次连接更新用户表sid, 在本次连接用到的时候取出用即可。

    扩展:
    经上述测试后我们是否可以简单实现房间聊天功能。
    1. 用户去创建自己的聊天室并命名(存到数据库中, 可以有唯一id + 聊天室名字), 
    2. 展示所有聊天室(从数据库中获取)
    3. 用户选择聊天室(向后台join接口发送用户信息, 同时发送房间唯一标识),
    4. 后台将用户加入聊天室,使用(join_room) 即可
    5. 用户发送消息的时候,出消息内容还要加上聊天室唯一标识, 后台去向指定标识传就可以
    6. 用户关闭聊天室,采用 close_room(room) 关闭
    
    再次扩展:
    	1.聊天室信息存到 redis 中, 在redis中存储聊天室在线人数,每有人连接进来更新一下。
    	2. 单对单聊天扩展
    		这种情况,要考虑消息离线问题,需要数据库/ 消息队列。
    		简单点采用数据库:
    			单对单原理: 
    				相当于创建1对1的房间
    			1. 与谁聊天就和谁创建一个房间,数据库为房间表 + 用户表, 一 对 多, 采用join_room()
    				1>每次打开聊天界面,把后台消息刷新到页面上。
    				2>删除聊天的时候删除房间
    			2. 同样来一个 房间表 + 用户表, 一 对 多, 采用sid 指定 用户的sid 发送消息,
    				
    

    Copy

    在这两个事件处理器中,我们分别调用Flask-SocketIO提供的
    join_room()和leave_room()函数,并传入房间的唯一标识符。
    提示
    房间也支持命名空间,通过join_room()和leave_room()函数的
    namespace参数指定,默认使用当前正在处理的命名空间,可以通过
    Flask-SocketIO附加在请求对象上的namespace属性获得,即
    request.namesapce。
    同样,在发送事件时,也要指定发到哪个房间,这通过使用
    send()和emit()函数中的room参数来指定。比如,下面是创建广播
    新消息的room message事件处理函数:

    @socketio.on('room message')
    def new_room_message(message_body):
    	emit('message', {'message': current_user.username + ':' + message_body}, 			room=current_user.room)
    

    如果你仅需要对用户进行分组,那么房间是你的最佳选择。命名空
    间是在程序层面上的频道分离。如果我们要在程序中同时实现全局聊
    天、匿名聊天室、房间、私聊,这四类功能对消息的处理各自不同,所
    以我们需要为这四类功能指定不同的命名空间(全局聊天可以使用默认
    的全局命名空间)。在需要分离通信频道时,我们需要根据程序的特点
    来决定方式:仅使用命名空间、仅使用房间或两者结合使用。
    附注
    你可以通过Flask-SocketIO作者Miguel Grinberg提供的这个聊天程序
    https://github.com/miguelgrinberg/Flask-SocketIO-Chat)示例了解关于
    房间的具体实现。
    顺便说一下,基于房间你也可以实现私信/私聊功能。只需要把
    room设为代表某个用户的唯一值,在发送事件时,就只有目标用户的客
    户端才能接收到事件。你可以把这种实现方法理解为“一个人的房间”。
    这个能代表用户的唯一值可以是主键值、username或是Flask-SocketIO附
    加到request对象上代表每个客户端id的session id(request.sid)。
    提示
    如果你使用request.sid作为唯一值,那么需要在User模型中添加一个
    sid字段存储这个值,然后在服务器端的connect事件处理函数中更新这
    个值。

    部分概念和内容来自:《Flask Web开发实战(李辉)》

    https://huyu.info/blog/detail/103

    一个简单的测试项目, 实现大厅聊天和开房间聊天

    https://github.com/SweetShance/weChat

  • 相关阅读:
    《计算学科系统导论》系列路线图
    “知行合一”的高职计算机教学观
    文化的概念
    关于班级博客地址
    一个字符编码处理小程序(一)
    关于计应151/152《软件工程》课程实践的安排
    人人都要学一点系统论
    我与软件工程
    关于本人与本博客
    THUWC前集训9
  • 原文地址:https://www.cnblogs.com/ShanCe/p/14982388.html
Copyright © 2020-2023  润新知