# 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` 中声明共享 API(`ws_set_async_cmd_conn`、`ws_get_async_cmd_conn`、`ws_send_binary`),各模块只需包含 `mySystem.h` 即可使用,无需跨模块引用头文件 2. **读写接口封装**:用 `ws_set_async_cmd_conn()` / `ws_get_async_cmd_conn()` 取代直接暴露全局变量,内部用 mutex 保护 3. **多连接支持**:使用 `std::map` 以命令名为 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` 实现多连接推送