使用socketio.emit的服务器无法向React客户端广播GPIO触发事件求助
问题描述
我是第一次使用Stack Overflow,若操作有误敬请谅解。
我尝试多日开发一个基于React的Web应用,实现与树莓派(Raspberry Pi)上LED和开关的交互,采用的架构如下:
- 树莓派4(RPi4)运行Python脚本,通过flask、flask_socketio和gpiozero控制并暴露GPIO接口,已安装Gevent;
- 同一树莓派部署导入socket.io-client的React应用生产构建包。
脚本与Web应用均可正常运行,WebSocket连接正常,客户端可接收连接/断开事件。但当基于物理GPIO开关触发服务器端事件时,对应函数可执行并在服务器打印日志,但socketio.emit命令发送的事件无法被React的useEffect监听器接收。
我已查阅大量在线示例及相关库的文档与API,若能指出问题所在将不胜感激!
React客户端代码(app.js)
import './App.css'; import { io } from 'socket.io-client'; import { useState, useEffect } from 'react'; import { Panel } from './components/Panel'; const socket = io('localhost:5001/', { transports: ['websocket'], cors: { origin: 'http://localhost:3000/', }, }); function App() { const [connected, setConnected] = useState(false); useEffect(() => { socket.on('connect', () => { setConnected(true); }); socket.on('disconnect', () => { setConnected(false); }); socket.on('switch', data => { console.log('Incoming data from socket:'); console.log(data); }); return () => { socket.off('connect'); socket.off('disconnect'); socket.off('switch'); }; }, []); console.log(socket); return ( <div className="App"> <header className="App-header"> <h1>Panel Websocket Interface v2.1</h1> <p>Connected: {'' + connected}</p> <Panel socket={socket} /> </header> </div> ); } export default App;
服务端代码(server.py)
from flask import Flask, request, jsonify from flask_socketio import SocketIO, emit from flask_cors import CORS from gpiozero import LED, Button # GPIO panel definitions LEDS = ["red", "yellow", "green"] LED_PINS = [2,3,4] LED_STATUS = [0,0,0] SWITCHES = ["one"] SWITCH_PINS = [27] SWITCH_STATUS = [0] # setup GPIO redLed=LED(4) yellowLed=LED(3) greenLed=LED(2) switch1=Button(27) redLed.source = switch1 # create a new Flask app app = Flask(__name__) app.config['SECRET_KEY'] = 'secret!' CORS(app,resources={r"/*":{"origins":"*"}}) socketio = SocketIO(app,cors_allowed_origins="*") @app.route("/http-call") def http_call(): """return JSON with string data as the value""" data = {'data':'This text was fetched using an HTTP call to server on render'} return jsonify(data) @socketio.on("connect") def connected(): """event listener when client connects to the server""" print(request.sid) print("client has connected") emit("connect",{"data":f"id: {request.sid} is connected"}) @socketio.on('data') def handle_message(data): """event listener when client types a message""" print("data from the front end: ",str(data)) emit("data",{'data':data,'id':request.sid},broadcast=True) @socketio.on("disconnect") def disconnected(): """event listener when client disconnects to the server""" print("user disconnected") emit("disconnect",f"user {request.sid} disconnected",broadcast=True) # switch handlers def switch1On(): print("Switch 1 on") socketio.emit('switch',{'id':1,'state':1}); def switch1Off(): print("Switch 1 off") socketio.emit('switch',{'id':1,'state':0}); switch1.when_pressed = switch1On switch1.when_released = switch1Off if __name__ == '__main__': socketio.run(app, debug=True,port=5001)
解决方案
问题核心在于:GPIO触发的回调函数(switch1On/switch1Off)是在Flask-SocketIO事件循环外的独立线程中执行的,而socketio.emit操作必须在SocketIO的内部事件循环线程中运行,否则无法正确推送事件给客户端。
直接修复方法
修改服务端的开关处理器,将emit操作包装在socketio.start_background_task中,确保事件发送逻辑在SocketIO的事件循环上下文里执行:
# switch handlers def switch1On(): print("Switch 1 on") socketio.start_background_task( lambda: socketio.emit('switch', {'id':1,'state':1}) ) def switch1Off(): print("Switch 1 off") socketio.start_background_task( lambda: socketio.emit('switch', {'id':1,'state':0}) )
如果需要将事件广播给所有已连接客户端,也可以直接在emit时添加broadcast=True参数:
def switch1On(): print("Switch 1 on") socketio.emit('switch', {'id':1,'state':1}, broadcast=True)
额外检查点
- 客户端连接地址:如果React应用是生产环境部署(比如用Nginx托管),客户端代码中的
io('localhost:5001/')需改为树莓派的实际IP或域名,避免因地址错误导致的事件接收失败。 - 浏览器控制台排查:打开浏览器F12的Console和Network标签页,检查WebSocket连接状态、是否有跨域报错或事件接收失败的日志。
内容的提问来源于stack exchange,提问作者Dave H
相关产品推荐
相关产品推荐

