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

使用Flask与JavaScript实现SSE时的延迟与重复消息问题

问题描述

我正在开发一个基于Flask后端、JavaScript前端的Web应用,采用Server-Sent Events(SSE)实现实时数据更新。但目前遇到了意外延迟与重复消息显示问题:每10秒会一次性显示10条消息,而非每秒显示一条。


客户端JavaScript代码

<script>
    var userId = {{ user_id|tojson }};
    const eventSource = new EventSource("/stream");
    eventSource.onmessage = function(event) {
        const message = JSON.parse(event.data);
        const messageContent = `<div class="${message.me === userId ? 'message me' : 'message'}">
                                    <div class="bubble">${message.text}</div>
                                    <div class="time">${message.time}</div>
                                </div>`;
        document.querySelector(".message-content").innerHTML += messageContent;
    };

    function sendMessage(event) {
        event.preventDefault();
        const input = document.querySelector(".message-form input[type='text']");
        const message = input.value.trim();
        if (message !== "") {
            fetch("/send_message", {
                method: "POST",
                headers: {
                    "Content-Type": "application/json"
                },
                body: JSON.stringify({ message: message })
            })
            .then(response => {
                if (!response.ok) {
                    throw new Error("Failed to send message");
                }
                input.value = ""; // Clear input field after sending message
            })
            .catch(error => console.error(error));
        }
    }
</script>

服务端Flask代码(示例)

with open('msg','r') as file:
    messages = eval(file.read() + ']')
@app.route('/stream')
def stream():
    def y():
        while True:
            yield 'data: ' + dumps(messages[-1]) + '\n\n'
            sleep(1)
    return Response(y(), mimetype='text/event-stream')
@app.route('/send_message', methods=['POST'])
def send_message():
    message = {'text':request.json.get('message'),'time':datetime.datetime.now().strftime("%H:%M:%S"),'me':current_user.id}
    messages.append(message)
    with open('msg','a') as file:
        file.write(str(message) + ',')
    socketio.emit('new_message', message, broadcast=True)
    return jsonify({'status': 'success'})

msg文件内容

[{'text': 'Welcome', 'time': '04:32:13', 'me': 'sadra'}

问题分析与解决方案

核心问题

  1. 输出缓冲导致批量发送:Flask默认会缓冲响应内容,每秒生成的小数据被累积,直到缓冲区满才一次性推送给客户端,形成10秒批量显示的现象。
  2. SSE逻辑无效:服务端仅循环发送最后一条旧消息,没有检测新消息的机制,即使有新消息也不会主动推送,反而重复发送旧内容。
  3. 数据存储不安全且格式错误:用eval读取文件存在代码注入风险,且追加消息的格式会导致下次读取时JSON结构损坏。
  4. 冗余SocketIO调用:代码混用SSE与SocketIO,但客户端未监听SocketIO事件,属于无效代码。

修复步骤

1. 修复SSE缓冲与推送逻辑

禁用响应缓冲,添加新消息检测机制,确保实时推送新内容:

from flask import Response, stream_with_context
import json
from datetime import datetime
import time

# 初始化消息列表(替换原eval逻辑)
try:
    with open('msg.json', 'r') as file:
        messages = json.load(file)
except (FileNotFoundError, json.JSONDecodeError):
    messages = []

@app.route('/stream')
def stream():
    def generate():
        last_msg_count = len(messages)
        while True:
            # 检测新消息
            if len(messages) > last_msg_count:
                for msg in messages[last_msg_count:]:
                    yield f'data: {json.dumps(msg)}\n\n'
                last_msg_count = len(messages)
            # 发送空注释触发刷新,避免缓冲
            yield ':\n\n'
            time.sleep(1)
    # 设置头部禁用缓冲
    return Response(
        stream_with_context(generate()),
        mimetype='text/event-stream',
        headers={
            'Cache-Control': 'no-cache',
            'Connection': 'keep-alive',
            'X-Accel-Buffering': 'no'  # 适配反向代理(如Nginx)
        }
    )

2. 修复消息存储逻辑

改用标准JSON格式存储,替换不安全的eval:

@app.route('/send_message', methods=['POST'])
def send_message():
    message = {
        'text': request.json.get('message'),
        'time': datetime.now().strftime("%H:%M:%S"),
        'me': current_user.id
    }
    messages.append(message)
    # 写入完整JSON数组,避免格式错误
    with open('msg.json', 'w') as file:
        json.dump(messages, file)
    # 移除冗余SocketIO调用(若不需要同时使用SocketIO)
    return jsonify({'status': 'success'})

将原msg文件重命名为msg.json,初始内容改为标准JSON:

[{"text": "Welcome", "time": "04:32:13", "me": "sadra"}]

3. 客户端优化(可选)

添加SSE错误处理,提升稳定性:

<script>
    var userId = {{ user_id|tojson }};
    const eventSource = new EventSource("/stream");
    
    eventSource.onmessage = function(event) {
        const message = JSON.parse(event.data);
        // 过滤空注释消息
        if (!message.text) return;
        const messageContent = `<div class="${message.me === userId ? 'message me' : 'message'}">
                                    <div class="bubble">${message.text}</div>
                                    <div class="time">${message.time}</div>
                                </div>`;
        document.querySelector(".message-content").innerHTML += messageContent;
    };

    // 连接错误处理与重连
    eventSource.onerror = function(error) {
        console.error('SSE连接异常:', error);
        setTimeout(() => window.location.reload(), 5000);
    };

    function sendMessage(event) {
        event.preventDefault();
        const input = document.querySelector(".message-form input[type='text']");
        const message = input.value.trim();
        if (message !== "") {
            fetch("/send_message", {
                method: "POST",
                headers: {
                    "Content-Type": "application/json"
                },
                body: JSON.stringify({ message: message })
            })
            .then(response => {
                if (!response.ok) throw new Error("消息发送失败");
                input.value = "";
            })
            .catch(error => console.error(error));
        }
    }
</script>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 01:15:36