Flask socketio 不监听事件

Posted

技术标签:

【中文标题】Flask socketio 不监听事件【英文标题】:Flask socketio not listening to events 【发布时间】:2022-01-03 08:03:11 【问题描述】:

我正在使用烧瓶 socketio 构建一个烧瓶应用程序。我在 Ubuntu 20.04 上本地运行它,在 localhost:8000 上使用 gunicorn 和 eventlet。

我的服务器端测试代码如下所示:

$(document).ready(function() 
    // const socketGame = io(window.location.hostname + "/coin")
    var socket = io.connect('http://127.0.0.1:8000');

    socket.on('connect', function() 
        console.log("ok");
        socket.send('User has connected!');
    );

    socket.on('message', function(msg) 
        console.log('Received message');
    );

);

在我的服务器中,我有以下监听器:

import re

from flask import render_template, request, redirect, url_for, jsonify, current_app
from flask_mail import Mail, Message
from flask_socketio import emit, join_room, leave_room, rooms
from flask_login import LoginManager, UserMixin, login_user, login_required, current_user, logout_user
from werkzeug.security import generate_password_hash, check_password_hash

from .. import socketio
from . import games_blueprint
from app.models import *
from app.utilities import sanitizehtml

#socketio chat server
#need to create event for admin broadcasting messages
@socketio.on('message', namespace='/')
def handleMessage(msg):
    print('Message: ' + msg)
    send(msg, broadcast=True)

它应该简单地在终端打印来自客户端的消息并广播回一条消息。但是,从 gunicorn 日志来看,服务器似乎正在从客户端接收事件,而不是将其传递给侦听器:

(vegazbetenv) jveiga@LAPTOP-C6PJKMM6:~/vegazbet$ gunicorn --worker-class eventlet -w 1 wsgi:application
[2021-11-24 21:00:59 -0300] [134633] [INFO] Starting gunicorn 20.1.0
[2021-11-24 21:00:59 -0300] [134633] [INFO] Listening at: http://127.0.0.1:8000 (134633)
[2021-11-24 21:00:59 -0300] [134633] [INFO] Using worker: eventlet
[2021-11-24 21:00:59 -0300] [134635] [INFO] Booting worker with pid: 134635
Server initialized for eventlet.
Server initialized for eventlet.
Server initialized for eventlet.
HYYx0QNgrNr7YodDAAAA: Sending packet OPEN data 'sid': 'HYYx0QNgrNr7YodDAAAA', 'upgrades': ['websocket'], 'pingTimeout': 20000, 'pingInterval': 25000
HYYx0QNgrNr7YodDAAAA: Received packet MESSAGE data 0
HYYx0QNgrNr7YodDAAAA: Sending packet MESSAGE data 0"sid":"q1NjSbTlPRYmT_TYAAAB"
HYYx0QNgrNr7YodDAAAA: Received request to upgrade to websocket
HYYx0QNgrNr7YodDAAAA: Received packet MESSAGE data 2["message","User has connected!"]
received event "message" from q1NjSbTlPRYmT_TYAAAB [/]
HYYx0QNgrNr7YodDAAAA: Upgrade to websocket successful
HYYx0QNgrNr7YodDAAAA: Sending packet PING data None
HYYx0QNgrNr7YodDAAAA: Received packet PONG data 

从日志中可以看出,websocket 连接升级成功,socket-io 服务器接收到来自客户端的消息,它只是没有将它传递给应该监听事件的函数。

澄清一下,我正在使用应用工厂模式运行烧瓶,侦听器代码位于蓝图views.py 文件中,遵循我的应用工厂代码和蓝图:

app/__init__.py

import os

from flask import Flask, jsonify, render_template, request, redirect, url_for
from flask_login import LoginManager, UserMixin, login_user, login_required, current_user, logout_user
from flask_mail import Mail
from flask_socketio import SocketIO
from flask_mongoengine import MongoEngine
from flask_wtf.csrf import CSRFProtect
from celery import Celery


from config import Config
from app.models import *
from config import *


### Flask extension objects instantiation ###
mail = Mail()
csrf = CSRFProtect()
login_manager = LoginManager()
# socketio = SocketIO(engineio_logger=True, logger=True, message_queue=os.getenv('CELERY_BROKER_URL'), cors_allowed_origins="*")
socketio = SocketIO(engineio_logger=True, logger=True, cors_allowed_origins="*")

### Celery instantiation ###
celery = Celery(__name__, broker=Config.CELERY_BROKER_URL, result_backend=Config.RESULT_BACKEND)


### Application Factory ###
def create_app():

    app = Flask(__name__)

    # Configure the flask app instance
    CONFIG_TYPE = os.getenv('CONFIG_TYPE', default='config.DevelopmentConfig')
    app.config.from_object(CONFIG_TYPE)

    #Configure celery
    celery.conf.update(app.config)

    # Initialize flask extension objects
    initialize_extensions(app)

    # Register blueprints
    register_blueprints(app)

    #setup db integration
    configure_db(app)

    # Configure logging
    configure_logging(app)

    # Register error handlers
    register_error_handlers(app)

    #setup db for flask-login
    @login_manager.user_loader
    def load_user(user_id):
        user = User.objects(id=user_id)[0]
        return user

    return app


### Helper Functions ###
def register_blueprints(app):
    from app.auth import auth_blueprint
    from app.main import main_blueprint
    from app.games import games_blueprint
    from app.admin import admin_blueprint

    app.register_blueprint(auth_blueprint, url_prefix='/users')
    app.register_blueprint(main_blueprint)
    app.register_blueprint(games_blueprint, url_prefix='/games')
    app.register_blueprint(admin_blueprint, url_prefix='/admin')

def initialize_extensions(app):
    mail.init_app(app)
    login_manager.init_app(app)
    csrf.init_app(app)
    socketio.init_app(app)

def register_error_handlers(app):

    # 400 - Bad Request
    @app.errorhandler(400)
    def bad_request(e):
        return render_template('400.html'), 400

    # 401 - Unauthorized
    @app.errorhandler(401)
    def bad_request(e):
        return render_template('401.html'), 401

    # 403 - Forbidden
    @app.errorhandler(403)
    def forbidden(e):
        return render_template('403.html'), 403

    # 404 - Page Not Found
    @app.errorhandler(404)
    def page_not_found(e):
        return render_template('404.html'), 404

    # 405 - Method Not Allowed
    @app.errorhandler(405)
    def method_not_allowed(e):
        return render_template('405.html'), 405

    # 500 - Internal Server Error
    @app.errorhandler(500)
    def server_error(e):
        return render_template('500.html'), 500

def configure_logging(app):
    import logging
    from flask.logging import default_handler
    from logging.handlers import RotatingFileHandler

    # Deactivate the default flask logger so that log messages don't get duplicated
    app.logger.removeHandler(default_handler)

    # Create a file handler object
    file_handler = RotatingFileHandler('flaskapp.log', maxBytes=16384, backupCount=20)

    # Set the logging level of the file handler object so that it logs INFO and up
    file_handler.setLevel(logging.INFO)

    # Create a file formatter object
    file_formatter = logging.Formatter('%(asctime)s %(levelname)s: %(message)s [in %(filename)s: %(lineno)d]')

    # Apply the file formatter object to the file handler object
    file_handler.setFormatter(file_formatter)

    # Add file handler object to the logger
    app.logger.addHandler(file_handler)

def configure_db(app):
    db = MongoEngine(app)

app/games/__init__.py

#This bluepint will deal with all site functionality

from flask import Blueprint

games_blueprint = Blueprint('games', __name__, template_folder='templates')

from . import views

关于为什么会发生这种情况以及如何解决它的任何想法?

【问题讨论】:

【参考方案1】:

您的客户端正在寻找socket.on('message', incoming_message_as_a_string),但您的服务器正在发送socket.send('just the string without an event')

试试:

socket.on('connect', function() 
    socket.send('message', 'User has connected!');
);

但我会养成在客户端和服务器上始终使用emit 的习惯:

socket.on('connect', function() 
    console.log("ok");
    socket.emit('message', 'User has connected!');
);

原因是emit 可以发送对象或字符串,而send 只能发送字符串。


更新:

再看你的问题,我不认为你的两个方面是匹配的。发送套接字消息时,第一个参数是“事件名称” - 基本上是一个触发器,它可以让对方有一些需要注意的东西(以便套接字侦听器可以区分不同类型的消息)。

服务器只监听message 事件,然后发送自己的message - 这意味着客户端必须先发送一些东西。通常,这是一条“连接”消息——当客户端连接时它会发送其中一条消息,但服务器端并未监听该事件。它只监听“消息”事件——客户端仅在第一次收到来自服务器的消息事件后发送message 事件。

在服务器端,尝试更改:

@socketio.on('message', namespace='/')
def handleMessage(msg):
    print('Message: ' + msg)
    send(msg, broadcast=True)

到这里:

@socketio.on('connect')
def handleConnect():
    print('Someone connected')
    send('message', 'A user has joined', broadcast=True)

@socketio.on('message')
def handleMessage(msg):
    print('Message: ' + msg)
    send(msg, broadcast=True)

PS - 我删除了命名空间的东西,因为没有它我也能正常工作,我不确定它会如何影响结果。进一步了解后,您可以将其重新添加。

【讨论】:

不幸的是,这不起作用,服务器仍然没有响应事件。我相信问题出在服务器代码上。 感谢您对使用 emit 的提醒 @JoãoA.Veiga 我用其他想法/建议更新了我的答案。 刚刚测试过,结果还是一样。服务器不会向客户端发送任何内容。我开始相信这可能是使用 eventlet 的 flask-socketio 服务器的更深层次的问题。有什么想法吗? 感谢@cssyphus 的洞察力,刚刚开始工作。我会发布一个答案来解释我做了什么【参考方案2】:

不太确定这里的细节,但以下更改使服务器正常工作。

在最初的应用工厂函数中,我在注册蓝图之前调用了 socketio.init_app(app)。我只是把它移到应用工厂的最后一个操作:

def create_app():

    app = Flask(__name__)

    # Configure the flask app instance
    CONFIG_TYPE = os.getenv('CONFIG_TYPE', default='config.DevelopmentConfig')
    app.config.from_object(CONFIG_TYPE)

    #Configure celery
    celery.conf.update(app.config)

    # Register blueprints
    register_blueprints(app)

    #setup db integration
    configure_db(app)

    # Configure logging
    configure_logging(app)

    # Register error handlers
    register_error_handlers(app)
    
    ##########################################
    # HERE
    # Initialize flask extension objects
    initialize_extensions(app)
    ##########################################

    #setup db for flask-login
    @login_manager.user_loader
    def load_user(user_id):
        user = User.objects(id=user_id)[0]
        return user

    return app

我猜测操作顺序会以某种方式阻止 socketio 实际注册事件处理程序。如果任何对 flask-socketio 更有经验的人可以更深入地研究这个问题,我们将不胜感激。

【讨论】:

以上是关于Flask socketio 不监听事件的主要内容,如果未能解决你的问题,请参考以下文章

托管在 Heroku 上的 Flask SocketIO 应用程序不使用 @socketio.on 事件?

使用flask_socketio实现客户端间即时通信

数据库进阶实践-事件监听

用 Python flask_socketio 反应 socket.io-client 不处理事件

Flask 事件处理程序未从 SocketIO 接收事件

Flask-socketio,向另一个命名空间发出事件