使用 Flask-SocketIO 实现私聊:通过flask-socketio中的sid给指定的客户端发送消息,对方接收不到

首先说声抱歉,在上一篇帖子中,我没有把问题和代码描述清楚,现在我把两个不同版本的代码,发出来,大佬们帮我看下是因为什么(本来是想把运行结果截图也上传的,但是网站提示新用户只能上传一张截图)

版本一:不借助登录,直接将username传递给后端,保存用户的sid

前端代码如下:

<html>
<head>
    <title>Chat Room</title>
    <br>
    <br>
    <h3>模拟登录</h3>
    Username: <input type="text" id="username" autocomplete="off">
    <button id="login">Login</button>
    <br>
    <br>

    <div id="send_msg_area" style="display: none">
        <h3>发送消息</h3>
        recipient: <input type="text" id="recipient">
        sender : <input type="text" id="sender">
        Message: <input type="text" id="message">
        <button id="send">Send</button>
    </div>

</head>
<body>
<script type="text/javascript" src="{{ url_for('static', filename='js/jquery-3.5.1.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/socket.io.js') }}"></script>
<script type="text/javascript">
    $(document).ready(function () {
        var socket = io('http://127.0.0.1:9000');

        $('#login').on('click', function () {
            if ($('#username').val() === '') {
                alert('用户名不能为空');
            } else {
                socket.emit('store_sid', {'username': $('#username').val()});
            }
        });

        $('#send').on('click', function () {
            socket.emit('new_message', {
                'recipient': $('#recipient').val(),
                'sender': $('#sender').val(),
                'message': $('#message').val()
            });
        });

        socket.on('store_resp', function (data) {
            if (data.code === 200) {
                $('#send_msg_area').show();
            }
        });

        socket.on('message_resp', function (data) {
            alert(data.message);
        });
    });
</script>
</body>
</html>


后端代码如下:

from flask import Flask, render_template, request
from flask_socketio import SocketIO, emit

app = Flask(__name__)
app.config["SECRET_KEY"] = "secret key"

socketio = SocketIO(app, cors_allowed_origins='*')


all_sid = {}


@app.route('/chat')
def chat():
    return render_template('chat.html')


@socketio.on('store_sid')
def store_sid(data):
    all_sid[data['username']] = request.sid
    print('username added!      all_sid: ', all_sid)
    emit('store_resp', {'code': 200})


@socketio.on('new_message')
def new_message(data):
    recipient = data['recipient']
    sender = data['sender']
    message = data['message']
    print(all_sid[recipient])
    emit('message_resp', {'message': message}, room=all_sid[recipient])


if __name__ == "__main__":
    socketio.run(app, host="0.0.0.0", port=9000, debug=True)

说明:通过这种方式是可以实现利用socketio转发消息的功能

版本二:在版本一的基础上,加上登录功能,登录之后,将登录成功的username传递给后端,保存其sid

前端代码如下:
登录页面代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>demo1-login</title>
</head>
<body>

<h3>模拟登录</h3>
Username: <input type="text" id="username" autocomplete="off">
<button id="login">Login</button>

</body>
<script type="text/javascript" src="{{ url_for('static', filename='js/jquery-3.5.1.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/socket.io.js') }}"></script>
<script type="text/javascript">
    var socket = io('http://127.0.0.1:2323');
    $('#login').click(function () {
        if ($('#username').val() === ''){
            alert('用户名不能为空')
        } else{
            $.ajax({
            url: '/login',
            type: 'POST',
            contentType: 'application/json',
            data: JSON.stringify({
                username: $('#username').val(),
            }),
            success: function (data) {
                if (data.code === 200) {
                    socket.emit('store_sid', {'username': data.username});
                    window.location = '/chat'
                }
            },
            error: function (data) {
                console.log(data.code)
            }
        })
        }
    })
</script>
</html>

发送消息页面:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>demo1-chat</title>
</head>
<body>

    <h3>模拟转发聊天</h3>
    <h3>hello, {{ username }}</h3>
    <a href="/login">重新登录</a><br><br>

    recipient: <input type="text" id="recipient">
    sender: <input type="text" id="sender">
    message: <input type="text" id="message">
    <button id="send">Send</button>

</body>
<script type="text/javascript" src="{{ url_for('static', filename='js/jquery-3.5.1.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/socket.io.js') }}"></script>

<script type="text/javascript">
    var socket = io('http://127.0.0.1:2323');

    $('#send').on('click', function () {
        socket.emit('new_message', {
            'recipient': $('#recipient').val(),
            'sender': $('#sender').val(),
            'message': $('#message').val()
        })
    });

    socket.on('message_resp', function (data) {
        console.log(data);
        alert(data.new_message)
    })
</script>
</html>

后端代码如下:

from flask import Flask, render_template, request, jsonify, session
from flask_socketio import SocketIO, emit

app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret key'

socketio = SocketIO(app, cors_allowed_origins='*')


all_sid = {}


@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.json.get('username')
        if username:
            session['username'] = username
            return jsonify({'code': 200, 'username': username})
        return jsonify({'code': 500})
    return render_template('login.html')


@app.route('/chat')
def chat():
    return render_template('chat.html', username=session['username'])


@socketio.on('store_sid')
def store_sid(data):
    all_sid[data['username']] = request.sid
    print('username added!      all_sid: ', all_sid)


@socketio.on('new_message')
def new_message(data):
    recipient = data['recipient']
    sender = data['sender']
    message = data['message']
    print('message: ', message)
    emit('message_resp', {'message': message}, room=all_sid[recipient])


if __name__ == '__main__':
    socketio.run(app, host='0.0.0.0', port=2323, debug=True)

运行结果截图如下:

说明:通过IED的打印可以看到,后端是已将收到了前端发送的message内容,但是前端并没有alert弹窗,这就很奇怪,不知道是怎么回事。有没有大佬帮忙看看是因为什么造成的

环境版本:
Flask-SocketIO==4.3.1
Python==3.7.8

没发现代码有什么问题,建议在 new_message 函数内打印出来 recipientall_sid 以及 all_sid[recipient] 看看对应的值是不是正确。再看下浏览器控制台有没有报错。如果都正确的话,可以考虑把代码上传到 GitHub,这样我可以运行调试下。

P.S. 更新同一个问题不用创建新的主题,可以直接编辑更新旧主题(点主题下面的铅笔图标)。有问题直接在论坛回复就行,不用发微信……

好的辉哥,以后我会注意的,代码已经上传到GitHub,烦劳您帮忙看下,谢谢,地址:https://github.com/emmm2333/test_private_chat

调试了很久也不行,也许是 Flask-SocketIO 的 bug……我明天再试试看。

好的,辉哥,辛苦您了,您费心了,谢谢

这个主要问题是,如何指定转发给某人,比如:A用户要给B用户发起一个视频邀请或者远程控制的请求。

辉哥,我把store_sid的socketio事件挪到chat页面,就可以了(不在login页面去emit,在chat页面去emit),这是因为什么?【还是没有达到我想要的效果,我是想在login成功的时候,就存储sid,后续转发去找到相应sid】
如果是因为socketio每次请求服务端发送的sid不一样,那么就太局限了吧,那么这就只能在转发请求之前,去存储每个客户端的sid(保证这次存储的sid提供给紧接着下次的转发请求)。有点不切实际,而且,如果sid一直在变,emit是怎么找到对应的sid的呢

我猜测有可能是因为 chat 页面重新创建了 socket 对象,所以就重新创建了连接,然后导致 sid 产生变化(从终端输出看的确产生了变化)。如果是这样的话,我理解大概是建立连接以后在这个会话内这个 sid 是固定的。

sid 是需要存到数据库的,或许可以在每次建立连接后更新用户存在数据库的 sid。具体怎么把用户匹配到对应的 sid,我晚上再研究一下。

好的,谢谢辉哥

不用客气。我想到一个更简单的实现方式,这是我在开发书里的 CatChat 项目时的私聊实现方式,后来因为项目弄得太复杂久删掉了。大概是这样实现,在监听 connect 的回调函数里把当前用户加入到自己用户名对应的 room:

from flask_login import current_user  # 这里用了 Flask-Login 扩展

@socketio.on('connect')
def connect():
    join_room(current_user.username)  # 每个用户都在连接时加入到自己用户名命名的 room

然后在发送私聊消息时,再把发消息的用户加入到对方的 room 里,然后 emit 的时候直接把 room 指定为对方的用户名即可。

@socketio.on('new_message')
def new_message(data):
    recipient = data['recipient']
    message = data['message']
    join_room(recipient)  # 加入到对方用户名的 room
    emit('message_resp', {'message': message}, room=recipient)

如果你想(一般是自己和对方的消息记录里都显示)让自己不收到私信的话,可以在客户端接收消息的函数里验证一下过滤掉即可。

通过 request.sid 的形式实现也类似这样,在 connect 回调函数里对建立连接的当前用户的 sid 进行更新:

from flask_login import current_user

@socketio.on('connect')
def connect():
    current_user.sid = request.sid
    db.session.commit()
1 Like