You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flask-SocketIO定时广播失效求助:仅连接时Emit功能正常

问题:Flask-SocketIO定时广播无法送达客户端

我想用Flask的flask_socketio实现WebSocket广播功能,目标是服务器每5秒推送新信息,通知客户端重新渲染页面。我建立了Socket连接,并用守护线程尝试每5秒向客户端广播内容,但只有@socketio.on('connect')里的Emit能正常工作,线程中的Emit完全没效果。已经确认连接已建立,首次Emit能更新前端,但服务器的定时广播消息就是送不到前端。

以下是我的代码:

import time
from flask import Flask, render_template
from flask_socketio import SocketIO, emit, join_room, leave_room

from flask_cors import CORS
from flask import request


import threading
import eventlet
from eventlet import wsgi

app = Flask(__name__)
cors = CORS(app)

app.config['SECRET_KEY'] = 'secret_key'
socketio = SocketIO(app, cors_allowed_origins='*')

# Track connected clients
connected_clients = set()

# Define WebSocket event handlers
@socketio.on('connect')
def handle_connect():
    print('Client connected, ID=', request.sid)
    connected_clients.add(request.sid)
    emit('custom_event', 'Hello There!')  # Send a message to the client
    # emit('heartbeat', 'beat')
    
@socketio.on('disconnect')
def handle_disconnect():
    print('Client disconnected')
    connected_clients.remove(request.sid)

heartbeat_msg = ["hdofajosjdof", "I'm still here", "Hey You!"]
# Thread function to send "I'm still here" message every 5 seconds
@socketio.on('heartbeat')
def send_heartbeat():
    i = 0
    while True:
        for client in connected_clients:
            print('sending heartbeat to', client)
            socketio.server.manager.emit('custom_event', i, '/')
            # socketio.emit('custom_event', heartbeat_msg[i%3])
            i += 1
        time.sleep(10)

# Start the heartbeat thread
heartbeat_thread = threading.Thread(target=send_heartbeat)
heartbeat_thread.daemon = True
heartbeat_thread.start()


if __name__ == '__main__':
#     print('Running')
    socketio.run(app, debug=True) 
    # wsgi.server(eventlet.listen(("127.0.0.1", 5000)), app, debug=True)

问题分析与修复方案

核心问题点

  1. 错误的装饰器使用:send_heartbeat被@socketio.on('heartbeat')装饰,这会把它注册成WebSocket事件处理函数,而非普通后台任务函数。直接用线程调用会脱离SocketIO上下文,导致emit无效。
  2. 普通线程与协程环境不兼容:Flask-SocketIO依赖eventlet/gevent协程库,普通threading.Thread无法正确集成到协程环境,会导致消息推送失败。
  3. 阻塞式sleep:time.sleep()会阻塞整个协程线程,影响SocketIO正常通信,应使用SocketIO提供的非阻塞socketio.sleep()。
  4. 广播方式错误:手动遍历客户端集合存在线程安全问题,且没有利用SocketIO原生的广播机制。

修复后的代码

import time
from flask import Flask, render_template
from flask_socketio import SocketIO, emit
from flask_cors import CORS
from flask import request

import eventlet

app = Flask(__name__)
cors = CORS(app)

app.config['SECRET_KEY'] = 'secret_key'
# 明确指定使用eventlet协程
socketio = SocketIO(app, cors_allowed_origins='*', async_mode='eventlet')

# 线程安全的客户端集合
connected_clients = set()

# 客户端连接事件
@socketio.on('connect')
def handle_connect():
    print('Client connected, ID=', request.sid)
    connected_clients.add(request.sid)
    emit('custom_event', 'Hello There!')
    
@socketio.on('disconnect')
def handle_disconnect():
    print('Client disconnected')
    if request.sid in connected_clients:
        connected_clients.remove(request.sid)

heartbeat_msg = ["hdofajosjdof", "I'm still here", "Hey You!"]

# 后台广播任务函数(移除事件装饰器)
def send_heartbeat():
    i = 0
    while True:
        # 利用SocketIO广播功能,一次性推送给所有客户端
        print(f'Broadcasting heartbeat, count: {i}')
        socketio.emit('custom_event', heartbeat_msg[i%3])
        i += 1
        # 使用非阻塞sleep
        socketio.sleep(5)

if __name__ == '__main__':
    # 用SocketIO自带方法启动后台任务,替代普通线程
    socketio.start_background_task(send_heartbeat)
    socketio.run(app, debug=True)

关键修复说明

  • 移除错误装饰器:去掉send_heartbeat上的@socketio.on('heartbeat'),让它成为普通后台任务函数。
  • 使用SocketIO后台任务:用socketio.start_background_task()替代threading.Thread,确保任务运行在协程环境中,能正确调用emit方法。
  • 替换sleep方法:用socketio.sleep()替代time.sleep(),避免阻塞协程线程。
  • 利用原生广播:直接调用socketio.emit()默认会广播给所有连接客户端,无需手动遍历集合,避免线程安全问题。
  • 线程安全集合操作:断开连接时先判断sid是否在集合中,避免KeyError。

内容的提问来源于stack exchange,提问作者ILoveET

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 13:42:48