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

使用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)

额外检查点

  1. 客户端连接地址:如果React应用是生产环境部署(比如用Nginx托管),客户端代码中的io('localhost:5001/')需改为树莓派的实际IP或域名,避免因地址错误导致的事件接收失败。
  2. 浏览器控制台排查:打开浏览器F12的Console和Network标签页,检查WebSocket连接状态、是否有跨域报错或事件接收失败的日志。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 21:05:17