# 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 活跃 WebSocket 连接列表 g_ws_conns_mutex → pthread 互斥锁,保护 g_ws_conns g_ws_sessions → map 每连接独立信号资源(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 out_signals; // 本连接订阅的遥信 vector in_signals; // 本连接订阅的遥测 vector yk_signals; // 本连接订阅的遥控 vector ao_signals; // 本连接订阅的定值 vector param_signals; // 本连接订阅的参数 }; ``` 全局 `g_ws_sessions: map` 管理所有连接的独立资源。`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`,遍历广播 | | 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` 管理 | | — | `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 | 释放连接对应的所有信号资源 |