资讯详情

基于Drogon框架的服务器室温监控系统实现详解

📅 2026/9/10 8:16:59 | 华诺云谱 👁 阅读
基于Drogon框架的服务器室温监控系统实现详解
简介面向系统管理员与嵌入式开发者的低成本服务器室温监控方案基于Drogon C Web框架搭配树莓派等GPIO单板机和一总线温度传感器实现实时采集、Web可视化、告警邮件与设备管理。压缩包共五十二个文件约450KB涵盖C后端源码、CSP页面和JavaScript样式文件、KiCad原理图及PCB文档、SQL脚本与部署配置软硬件资料齐全。目前已有五十人学习适合想基于C快速搭建物联网监控服务的中高级开发者。利用附带的电路设计、轮询脚本和数据库迁移脚本可完整复现硬件连接与软件初始化流程同时资源目录按源码、视图、硬件设计分模块组织便于按需查阅能显著缩短从概念到部署的周期。1. 用 Drogon 框架做服务器室温监控先确认这个系统该管什么机房热失控往往不是突然发生的。空调压缩机效率下降、冷通道被线缆堵住、夜间加班误关空调温度曲线会在一两个小时里缓慢爬升等到监控平台弹告警时硬件已经顶着高温跑了很久。这个场景里需要的是一个长期驻留在机房内、接口足够简单、内存占用尽量小的室温采集服务。用 Drogon 框架来写本质上是把 C 异步 HTTP 能力用在监控数据采集与展示链路上编译出一个单文件二进制直接部署依赖少常驻内存一般压在几十 MB 量级。“服务器室温监控系统”这个词组拆开看核心是两条线一是采集线从温度传感器、空调控制器或服务器 BMC 上周期读取温度二是服务线通过 Drogon 暴露 HTTP 接口和 WebSocket 推送让运维在浏览器里看到实时曲线和告警。适合读这篇内容的人包括机房运维工程师、负责硬件健康度的 SRE、做监控平台二次开发的后端以及想用真实业务场景练习 C 异步编程的开发者。拿到 zip 源码包后第一步不是急着编译而是先把数据模型、采集频率、存储窗口和告警阈值这几件事定下来。2. 室温监控系统的数据链路与 Drogon 选型理由2.1 从传感器到浏览器的完整数据链路一个最基本的室温监控闭环包括四段传感器采集温度本地服务聚合数据通过接口对外提供查询与推送前端展示曲线并触发告警。常见做法是让 Drogon 服务跑在机房内的一台管理机上通过串口、Modbus、SNMP 或 IPMI 从温度传感器和服务器管理口取数。采集频率不需要太高15 到 30 秒一次已经足够覆盖制冷失效的渐进过程频率过高反而会让环形队列和日志文件迅速膨胀。取到的温度数据在内存里形成时间序列保留最近 6 到 24 小时用于页面展示超过窗口的数据通过文件日志滚动落盘。Drogon 在这条链路里承担的是接口层职责接收传感器写入、响应轮询请求、推送实时变更。存储不要做太重内存环形队列加文件日志即可满足机房监控场景既没有数据库运维负担也不会给老旧的机房管理机带来额外负载。2.2 为什么选 Drogon 而不是 Flask 或 Node.js选型理由要从长期驻留这个前提展开。用一张表对比三种常见方案对比维度Drogon (C)Flask (Python)Node.js常驻内存约 20 到 60 MB约 80 到 150 MB 含解释器约 40 到 100 MB并发处理多线程 epoll异步非阻塞同步模型高并发需额外部署事件循环单线程带 worker部署形态单二进制文件依赖极少需要 Python 环境和依赖包需要 Node 运行时和 node_modules启动速度毫秒级百毫秒级百毫秒级交叉编译CMake 交叉编译顺畅受解释器限制较大取决于原生模块Drogon 的异步模型天然适合大量浏览器页面同时挂着监控界面的场景。如果 30 个运维同时打开温度看板每个页面每 5 秒轮询一次Drogon 用少量线程就能扛住Flask 在这种场景下每个请求都占用一个 worker 线程长时间保持连接会撑高内存Node.js 虽然事件循环模型接近但在弱网环境和机器资源受限的机房里原生模块编译和依赖管理反而不如一个静态二进制干净。选 Drogon 还有一个实际理由机房里的服务器硬件种类杂有的跑 x86 的 Ubuntu有的是 ARM 架构的边缘网关Drogon 的 CMake 构建体系在交叉编译时很省事一套代码编出不同平台的二进制直接能用。如果只是临时写个脚本测温Python 没问题但做成一个长期守护的运行实体Drogon 的资源占用和收敛性更让人放心。2.3 系统边界与模块划分按标题给的“服务器室温监控系统”把源码工程拆成四个模块是比较常见的做法也方便后续替换硬件或扩展告警通道。模块职责关键依赖采集模块从温度传感器或 BMC 读取数据加时间戳后写入存储传感器驱动、Modbus/IPMI 协议存储模块内存环形队列保存近期样本文件日志滚动落盘标准库容器、文件系统接口模块Drogon 路由、JSON 序列化、WebSocket 推送Drogon 框架、JSON 序列化组件告警模块超阈值判断、告警去重、回调通知存储模块、日志模块采集模块只负责把温度变成TempSample不关心数据怎么展示接口模块只负责路由和序列化不关心温度从哪来。Drogon 的事件循环把采集定时器和 HTTP 路由放在同一个进程里共享温度快照通过互斥锁或原子变量保护读写避免出现数据竞争。3. 基于 Drogon 框架实现室温监控核心接口3.1 定义温度数据模型与内存环形队列先定义温度样本的结构体这是整个系统最基础的数据契约struct TempSample { uint64_t timestamp; // 采样时间Unix 秒 int zone_id; // 区域编号0 表示 A 机柜1 表示 B 机柜 float temperature; // 摄氏温度保留小数点后一位 };timestamp 用uint64_t是为了避开 2038 年问题监控服务一旦长期运行时间字段不能成为短板。zone_id 用整数而不是字符串因为机房内区域数量通常不超过几十个整数检索和比较的开销更小。temperature 用 float 足够室温精度到 0.1 摄氏度即可double 会增加 JSON 序列化后的字节长度。存储层用一个环形队列每个 zone_id 维护独立队列constexpr size_t kRingCapacity 4096; std::arrayTempSample, kRingCapacity ring; std::size_t head 0; std::size_t count 0; std::mutex mtx; void appendSample(const TempSample sample) { std::lock_guardstd::mutex lock(mtx); ring[(head count) % kRingCapacity] sample; if (count kRingCapacity) { count; } else { head (head 1) % kRingCapacity; } }这段代码把新样本写到队尾队列满后覆盖最旧的数据。加锁是因为采集线程和 HTTP 查询线程可能同时访问head和count。用std::lock_guard保证异常安全函数退出时自动释放锁。4096 条样本在 15 秒采集间隔下大约能存 17 小时足够覆盖白班运维的观察窗口。如果温度上报频率提高到了 5 秒一次4096 条只能存 5.7 小时这时需要在存储模块里增加文件日志回放逻辑或者把kRingCapacity调到 16384。这个参数直接影响内存占用每条TempSample16 字节16384 条大约 256 KB完全可以接受。3.2 注册 HTTP 路由采集接口、查询接口、告警接口Drogon 的registerHandler负责路由注册先实现传感器上报温度的数据采集接口#include drogon/drogon.h using namespace drogon; void setupRoutes() { app().registerHandler( /api/temp, [](const HttpRequestPtr req, std::functionvoid(const HttpResponsePtr) callback) { auto json req-getJsonObject(); if (!json) { auto resp HttpResponse::newHttpResponse(); resp-setStatusCode(k400BadRequest); resp-setBody({\error\:\invalid json\}); callback(resp); return; } TempSample sample; sample.timestamp (*json)[ts].asUInt64(); sample.zone_id (*json)[zone].asInt(); sample.temperature (*json)[temp].asFloat(); appendSample(sample); auto resp HttpResponse::newHttpResponse(); resp-setStatusCode(k200OK); resp-setBody({\status\:\ok\}); callback(resp); }, {Post}); }接口逻辑分三层先校验请求体是否为合法 JSON再提取ts、zone、temp三个字段最后写入环形队列并返回状态。采集端如果在 Modbus 上读到的值不是 JSON 格式需要在采集模块里先做协议转换Drogon 这一侧只接收标准 JSON。告警阈值接口建议单独做一个GET /api/config和PUT /api/config用 JSON 保存各区域的温度上限{zone: 0, max_temp: 28.0, min_temp: 10.0}阈值放到接口里而不要写死到代码里是因为机房负载会随业务变化空调维护期间临时调高阈值是常态改接口比重新编译部署简单得多。查询接口的核心是把环形队列转成 JSON 数组app().registerHandler( /api/temp/history, [](const HttpRequestPtr req, std::functionvoid(const HttpResponsePtr) callback) { auto zoneOpt req-getParameter(zone); Json::Value result(Json::arrayValue); std::lock_guardstd::mutex lock(mtx); for (size_t i 0; i count; i) { const auto s ring[(head i) % kRingCapacity]; if (!zoneOpt || std::stoi(zoneOpt) s.zone_id) { Json::Value item; item[ts] s.timestamp; item[temp] s.temperature; result.append(item); } } auto resp HttpResponse::newHttpResponse(); resp-setBody(Json::FastWriter().write(result)); resp-setContentTypeCode(CT_APPLICATION_JSON); callback(resp); }, {Get});这里把锁粒度和数据复制量做了折中读取时一次性复制整个队列近期数据到Json::Value虽然短时间占用堆内存但避免了长时间持锁阻塞采集线程。查询时支持?zone0过滤不传则返回全部区域。3.3 用 WebSocket 推送实时温度到管理端页面轮询会带来大量无效请求机房温度变化本身是低频事件用 WebSocket 推送更合适。Drogon 提供了WebSocketController基类class TempWebSocket : public WebSocketControllerTempWebSocket { public: void handleNewConnection(const HttpRequestPtr req, const WebSocketConnectionPtr conn) override { conn-send(fetchLatestSnapshot()); // 连接建立后立即推一次当前温度 } void handleNewMessage(const WebSocketConnectionPtr conn, std::string message, const WebSocketMessageType type) override { if (type WebSocketMessageType::Text) { conn-send(fetchLatestSnapshot()); } } void handleConnectionClosed(const WebSocketConnectionPtr conn) override { LOG_INFO websocket closed; } };客户端连上后立刻收到当前快照之后客户端可以发送任意文本消息触发一次主动推送。如果要做真正的服务端周期推送可以在 Drogon 的事件循环里注册定时器每 5 秒遍历所有连接调用send。简化方案是让前端自己发请求拉数据。3.4 用 CMake 编译并验证最小链路工程的 CMakeLists.txt 核心部分如下cmake_minimum_required(VERSION 3.10) project(temp_monitor) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(Drogon REQUIRED) add_executable(temp_monitor main.cpp) target_link_libraries(temp_monitor Drogon::Drogon)编译和验证命令mkdir -p build cd build cmake .. make -j$(nproc) ./temp_monitor curl -X POST http://127.0.0.1:8848/api/temp \ -H Content-Type: application/json \ -d {ts:1700000000,zone:0,temp:25.6} curl http://127.0.0.1:8848/api/temp/history?zone0第一条 curl 模拟传感器上报一条 25.6 度的数据第二条 curl 查询历史数据。如果能从返回的 JSON 里看到刚写入的温度说明路由、模型、存储三层已经连通。这里确认一下 Drogon 的监听端口和线程数的初始化代码int main() { setupRoutes(); app().addListener(0.0.0.0, 8848); app().setNumThreads(4); app().run(); }addListener绑定所有网卡 IP生产环境建议绑到机房内网地址。setNumThreads(4)根据管理机 CPU 核数调整通常设为核数或核数加一。4. Drogon 服务端长时间运行调优与连接治理4.1 线程数与监听地址配置Drogon 的线程模型是主线程加工作线程池setNumThreads(4)指定处理网络事件和 HTTP 请求的线程数。不是线程越多越好机房管理机通常配置不高4 核 4 GB 内存是常见规格设 4 个线程足够。如果前端有大量 WebSocket 长连接可以适当加到 8让多个线程分担连接事件。addListener需要注意一个细节如果不传具体 IP 而是用0.0.0.0意味着服务会监听所有网卡接口包括外网网卡。虽然监控系统多半只有内网访问但建议显式绑定内网 IP例如app().addListener(192.168.10.20, 8848)减少暴露面。4.2 请求体限制、超时与心跳参数温度采集接口的数据量很小最大请求体限制可以收紧app().setClientMaxBodySize(8 * 1024); // 请求体最大 8 KB app().setIdleConnectionTimeout(60); // 空闲连接 60 秒断开 app().setKeepaliveTimeout(120); // keep-alive 超时 120 秒参数说明参数建议值作用setClientMaxBodySize8192防止异常大包挤占内存温度 JSON 通常在几百字节setIdleConnectionTimeout60释放长期不活跃的 HTTP 连接防止连接数缓慢积压setKeepaliveTimeout120保持健康的长连接减少反复握手开销WebSocket 连接不建议设置过短的绝对超时因为监控页面可能长时间挂着。WebSocket 的心跳由前端自行实现每 30 秒发一个 ping 帧Drogon 侧收到 ping 后维持连接活跃。4.3 日志级别与守护进程模式日志是排查采集断点的第一手段。Drogon 使用 trantor 日志库可以设置日志级别和运行方式trantor::Logger::setLogLevel(trantor::Logger::kDebug); trantor::Logger::setOutputPath(./logs); trantor::Logger::setOutputFileName(temp-monitor); app().enableRunAsDaemon(); // 以守护进程方式后台运行 app().run();生产环境建议把日志级别设置为kInfo调试采集协议时临时降级到kDebug。enableRunAsDaemon让程序脱离终端后台运行配合 systemd 或 supervisor 做进程守护。常见排查命令tail -f logs/temp-monitor.log ss -tnp | grep 8848 curl -v http://127.0.0.1:8848/api/temp/history?zone0日志里出现大量Timeout或Connection reset时先查网络链路再查采集端传感器是否掉线。ss -tnp可以观察连接数是否随运行时间持续增长如果 ESTABLISHED 数量线性上升说明存在连接泄漏重点检查 WebSocket 的关闭回调是否被正确执行。5. 进阶技巧用 Drogon 定时任务与文件日志自检室温监控5.1 把告警巡检注册到事件循环里Drogon 的事件循环提供runEvery接口可以替代外部 cron 来做周期巡检int main() { setupRoutes(); app().getLoop()-runEvery(15.0, []() { TempSample latest readLatestSample(0); if (latest.temperature 28.0f) { writeAlert(latest, zone 0 high temperature); } }); app().addListener(0.0.0.0, 8848); app().setNumThreads(4); app().run(); }runEvery的第一个参数是秒数回调会被调度到 Drogon 事件循环中执行不额外占用线程。告警阈值 28 摄氏度只是示例具体值应该根据机房历史温度分位数来确定。writeAlert内部先写告警日志再通过回调通知运维平台例如发送到企业微信机器人或钉钉群。为避免告警风暴同一区域同一级别的告警需要做时间窗口去重15 分钟内不重复触发。5.2 文件日志承担温度曲线回放环形队列只保留近一天的数据更长周期的趋势分析依赖文件日志。在appendSample里增加一行追加写入void appendSample(const TempSample sample) { FILE* fp fopen(temps.csv, a); if (fp) { fprintf(fp, %llu,%d,%.1f\n, static_castunsigned long long(sample.timestamp), sample.zone_id, sample.temperature); fclose(fp); } // 同时写入环形队列 }CSV 格式简洁方便用 python 做回放分析。告警阈值调参前先回放最近一周的温度日志看看温度波动范围和高温持续时间再决定阈值设在多少而不是凭印象拍脑袋。这个动作能让监控系统在空调故障发生前提前预警。5.3 验证告警链路是否生效最后用一个简单的脚本验证整套逻辑是否闭环curl -X POST http://127.0.0.1:8848/api/temp \ -H Content-Type: application/json \ -d {ts:$(date %s),zone:0,temp:29.5} sleep 16 tail -5 logs/temp-monitor.log grep zone 0 high temperature logs/temp-monitor.log这条命令链把 29.5 度的高温样本写入系统等待超过巡检周期后检查日志中的告警记录。如果出现告警条目说明采集、存储、定时巡检、告警落盘整条链路都在工作。下一步再把 WebSocket 推送和前端曲线接上整个基于 Drogon 框架的服务器室温监控系统就具备上线条件了。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。