Flask+SocketIO问题:无法推送外部设备WebSocket消息至异步HTML页面
Flask SocketIO 应用问题排查与修复
问题概述
- 外部设备(IP:10.0.0.144)向部署在10.0.0.218:8089的应用发送WebSocket消息时,控制台返回400错误(请求语法错误);
- 收到的消息无法推送到异步HTML页面,页面无更新。
消息已存入列表,仅关注来自10.0.0.144的消息,不确定当前实现是否最优。
相关代码
Flask 后端代码
from flask import Flask, render_template from flask_socketio import * from threading import Thread, Event import socket import json app = Flask(__name__) socketio = SocketIO(app) # 存储消息的共享变量 messages = [] thread = Thread() thread_stop_event = Event() @app.route('/') def index(): return render_template('index.html', messages=messages) @socketio.on('connect', namespace='/test') def test_connect(): # 需要全局线程对象的可见性 global thread print('客户端已连接') # 仅在线程未启动时启动消息推送线程 if not thread.is_alive(): print("启动线程") thread = socketio.start_background_task(start_socket_server) def start_socket_server(host='10.0.0.218', port=8089): server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_socket.bind((host, port)) server_socket.listen(5) print(f"服务器监听 {host}:{port}") while True: client_socket, addr = server_socket.accept() message = client_socket.recv(1024).decode() if message: # print(f"{addr, message}") messages.append(message) # 向所有连接的客户端发送消息 socketio.emit('new_message', {'message': message}) client_socket.close() if __name__ == "__main__": # 在独立线程中启动socket服务器 socket_thread = Thread(target=start_socket_server) socket_thread.start() # 启动带SocketIO的Flask应用 socketio.run(app, allow_unsafe_werkzeug=True)
前端JS代码
$(document).ready(function(){ // 连接socket服务器 var socket = io.connect() var messages_received = []; // 接收服务器消息 socket.on('new_message', function(msg) { console.log("收到消息" + msg.number); // 仅保留最近10条消息 if (messages_received.length >= 10){ messages_received.shift() } messages_received.push(msg.number); messages_string = ''; for (var i = 0; i < messages_received.length; i++){ messages_string += '<p>' + messages_received[i].toString() + '</p>'; } $('#log').html(messages_string); }); });
控制台输出示例
* Running on http://10.0.0.218:8089 Press CTRL+C to quit * Restarting with stat Server initialized for threading. Werkzeug appears to be used in a production deployment. Consider switching to a production web server instead. * Debugger is active! * Debugger PIN: 123-630-169 RMoYImBp0_FvWs_lAAAA: Sending packet OPEN data {'sid': 'RMoYImBp0_FvWs_lAAAA', 'upgrades': ['websocket'], 'pingTimeout': 20000, 'pingInterval': 25000} 10.0.0.218 - - [31/Jul/2024 11:54:54] "GET /socket.io/?EIO=4&transport=polling&t=P49W5nO HTTP/1.1" 200 - RMoYImBp0_FvWs_lAAAA: Received packet MESSAGE data 0/test, RMoYImBp0_FvWs_lAAAA: Received request to upgrade to websocket emitting event "new_message" to all [/] RMoYImBp0_FvWs_lAAAA: Sending packet MESSAGE data 0/test,{"sid":"Fzb-QYWF3SFFBr4EAAAB"} 10.0.0.218 - - [31/Jul/2024 11:54:54] "POST /socket.io/?EIO=4&transport=polling&t=P49W64r&sid=RMoYImBp0_FvWs_lAAAA HTTP/1.1" 200 - 10.0.0.218 - - [31/Jul/2024 11:54:54] "GET /socket.io/?EIO=4&transport=polling&t=P49W64s&sid=RMoYImBp0_FvWs_lAAAA HTTP/1.1" 200 - RMoYImBp0_FvWs_lAAAA: Upgrade to websocket successful Client connected Starting Thread emitting event "new_message" to all [/] emitting event "new_message" to all [/] 10.0.0.144 - - [31/Jul/2024 11:54:59] code 400, message Bad request syntax ('{"ts":1722441301,"dio":{"dio1":{"in":true},"dio2":{"in":true},"dio3":{"label":"Link","in":true},"dio4":{"in":true}},"ain":{"ain1":{"voltage":12},"ain2":{"voltage":2},"ain3":{"voltage":10},"ain4":{"voltage":0}}}') 10.0.0.144 - - [31/Jul/2024 11:54:59] "{"ts":1722441301,"dio":{"dio1":{"in":true},"dio2":{"in":true},"dio3":{"label":"Link","in":true},"dio4":{"in":true}},"ain":{"ain1":{"voltage":12},"ain2":{"voltage":2},"ain3":{"voltage":10},"ain4":{"voltage":0}}}" HTTPStatus.BAD_REQUEST - emitting event "new_message" to all [/] emitting event "new_message" to all [/] emitting event "new_message" to all [/] 10.0.0.218 - - [31/Jul/2024 11:55:05] "GET /socket.io/?EIO=4&transport=websocket&sid=RMoYImBp0_FvWs_lAAAA HTTP/1.1" 200 - 10.0.0.218 - - [31/Jul/2024 11:55:05] "GET / HTTP/1.1" 200 - Client disconnected 10.0.0.218 - - [31/Jul/2024 11:55:05] "GET /static/application.js HTTP/1.1" 304 -
HTML页面代码
<!DOCTYPE html> <html> <head> <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script> <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/socket.io/4.4.1/socket.io.min.js"></script> <script src="static/application.js"></script> <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> <meta name="viewport" content="width=device-width, initial-scale=1"> </head> <body> <div class="container"> <div class="jumbotron"> <h2>异步Flask通信</h2> <p>来自现场设备的WebSocket消息。</p> </div> </div> </div> <div class="container" id="content"> <div class="row"> <p>异步页面更新将显示在这里:</p> <h3>消息:</h3> <div id="log"> </div> <!-- /#log --> </div> </div> </body> </html>
问题修复与优化方案
1. 400错误修复
控制台的400错误是因为你同时启动了两个服务抢占8089端口:
- Flask-SocketIO在8089端口提供基于HTTP/WebSocket协议的SocketIO服务
- 自定义的
start_socket_server也绑定8089端口,监听原始TCP连接
外部设备直接发送JSON字符串到8089端口,被Flask的HTTP服务器接收(而非你的自定义TCP服务),因不符合HTTP请求格式被判定为无效请求,返回400错误。
修复方案二选一:
- 给自定义TCP服务更换端口(比如8090),让外部设备连接该端口;
- 移除自定义TCP服务,直接用Flask-SocketIO接收设备消息(推荐,简化架构)。
推荐方案的代码修改:
@socketio.on('device_message', namespace='/test') def handle_device_message(message): # 验证发送方IP from flask import request client_ip = request.remote_addr if client_ip == '10.0.0.144': messages.append(message) socketio.emit('new_message', {'message': message}, namespace='/test')
外部设备需用SocketIO客户端发送device_message事件,而非直接发送JSON字符串。
2. 页面无更新修复
当前代码存在两个核心问题:
- 后端
socketio.emit未指定命名空间,前端连接默认命名空间,但后端connect事件绑定在/test命名空间,两者不匹配; - 前端JS尝试访问
msg.number,但后端发送的是{'message': message},字段名不匹配。
修复步骤:
- 前端连接指定命名空间:
var socket = io.connect('/test');
- 前端修正消息处理逻辑:
socket.on('new_message', function(msg) { console.log("收到消息:" + msg.message); if (messages_received.length >= 10){ messages_received.shift() } messages_received.push(msg.message); messages_string = ''; for (var i = 0; i < messages_received.length; i++){ messages_string += '<p>' + messages_received[i].toString() + '</p>'; } $('#log').html(messages_string); });
- 后端发送消息时指定命名空间:
socketio.emit('new_message', {'message': message}, namespace='/test')
最优实现方案
- 移除自定义TCP服务器,完全基于Flask-SocketIO实现设备消息接收和页面推送,减少端口冲突与架构复杂度;
- 保留设备IP验证逻辑,确保仅处理来自10.0.0.144的消息;
- 使用SocketIO命名空间隔离设备连接与前端页面连接;
- 给
messages列表添加线程锁(threading.Lock),避免多线程访问时的并发冲突。
内容的提问来源于stack exchange,提问作者killedbydeath
相关产品推荐
相关产品推荐

