9.1 KiB
9.1 KiB
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 全局状态
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 会话结构
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)
{
"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 | 释放连接对应的所有信号资源 |