找回密码
立即注册
搜索
发回帖 发新帖

6374

积分

0

好友

808

主题
发表于 昨天 22:35 | 查看: 8| 回复: 0

网关类设备现场调试时,最省事的不是接串口,也不是装专用 App,而是让工程师掏出手机连上设备热点,打开浏览器就能改 IP、看采集状态、下发参数。

需求边界

先明确要什么:一个能跑在几 MB 内存、几十 MHz 主频设备上的 HTTP 服务,能返回静态页面(HTML/CSS/JS)、响应配置读写的接口,并把参数写入非易失存储。它不需要支撑上千并发连接,同时在线的管理员通常也就一两个。

选型

方案 体积 特点 适用
Boa 极小(~100KB) 单线程、CGI 支持、多年未活跃更新 极简老项目
lighttpd 中等(~500KB+fastcgi) 成熟、支持 CGI/FastCGI、配置灵活 需要 CGI 脚本(PHP/Lua)
Mongoose / libmicrohttpd 可裁剪到几十 KB 以库形式嵌入、事件回调、易集成 新项目、需要自定义 API

新项目推荐 嵌入式库方案(Mongoose 或 libmicrohttpd):把它们作为库链接进主程序,HTTP 服务和业务逻辑在同一个进程里,直接读写全局变量、直接调用采集接口,不需要 CGI 那套进程间通信,调试也简单。

以 Mongoose 为例的最小服务

Mongoose 用事件回调模型,注册一个 HTTP 监听,所有请求进同一个回调,按 URI 路由。

#include "mongoose.h"

static const char *s_listen_on = "http://0.0.0.0:80";
static const char *s_root_dir = "/www";   // 静态页面目录

// 事件处理器:mg_http_serve_dir 服务静态文件,自己处理 /api
static void cb(struct mg_connection *c, int ev, void *ev_data)
{
  if (ev == MG_EV_HTTP_MSG) {
    struct mg_http_message *hm = (struct mg_http_message *) ev_data;

    // 配置读取:GET /api/config
    if (mg_match(hm->uri, mg_str("/api/config"), NULL)) {
      char json[256];
      build_config_json(json, sizeof(json));   // 组装当前参数
      mg_http_reply(c, 200, "Content-Type: application/json\r\n", "%s", json);
      return;
    }

    // 配置下发:POST /api/config
    if (mg_match(hm->uri, mg_str("/api/config"), NULL) &&
        mg_strcmp(hm->method, mg_str("POST")) == 0) {
      if (apply_config_from_json(hm->body.ptr, hm->body.len)) {
        save_config_to_flash();              // 持久化到非易失存储
        mg_http_reply(c, 200, "", "{\"result\":\"ok\"}");
      } else {
        mg_http_reply(c, 400, "", "{\"result\":\"bad param\"}");
      }
      return;
    }

    // 其余请求当作静态文件请求
    struct mg_http_serve_opts opts = { .root_dir = s_root_dir };
    mg_http_serve_dir(c, hm, &opts);
  }
}

int main(void)
{
  struct mg_mgr mgr;
  mg_mgr_init(&mgr);
  mg_http_listen(&mgr, s_listen_on, cb, &mgr);
  for (;;) mg_mgr_poll(&mgr, 100);                 // 事件循环
  return 0;
}

核心是一个事件循环 mg_mgr_poll。/api/... 路径自己解析处理,其他路径交给 mg_http_serve_dir 返回静态文件。整个 HTTP 服务和采集主程序在同一进程里,读到的配置直接就是内存中的变量。

REST 风格的配置接口

前后端用 JSON 交换配置,前端 fetch 调用。接口设计保持简单一致:

// 前端读取配置
fetch('/api/config').then(r => r.json()).then(cfg => {
  document.getElementById('serverIp').value = cfg.serverIp;
});

// 前端提交配置
fetch('/api/config', {
  method: 'POST',
  headers: {'Content-Type': 'application/json'},
  body: JSON.stringify({serverIp: '192.168.1.100', pollInterval: 5})
}).then(r => r.json()).then(res => alert(res.result));

C 侧的 JSON 解析不必引入重型库,Mongoose 自带轻量 mg_json_get_* 系列函数,能按路径取出字段:

// 从 POST 的 JSON body 里取字段(Mongoose 内置 JSON 助手)
int apply_config_from_json(const char *body, size_t len)
{
  struct mg_str json = mg_str_n(body, len);
  double poll_interval;
  char server_ip[64];

  if (mg_json_get_num(json, "$.pollInterval", &poll_interval) &&
      mg_json_get_str(json, "$.serverIp", server_ip, sizeof(server_ip))) {
    g_config.poll_interval = (uint32_t)poll_interval;
    strncpy(g_config.server_ip, server_ip, sizeof(g_config.server_ip));
    return 1;   // 成功
  }
  return 0;       // 字段缺失或格式错误
}

参数持久化与生效

配置改动要能重启后保留。常见做法是存成 JSON 文件放到一个独立的可写分区(如 /mnt/config/config.json),或者直接写 Flash 的某个扇区。

// 保存:序列化后写临时文件再重命名,避免写一半掉电损坏
int save_config_to_flash(void)
{
  char tmp[] = "/mnt/config/.tmp.json";
  char dst[] = "/mnt/config/config.json";
  FILE *f = fopen(tmp, "w");
  if (!f) return -1;
  fprintf(f, "{\"serverIp\":\"%s\",\"pollInterval\":%u}",
          g_config.server_ip, g_config.poll_interval);
  fflush(f);
  fsync(fileno(f));        // 确保落盘
  fclose(f);
  rename(tmp, dst);        // 原子替换
  return 0;
}

用“写临时文件 + rename”而不是直接覆盖,是为了防止写入过程中掉电留下半个损坏的配置——rename 在同一文件系统内是原子操作,要么旧配置要么新配置,不会是不完整的中间态。

别忽略安全

本地配置页面也要有基本防护,设备一旦被接入局域网就不是“本地”了:

// 简单的会话口令校验:未登录则跳转到登录页
if (!is_authenticated(hm) && !mg_match(hm->uri, mg_str("/login.html"), NULL)) {
    mg_http_reply(c, 301, "Location: /login.html\r\n", "");
    return;
}
  • 改默认口令:出厂默认 admin/admin 是最大的安全缺口,首启强制改密。
  • 接口鉴权:不能只靠前端隐藏按钮,每个 /api/config 请求都要在服务端校验会话。
  • 不要 root 运行:Web 服务进程用普通用户跑,缩小被攻破后的影响面。

完整链路

嵌入式 Web 配置的关键是把 HTTP 服务和业务逻辑放进同一个进程——用嵌入式 HTTP 库而非独立服务器进程,省掉 CGI 通信开销,调试也直观。




上一篇:PHP 核心两枚 RCE 泄露:CVE-2026-109618/-109619 事件梳理与应急响应
下一篇:lwIP协议栈移植与TCP长连接通信实战:从PHY链路到自动重连
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-10-22 07:27 , Processed in 0.071937 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表