# libweb_server 模块分析 **日期**: 2026-06-12 **基于源码**: `src/system/libweb_server/`(5个文件,约1500行) --- ## 1. 模块定位 `libweb_server` 是 RTU 的嵌入式 Web 服务器,作为 `app_web_server` 线程(9个应用线程之第7号)运行。基于 Mongoose v7.x,提供: - HTTP 静态文件服务(内嵌前端工程) - WebSocket 实时数据通道(JSON 格式) - 多客户端并发支持(per-connection session) - 信号增量推送(仅变更时发送) ### 目录结构 ``` src/system/libweb_server/ ├── inc/ │ ├── web_server.h # 模块头文件 │ └── ws_method.h # WebSocket 消息处理接口 └── src/ ├── web_server.cpp # HTTP/WS 服务器 + Mongoose 事件循环 ├── ws_method.cpp # WebSocket 命令解析 + JSON 推送 └── packed_fs.c # 前端文件嵌入(自动生成) ``` ## 2. 架构设计 ### 2.1 Mongoose 集成 Mongoose 是单线程事件驱动的网络库。RTU 的 Web 服务器以独立线程运行,集成方式: ``` app_web_server_init1: → mg_mgr_init(&mgr) // 初始化事件管理器 → 加载 packed_fs(前端文件) // 如果定义了 USE_PACKED_FS app_web_server_init2: → mg_http_listen(&mgr, "http://0.0.0.0:8000", fn, NULL) → 启动事件循环 app_web_server 线程: while(1): task_event_recv(EV_TIMER1 | EV_TIMER2 | EV_TIMER3) EV_TIMER1 (10ms): mg_mgr_poll(&mgr, 0) // 非阻塞轮询 EV_TIMER2 (100ms): ws_task() // WebSocket 推送 EV_TIMER3 (1000ms): 空闲 ``` ### 2.2 事件处理函数 `fn` 统一的 HTTP + WebSocket 事件处理: ```cpp static void fn(struct mg_connection *c, int ev, void *ev_data): switch(ev): MG_EV_HTTP_MSG: → mg_http_get_header(hm, "Sec-WebSocket-Key") 存在? YES → mg_ws_upgrade(c, hm, NULL) // WebSocket 握手 NO → mg_http_serve_dir / mg_http_serve_packed // 静态文件 MG_EV_WS_MSG: → ws_recv(c, wm->data.buf, wm->data.len) // WebSocket 消息 MG_EV_CLOSE: → ws_session_destroy(c) // 释放 per-connection 资源 ``` ### 2.3 多连接管理 ```cpp LOCAL std::vector g_ws_conns; // 所有 WS 连接 LOCAL pthread_mutex_t g_ws_conns_mutex = PTHREAD_MUTEX_INITIALIZER; ``` - 连接建立时将 `mg_connection*` 加入 `g_ws_conns` - 断开时从 `g_ws_conns` 移除并调用 `ws_session_destroy` - 所有对 `g_ws_conns` 的访问受互斥锁保护 ## 3. 前端嵌入式部署(packed_fs.c) `packed_fs.c` 由构建工具自动生成,将前端文件(HTML/CSS/JS)以 `unsigned char` 数组嵌入: ```c const struct mg_mem_file mg_packed_files[] = { {"/css/style.css", v3, sizeof(v3) - 1, 1781072856}, {"/index.html", v4, sizeof(v4) - 1, 1781073518}, {"/js/app.js", v5, sizeof(v5) - 1, 1781073909}, {"/js/monitor.js", v6, sizeof(v6) - 1, 1781072788}, {"/js/pages.js", v7, sizeof(v7) - 1, 1781073958}, {"/js/ws-client.js", v8, sizeof(v8) - 1, 1781071573}, {NULL, NULL, 0, 0} }; ``` Mongoose 通过 `mg_http_serve_packed` 直接返回内嵌文件,无需外部 `web_root` 目录。 ## 4. WebSocket 消息处理(ws_method.cpp) ### 4.1 消息格式 客户端发送 JSON 命令: ```json { "cmd": "subscribe", // 命令类型 "type": "out,ao,param", // 订阅的信号类型 "signals": ["saddr1", "saddr2"], // 指定信号(空=全部) "conn_id": 12345 // 连接标识 } ``` ### 4.2 命令分发 `ws_recv` 解析 JSON → 提取 `cmd` 字段 → 路由到处理函数: | cmd | 功能 | 处理函数 | |-----|------|---------| | `subscribe` | 订阅信号推送 | `ws_handle_subscribe` | | `unsubscribe` | 取消订阅 | `ws_handle_unsubscribe` | | `read_all` | 读取全部当前值 | `ws_handle_read_all` | | `set_value` | 设置 AO/Param 值 | `ws_handle_set_value` | | `yk_control` | 遥控操作 | `ws_handle_yk_control` | ### 4.3 信号订阅与增量推送 每个连接维护独立的 per-connection session: ```cpp struct ws_session { unsigned long conn_id; std::set subscribed_signals; // 已订阅的信号 saddr 集合 std::set subscribed_types; // 已订阅的信号类型(out/ao/param) // ... }; ``` 订阅流程: ``` 客户端发送 {cmd: "subscribe", type: "out,ao", signals: ["sig1","sig2"]} → ws_handle_subscribe(c, json): 获取或创建 session 将信号 saddr 加入 subscribed_signals 将类型加入 subscribed_types 回复 {status: "ok"} ``` 推送流程 (`ws_task`, 每 100ms 执行): ``` ws_task(): 遍历 g_ws_conns: 对每个连接: 收集该连接订阅的信号中发生了变更的 构建 JSON: {signals: [{saddr, value, time, quality}, ...]} 通过 mg_ws_send 发送 ``` ### 4.4 数据广播 ```cpp void ws_send_all(const char *p_tx, uint16_t tx_len): pthread_mutex_lock(&g_ws_conns_mutex) for each c in g_ws_conns: mg_ws_send(c, p_tx, tx_len, WEBSOCKET_OP_TEXT) pthread_mutex_unlock(&g_ws_conns_mutex) ``` ### 4.5 单连接发送 ```cpp void ws_send_one(unsigned long conn_id, const char *p_tx, uint16_t tx_len): 在 g_ws_conns 中查找 mg_connection.conn_id == conn_id 找到 → mg_ws_send ``` ## 5. HTTP API 除了 WebSocket,还提供 RESTful HTTP 接口(通过 `MG_EV_HTTP_MSG` 处理): | 方法 | 路径 | 功能 | |------|------|------| | GET | `/` | 静态首页 | | GET | `/api/status` | 设备状态 | | GET | `/api/signals` | 信号列表快照 | | POST | `/api/control` | 控制命令 | ## 6. 线程安全 - Mongoose 事件循环在 `app_web_server` 线程中执行 - `ws_task()` 在 `EV_TIMER2`(100ms)中执行,与事件循环同线程 - `g_ws_conns` 访问受 `g_ws_conns_mutex` 保护 - 通过任务事件机制与 datacenter 交互(信号值读取通过 `dc_get_signal_val` 等线程安全 API) ## 7. 已知问题 1. **mg_mgr_poll 非阻塞模式**: `mg_mgr_poll(&mgr, 0)` 零超时,高频率轮询可能消耗 CPU。Mongoose 连接数少时不明显 2. **packed_fs 更新**: 修改前端后需重新生成 `packed_fs.c` 并重新编译 3. **广播效率**: `ws_send_all` 逐连接发送,大量连接时效率低。当前场景(嵌入式 RTU)连接数通常较少