RTU/mimo/plan/websocket_async_cmd_output.md

62 lines
3.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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<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>` 实现多连接推送