RTU/claude/工程/libweb_server模块分析.md

218 lines
9.1 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.

# libweb_server 模块分析
**日期**2026-06-10
---
## 1. 模块概览
`libweb_server` 是 RTU 的嵌入式 Web 服务器模块(`app_web_server` 线程),属于系统层的第 7 号线程。基于 Mongoose v7.21,提供 HTTP 静态文件服务 + WebSocket 实时数据通道。
### 目录结构
```
src/system/libweb_server/
├── inc/
│ ├── web_server.h # 对外接口app_web_server_init1/2, app_web_server
│ └── ws_method.h # WebSocket 消息处理方法声明
└── src/
├── web_server.cpp # HTTP/WS 服务器 + mongoose 事件循环
└── ws_method.cpp # WebSocket 命令解析 + JSON 数据推送
```
---
## 2. 架构
### 2.1 线程模型
```
┌─────────────────────────────────────────────────┐
│ app_web_server 线程 (RTU 9号线程) │
│ │
│ init1: web_server_init() │
│ → mg_mgr_init + mg_http_listen(8000) │
│ → pthread_create → web_server_run (独立线程) │
│ │
│ init2: (空) │
│ │
│ fun_cb: 事件循环 (3个定时器) │
│ EV_TIMER3 → ws_task() (每秒推送数据) │
└─────────────────────────────────────────────────┘
│ g_ws_conns (连接列表) + g_ws_sessions (会话资源)
┌─────────────────────────────────────────────────┐
│ web_server_run 线程 (mongoose 事件线程) │
│ │
│ while(1) mg_mgr_poll(500ms) │
│ → web_server_task() 回调 │
│ MG_EV_HTTP_MSG → WS升级 / 静态文件 │
│ MG_EV_WS_MSG → 接收命令 │
│ MG_EV_CLOSE → 断连清理 │
└─────────────────────────────────────────────────┘
```
### 2.2 会话隔离模型
每个 WebSocket 连接拥有独立的 `stru_ws_session`,包含自己的五类信号集。连接建立时开辟、断开时释放。
```
客户端A (WebSocket) ─→ session_A: {out_signals_A, in_signals_A, yk_signals_A, ...}
客户端B (WebSocket) ─→ session_B: {out_signals_B, in_signals_B, yk_signals_B, ...}
```
`ws_task()` 遍历所有 session为每个 session 构建独立的 JSON 并发送到对应连接。
### 2.3 数据流
```
浏览器 ←── HTTP ──→ mongoose ←── 静态文件 (web_root/)
浏览器 ←── WS ────→ mongoose ←── JSON 命令/数据 ←── ws_method.cpp
DataCenter
```
---
## 3. web_server.cpp 核心逻辑
### 3.1 全局状态
```cpp
g_web_root 静态文件根目录(进程目录 + "web_root"
mgr mongoose 事件管理器
g_ws_conns vector<mg_connection*> 活跃 WebSocket 连接列表
g_ws_conns_mutex pthread 互斥锁,保护 g_ws_conns
g_ws_sessions map<conn_id, stru_ws_session> 每连接独立信号资源(ws_method.cpp
g_ws_session_mutex pthread 互斥锁,保护 g_ws_sessions
```
### 3.2 事件处理 (web_server_task)
| 事件 | 处理 |
|------|------|
| `MG_EV_HTTP_MSG` | URI=`/ws` → `mg_ws_upgrade()` 升级为 WebSocket其他 → `mg_http_serve_dir()` 静态文件 |
| `MG_EV_WS_MSG` | 将连接加入 `g_ws_conns`,消息经 `std::string` 安全复制后传给 `ws_recv()` |
| `MG_EV_WS_CTL` | CLOSE 帧 → 调用 `ws_session_destroy()` + 从 `g_ws_conns` 移除 |
| `MG_EV_CLOSE` | 调用 `ws_session_destroy()` + 从 `g_ws_conns` 移除 |
### 3.3 ws_send_all / ws_send_one
- `ws_send_all()`:遍历 `g_ws_conns` 广播,跳过死连接
- `ws_send_one(conn_id, ...)`:按 `c->id` 精确定位单连接发送
均加 `g_ws_conns_mutex` 锁保护。
---
## 4. ws_method.cpp 命令处理
### 4.1 会话结构
```cpp
struct stru_ws_session {
vector<stru_ws_signal> out_signals; // 本连接订阅的遥信
vector<stru_ws_signal> in_signals; // 本连接订阅的遥测
vector<stru_ws_signal> yk_signals; // 本连接订阅的遥控
vector<stru_ws_signal> ao_signals; // 本连接订阅的定值
vector<stru_ws_signal> param_signals; // 本连接订阅的参数
};
```
全局 `g_ws_sessions: map<conn_id, stru_ws_session>` 管理所有连接的独立资源。`g_ws_session_mutex` 保护。
### 4.2 信号模型
每个连接 `add` 信号时仅影响自己的 session不同客户端之间完全隔离。
| 类型 | session 字段 | 支持操作 |
|------|-------------|---------|
| out (遥信) | `session.out_signals` | add / del / set |
| in (遥测输入) | `session.in_signals` | add / del |
| yk (遥控) | `session.yk_signals` | add / del / set (含 SBO) |
| ao (定值) | `session.ao_signals` | add / del / set (含 SBO) |
| param (参数) | `session.param_signals` | add / del / set (含 SBO, 多定值区) |
### 4.2 WebSocket 上行命令格式 (JSON)
```json
{
"curd": "add|del|set",
"signal_type": "out|in|yk|ao|param",
"saddr": "st.0",
"signal_data": "1.5",
"setting_zone": "0"
}
```
### 4.3 下行数据格式 (JSON)
每秒 (EV_TIMER3) 推送完整快照,五类信号分数组。修复后仅在值变化时推送。
### 4.4 SBO 控制流程
遥控/定值/参数支持 `DIRECT_NORMAL`(直接执行)和 `SBO_NORMAL`(选择-执行两步),通过 DataCenter 的 `dc_signal_yk_set_status` / `dc_signal_ao_set_val` / `dc_signal_param_set_val` 执行。
---
## 5. 缺陷与修复
### 5.1 缺陷清单(修复前)
| # | 严重度 | 位置 | 问题 |
|---|--------|------|------|
| 1 | 严重 | web_server.cpp L14 | `p_conn` 单指针,仅支持一个 WS 客户端 |
| 2 | 严重 | web_server.cpp | 无 `MG_EV_CLOSE` 处理,断连后悬空指针 |
| 3 | 严重 | web_server.cpp | `p_conn` 无锁保护,多线程竞态 |
| 4 | 严重 | web_server.cpp L66, ws_method.cpp L519 | `mg_str` 非 null-terminated 传给 `printf`/`cJSON_Parse`UB |
| 5 | 高危 | web_server.cpp L66 | 调试 `printf` 遗留 |
| 6 | 高危 | ws_method.cpp L386 | SBO `task_sleep_ms(1000)` 阻塞事件循环 |
| 7 | 中危 | — | 无心跳保活机制 |
| 8 | 中危 | ws_method.cpp | 每秒全量推送,数据不变也发送 |
| 9 | 中危 | ws_method.cpp L400,L502 | `LOG_E("%d")` 缺少对应参数 |
| 10 | 中危 | web_server.cpp L22 | `ws_send` 未校验 `is_websocket`/`is_draining` |
| 11 | 严重 | ws_method.cpp L21-25 | 多客户端共享同一套全局信号资源,客户端之间信号干扰 |
### 5.2 修复措施
**第一轮2026-06-10**
| # | 修复 |
|---|------|
| 1 | `p_conn``g_ws_conns: vector<mg_connection*>`,遍历广播 |
| 2 | 新增 `MG_EV_CLOSE` + `MG_EV_WS_CTL(CLOSE)` 从列表移除 |
| 3 | 新增 `pthread_mutex_t g_ws_mutex`,列表读写前加锁 |
| 4 | `std::string(wm->data.buf, wm->data.len)` 安全复制后再用 |
| 5 | `printf``LOG_I` |
| 6 | 删除 `task_sleep_ms(1000)` |
| 7 | 当前 500ms poll 周期可替代心跳mg_timer 需配合 wakeup_init 略复杂暂不引入 |
| 8 | `stru_ws_signal` 新增 `last_val``ws_task()` 仅在值变化时发送 |
| 9 | 补全 `p_signal->ctrl_type` 参数 |
| 10 | `ws_send()` 循环中增加 `c->is_websocket && !c->is_draining` 检查 |
**第二轮2026-06-10会话隔离重构**
| # | 修复 |
|---|------|
| 11 | 引入 `stru_ws_session` 每连接独立信号集,全局 `g_ws_sessions: map<conn_id, session>` 管理 |
| — | `ws_recv(c, ...)` 增加连接参数,操作仅影响对应 session |
| — | `ws_task()` 遍历 sessions为每个连接构建独立 JSON → `ws_send_one(conn_id, ...)` |
| — | `ws_session_destroy(c)``MG_EV_CLOSE`/`WS_CTL(CLOSE)` 时释放该连接所有信号资源 |
| — | 所有 `add/del/set/make` 函数改为接受 `stru_ws_session&` 参数,无状态纯函数 |
---
## 6. 对外接口
| 函数 | 文件 | 说明 |
|------|------|------|
| `app_web_server_init1(arg)` | web_server.cpp | 第一段初始化,启动 HTTP/WS 服务器 |
| `app_web_server_init2(arg)` | web_server.cpp | 第二段初始化(空) |
| `app_web_server(arg)` | web_server.cpp | 主线程循环,定时器驱动 `ws_task()` |
| `ws_send_all(p_tx, tx_len)` | web_server.cpp | 向所有 WS 客户端广播相同数据 |
| `ws_send_one(conn_id, p_tx, tx_len)` | web_server.cpp | 向单个 WS 连接发送数据 |
| `ws_recv(c, p_rx, rx_len)` | ws_method.cpp | 解析 WS 命令 JSON操作仅在 c 对应 session 内生效 |
| `ws_task()` | ws_method.cpp | 遍历所有 session各自构建增量 JSON 并推送 |
| `ws_session_destroy(c)` | ws_method.cpp | 释放连接对应的所有信号资源 |