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

FastAPI WebSocket调用Popen读取设备stdout:终端正常但无法实时推送到客户端

FastAPI WebSocket实时推送串口输出无响应问题解决

问题场景

搭建FastAPI WebSocket服务端与HTML/JS客户端,点击客户端按钮触发服务端执行cat /dev/ttyUSB0读取实时串口设备内容。终端打印输出正常,但WebSocket在程序中断前无任何推送;尝试单次调用process.stdout.readline()并推送有效,但循环推送失效。

核心原因

原代码中for output in process.stdout是同步阻塞式迭代,在FastAPI的异步事件循环中,这个循环会独占线程,导致后续的await websocket.send_text()无法被及时调度执行。事件循环被阻塞后,WebSocket的I/O操作得不到处理,自然看不到实时推送。而单次调用readline()时,执行完成后会让出事件循环,所以能成功推送一次。

解决方案

方案1:异步Subprocess(推荐)

使用asyncio的异步子进程模块,全程异步读取输出,不会阻塞事件循环:

import asyncio
from fastapi import WebSocket
from subprocess import shlex

async def run_command(websocket: WebSocket):
    print("running command")
    command = "cat /dev/ttyUSB0"
    args = shlex.split(command)

    # 创建异步子进程
    process = await asyncio.create_subprocess_exec(
        *args,
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.STDOUT
    )

    # 异步迭代输出流
    async for output in process.stdout:
        await websocket.send_text(output.decode().strip())
    
    await process.wait()

方案2:线程+队列(兼容同步操作)

如果必须使用同步subprocess.Popen,可以把阻塞的读取操作放到单独线程,用队列传递数据,异步从队列取数推送:

import asyncio
from fastapi import WebSocket
from subprocess import Popen, PIPE, STDOUT
import shlex
from threading import Thread
from queue import Queue, Empty

async def run_command(websocket: WebSocket):
    print("running command")
    command = "cat /dev/ttyUSB0"
    args = shlex.split(command)
    output_queue = Queue()

    # 线程函数:同步读取输出并放入队列
    def read_device_output(process, queue):
        for line in iter(process.stdout.readline, b''):
            queue.put(line)
        process.stdout.close()

    with Popen(args, stdout=PIPE, stderr=STDOUT, bufsize=1) as process:
        # 启动线程读取串口输出
        read_thread = Thread(target=read_device_output, args=(process, output_queue))
        read_thread.start()

        # 异步循环从队列取数据推送
        while read_thread.is_alive() or not output_queue.empty():
            try:
                # 非阻塞取数,超时0.1秒避免占用事件循环
                line = output_queue.get(timeout=0.1)
                await websocket.send_text(line.decode().strip())
            except Empty:
                # 让出事件循环给其他任务
                await asyncio.sleep(0.01)
        read_thread.join()

客户端优化(可选)

如果需要把推送内容显示到页面,修改客户端onmessage函数:

ws.onmessage = function(event) {
    console.log(event.data);
    const outputList = document.getElementById('output');
    const newItem = document.createElement('li');
    newItem.textContent = event.data;
    outputList.appendChild(newItem);
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 18:20:20