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

6.2 KiB
Raw Blame History

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 事件处理:

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 多连接管理

LOCAL std::vector<struct mg_connection *> 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/JSunsigned char 数组嵌入:

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 命令:

{
    "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

struct ws_session {
    unsigned long conn_id;
    std::set<std::string> subscribed_signals;  // 已订阅的信号 saddr 集合
    std::set<std::string> 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 数据广播

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 单连接发送

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_TIMER2100ms中执行与事件循环同线程
  • 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连接数通常较少