RTU/mimo/plan/websocket_async_cmd_output.md

3.3 KiB
Raw Blame History

WebSocket 异步命令输出修复方案

问题描述

Web 前端的命令终端页面中,datacenter in 等同步命令能正确显示输出,但 self_ptl 6 等异步命令的输出不会传输到前端。

原因分析

ws_cmd_exec() 通过重定向 STDOUT → pipe → reader 线程的方式捕获命令输出:

ws_cmd_exec() → dup2(pipe, stdout) → p_cmd->func() → dup2(saved_stdout, stdout) → join reader
  • datacenter in: cmd_dc() 直接调用 dc_show_in_signals(),使用 std::cout/printf 同步输出到 stdout → 被 pipe 捕获
  • self_ptl 6: cmd_self_ptl() 仅设置 g_flag[6]=1,立即返回。实际数据经 test_self_check_get()icp67_self_check_get_cb() → ICP67 协议发送 → 远端响应 → self_check_pop_out() 异步回调处理。该回调仅输出 LOG_I 日志,不走 stdout → 不被 pipe 捕获

解决方案

核心思路

在 WebSocket 服务端维护一个 <命令名 → WebSocket 连接 ID> 的 Map 映射,让异步回调可以根据命令名查找到对应的连接 ID将结果推送回前端。

架构设计要点

  1. 跨模块解耦:在 mySystem.h 中声明共享 APIws_set_async_cmd_connws_get_async_cmd_connws_send_binary),各模块只需包含 mySystem.h 即可使用,无需跨模块引用头文件
  2. 读写接口封装:用 ws_set_async_cmd_conn() / ws_get_async_cmd_conn() 取代直接暴露全局变量,内部用 mutex 保护
  3. 多连接支持:使用 std::map<std::string, unsigned long> 以命令名为 key 存储,不同命令互不干扰。同一命令的后续请求会覆盖前一个

修改文件

文件 修改内容
src/system/libweb_server/src/ws_method.cpp std::map + mutex 替代单一全局变量,添加 ws_set/ws_get_async_cmd_conn() 接口
src/system/libweb_server/inc/ws_method.h 移除 extern unsigned long g_async_cmd_conn_id
release/inc/mySystem.h 添加 ws_set_async_cmd_conn()ws_get_async_cmd_conn()ws_send_binary() 跨模块声明
src/system/libself_ptl/src/method.cpp 移除 #include "ws_method.h",改为 #include "mySystem.h",使用 ws_get_async_cmd_conn("self_ptl")
release/src/system/libself_ptl/makefile 移除跨模块包含路径 -I $(SRC_ROOT_DIR)/system/libweb_server/inc

数据流

前端 WebSocket 键入 self_ptl 6 (conn_id=100)
  → ws_recv() 提取命令名 "self_ptl"
  → ws_set_async_cmd_conn("self_ptl", 100)
  → map["self_ptl"] = 100
  → ws_cmd_exec() 返回初始输出 → ws_send_binary(初始输出)

同一时刻另一用户键入 datacenter in (conn_id=200)
  → ws_set_async_cmd_conn("datacenter", 200)
  → map["datacenter"] = 200 (不会覆盖 self_ptl 的条目 ✅)

(异步) ICP67 响应到达 → self_check_pop_out()
  → conn_id = ws_get_async_cmd_conn("self_ptl")  → 100
  → ws_send_binary(100, 格式化结果) → 第一个用户前端显示 ✅

多连接场景

  • 同一命令(如 self_ptl 6)被多个标签页先后执行 → 后续的会覆盖之前的 conn_id → 异步结果只推送给最后一个执行者
  • 不同命令互不干扰,各自独立存储 conn_id
  • 如需进一步优化(如广播到所有标签页),可在 map 中存储 std::vector<unsigned long> 实现多连接推送