62 lines
3.3 KiB
Markdown
62 lines
3.3 KiB
Markdown
# 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>` 实现多连接推送 |