使用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'}
问题分析与解决方案
核心问题
- 输出缓冲导致批量发送:Flask默认会缓冲响应内容,每秒生成的小数据被累积,直到缓冲区满才一次性推送给客户端,形成10秒批量显示的现象。
- SSE逻辑无效:服务端仅循环发送最后一条旧消息,没有检测新消息的机制,即使有新消息也不会主动推送,反而重复发送旧内容。
- 数据存储不安全且格式错误:用
eval读取文件存在代码注入风险,且追加消息的格式会导致下次读取时JSON结构损坏。 - 冗余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
相关产品推荐
相关产品推荐

