
机房温湿度告警这种事看着小真出了问题是大事一柜子服务器烧了都不一定有你机房温度超限来得快。我们这边上次因为空调故障机房温度直接被拉到38度要不是有人碰巧巡检发现了整个机柜的服务器状态早就一片飘红了。领导拍板说必须上一套机房的温湿度在线监控而且要直接接到办公室门口那块大屏上。设备选来选去定了带以太网口、支持TCP和SNMP双协议栈的温湿度记录仪我在对接过程中把TCP和SNMP两条通道前后都打通了。这篇就完整记录一下整个“双协议融合部署”的过程重点讲讲TCP/SNMP以太网温湿度记录仪接入机房大屏实操中会遇到的那些坑和细节给后面要搞同类项目的朋友一个参考。1. 为什么是“双协议融合”TCP和SNMP各自解决什么问题1.1 两种协议的本质差异先快速对齐一下基础概念。TCP是传输层协议核心是面向连接、可靠传输通信双方要先完成三次握手建立连接之后才能互相收发数据。SNMP是应用层协议跑在UDP之上设计目标是网络设备管理采用Manager/Agent模型被管设备上跑Agent管理站通过Get/Set/Trap报文来采集配置和状态。两者底层一套TCP/IP协议栈但气质完全不同。TCP像是你直接拨通对方电话接通后可以长时间通话、对方也能随时把变化告诉你SNMP更像是你定期发短信给对方对方回一条状态信息另外对方遇到紧急情况也会主动给你发一条特别短信即Trap。1.2 机房监控场景下各自的不可替代性我一开始图省事想过只用TCP。毕竟温湿度记录仪支持TCP上报把数据推到中间服务再转发给大屏链路短、好调试。但后来发现一个问题机房本身就有一套网络监控系统同事那边更习惯用SNMP纳管设备。如果我只开TCP通道那套网管系统就完全看不到这台温湿度记录仪两个系统之间又出现了一个信息孤岛。反过来只走SNMP也会难受SNMP轮询间隔最短一般也就30秒到1分钟大屏要实时刷新就有点“卡顿感”而且SNMP Walk一次拉全表做起来琐碎实时性和交互性远不如一条TCP长连接直接数据推送。所以最终定了“双协议融合”的方案TCP通道负责大屏实时数据刷新和交互指令SNMP通道负责网管平台的定期采集、OID查询和异常Trap告警。两条链路采用同一台设备数据源一致既满足了大屏的“实时感”又满足了网管系统的“标准化接入”还能互相校验数据有没有问题。1.3 整体架构这套方案一共分四层感知层以太网温湿度记录仪内置温度探头和湿度探头支持TCP Server/TCP Client双模式同时内置SNMP Agent。接入层一台机房旁边的Linux工控机作为采集服务上面跑两个采集模块一个维护与记录仪的TCP长连接另一个定期向记录仪的SNMP Agent发Get请求。汇聚层采集到的温湿度数据统一写入本地的SQLite/MySQL同时维护最新值在内存里。大屏的后端服务直接从内存或者库里取数。展示层办公室门口的智能大屏通过WebSocket或HTTP轮询从后端拿数据显示当前温度、湿度曲线超限时闪烁告警。这个架构的好处是TCP和SNMP两条路互不干扰。TCP断了SNMP还能兜底取数SNMP平台挂了也不影响大屏显示。故障域隔离得很清楚。2. 设备选型与协议规划开工前必须想清楚的事2.1 温湿度记录仪的选型要点现在市面上叫“以太网温湿度记录仪”的设备非常多价格从几百到几千都有但支持协议差别很大。我这边踩了个小坑有的设备只支持HTTP上报到云平台根本没有本地TCP和SNMP能力拿过来就是个“能用但不好用”的玩具。选型时要重点关注这几点通信接口必须要有RJ45以太网口支持标准TCP/IP协议栈。最好同时具备RS485口方便后续扩展其他传感器。TCP工作模式要确认设备是支持TCP Server被动等待连接还是TCP Client主动上报还是两种都支持。这个直接决定采集程序怎么写。SNMP支持确认设备内置SNMP Agent支持SNMPv2cv3一般也会支持但成本高并且有公开或可查的MIB文件。没有MIB文件的话后续OID只能靠试非常痛苦。精度和量程我当时选的设备是温度精度±0.3℃湿度精度±3%RH测温范围-20~60℃对机房环境完全够用。本地显示带不带LCD屏无所谓但一定要有本地状态灯至少能看出网线有没有通、是否处于TCP连接状态。我建议优先选同时支持TCP Server和TCP Client的设备兼容性最好。因为我既有“设备主动推数据给我”的场景也有“我主动去设备里拉数据”的场景两种模式在不同阶段都用得上。2.2 SNMP侧的MIB规划与OID划分很多厂家的温湿度记录仪出厂时SNMP OID是固定的选型时就问厂家要MIB文件。MIB文件本质就是一个树状结构的文本定义里面把设备的温度、湿度、运行状态映射到了具体的OID节点。以我这台设备为例它内置的OID大致是这样的企业私有根节点 1.3.6.1.4.1.38391 └─.1 温湿度记录仪 ├─.1 设备名称字符串 ├─.2 设备运行状态整数 ├─.3 当前温度整数单位0.1℃ ├─.4 当前湿度整数单位0.1%RH └─.5 告警状态整数按位标识温度/湿度超限实际请求的时候完整OID是这样的温度1.3.6.1.4.1.38391.1.1.3.0 湿度1.3.6.1.4.1.38391.1.1.4.0注意很多设备把温度/湿度定义为整数类型单位可能是0.1℃或0.01℃。也就是说设备返回值是235实际温度是23.5℃而不是235℃。这个“单位换算”问题非常经典不看清MIB定义就直接展示数据大屏上会出现一个两位数的温度谁看都慌。规划OID时还要想清楚一件事网管平台到底需要哪几个节点。不要贪多先把“当前温度”“当前湿度”“设备在线状态”“告警状态”四个节点规划好后续需要再加。SNMP老手都知道OID规划得越精简后面轮询压力越小排查问题也越容易。2.3 TCP侧的通信协议约定TCP链路这边设备作为TCP Server时默认监听端口通常是5000或8899具体看设备支持。连接建立后双方交互什么报文这里要提前约定好。一些高端设备支持Modbus TCP协议是最标准的方式一些设备则使用厂家自定义的简单文本协议。我遇到的情况是设备支持自定义协议指令格式是固定的十六进制报文。读取当前温湿度很简单下发读取指令16进制 FF 03 00 00 00 02 D0 0B 设备应答16进制 FF 03 04 00 F0 00 9C 9C 77应答报文里00 F0换算成十进制是240温度就是24.0℃00 9C是156湿度就是15.6%RH。注意返回的数据是两个寄存器每个寄存器用两个字节表示高位在前。我强烈建议买设备之前先把通信协议文档要过来看一眼。如果连协议文档都提供不清楚这设备后面用起来一定会让你抓狂。文档里必须明确波特率以太网不存在、端口号、报文格式、校验方式通常是CRC16或Modbus校验、寄存器地址对应表。3. TCP通道实操从三次握手到断线重连3.1 服务端设计要点设备作为TCP Server监听我们的采集程序就是客户端负责主动连接。我用Python写了一个采集服务用socket库实现整体逻辑不复杂维护一条到设备的长连接每隔10秒发一次读取指令收到应答后解析缓存。第一步是TCP三次握手的建立。客户端发起connect内核会自动完成SYN、SYN-ACK、ACK三次握手。这里不需要应用层操心但要注意connect超时时间设置。有的记录仪在停电或网线断开时TCP连接会处于半开状态客户端发数据后收不到应答TCP层的重传机制会一直重试可能导致请求卡死。所以socket一定要设置超时import socket import struct TCP_IP 192.168.1.100 TCP_PORT 5000 sock socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(5) try: sock.connect((TCP_IP, TCP_PORT)) print(TCP三次握手完成连接建立) except socket.timeout: print(连接超时) # 读取温湿度指令 req bytes.fromhex(FF 03 00 00 00 02 D0 0B) sock.send(req) try: resp sock.recv(256) if len(resp) 8: # 解析温度从偏移第3字节开始的两个字节 temp_raw struct.unpack(H, resp[3:5])[0] hum_raw struct.unpack(H, resp[5:7])[0] temperature temp_raw / 10.0 humidity hum_raw / 10.0 print(f当前温度: {temperature} ℃, 湿度: {humidity} %RH) except socket.timeout: print(等待应答超时)关键点在于settimeout(5)5秒内收不到应答就抛异常由上层逻辑决定是续读还是重连。实测下来对于机房内网环境5秒超时绰绰有余又不至于让程序长时间卡死。3.2 客户端对接中的高频坑TCP客户端看起来简单但真正跑起来之后常见的坑一个接一个。我一个个说。坑一重连时报“地址已在使用”。这在Linux上很常见。成因是客户端主动断开连接后本端的端口进入TIME_WAIT状态内核默认要等60秒左右才能释放。如果程序快速重启再次connect时用相同的本地端口就会报Address already in use。解决办法有三种在socket创建后设置SO_REUSEADDR服务端场景或设置SO_LINGER特殊需求下。干脆让操作系统自动分配新端口也就是每次connect前不bind本地端口由内核随机分配。大多数情况下这是最省事的做法。调小tcp_max_tw_buckets等内核参数但机房环境不建议乱调。sock socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)注意SO_REUSEADDR主要解决的是服务端bind时的端口复用问题对客户端TIME_WAIT的地址冲突实际效果有限。客户端最稳妥的办法还是别自己bind让内核分配临时端口。坑二对端设备重启后客户端“假连接”。设备断电重启后TCP连接其实已经被对端关闭了但客户端这边没有任何感知以为连接还活着。你发一帧读取指令TCP协议栈会先重传重传几次后才判断对端不可达然后返回错误。整个过程可能耗时几十秒甚至更久。解决方案是应用层心跳客户端每10秒正常发一次读取指令如果连续3次都超时就主动close掉旧连接重新connect。这样能保证故障恢复后大屏数据不长时间停留在旧值上。坑三粘包和半包。我在调试时遇到一个问题一次recv收上来的数据不是完整的一帧。原因是TCP是流式协议没有消息边界如果设备连续上报数据客户端一次recv可能收到两帧拼接或者只收到半帧。解决方案有两个根据帧长度解析先读够固定长度的头部根据头部里的长度字段再读足剩余字节。引入一个小的接收缓冲区拼帧处理。我这里是固定8字节长度代码里就直接判断len(resp) 8读满8字节再解析不满足就继续读这样就把半包问题处理掉了。3.3 大屏“实时刷新”的实现TCP进程从设备拿到温度、湿度后需要同步给大屏。我当时的实现方式是采集进程每秒更新一次本地latest_temp、latest_hum存在全局变量里也可以用redis然后通过一个WebSocket服务把最新值推给前端大屏。大屏页面用WebSocket连接后收到服务端推送的JSON直接更新数字显示和曲线。import json import asyncio import websockets latest_temp 0.0 latest_hum 0.0 async def push_latest(websocket): while True: await websocket.send(json.dumps({ temp: latest_temp, hum: latest_hum })) await asyncio.sleep(1) async def main(): async with websockets.serve(push_latest, 0.0.0.0, 18080): await asyncio.Future()WebSocket推流的好处是不需要前端反复轮询后端接口实时性好连接还能保持前端可以直接在回调里更新数据。第一次数据上屏后领导看了一眼“这温度曲线怎么一直在跳”那是大屏页面的Y轴自动缩放改成了固定范围之后曲线平滑多了。这属于展示层细节后面再接。4. SNMP通道实操从一个OID读到完整监控体系4.1 设备端SNMP配置TCP通道跑顺之后再配SNMP通道就轻松很多。设备管理页面里需要开启SNMP服务一般设置下面几项启用SNMP Agent打开SNMP版本SNMPv2c最通用兼容性最好如果网管平台只支持v1记得选v1但v1的Trap功能非常弱只读团体名Read Communitypublic机房内网环境可接受如果设备放在外部网络建议改成私密字符串Trap团体名public用于设备主动上报告警Trap目标地址管理站IP比如192.168.1.104.2 用snmpwalk验证数据Windows下一般没有自带SNMP工具需要自己装。现在很多是Windows系统自带SNMP服务在“启用或关闭Windows功能”里勾选“简单网络管理协议(SNMP)”就可以如果是命令行工具可以下载net-snmp的Windows安装包。我这一侧是在Linux上直接用net-snmp。先验证设备SNMP Agent是否正常响应snmpget -v2c -c public 192.168.1.100 1.3.6.1.4.1.38391.1.1.3.0正常返回值类似SNMPv2-SMI::enterprises.38391.1.1.3.0 INTEGER: 240240对应温度24.0℃换算关系前面说过了。湿度同理snmpget -v2c -c public 192.168.1.100 1.3.6.1.4.1.38391.1.1.4.0如果嫌一个个OID麻烦可以用snmpwalk把整个MIB子树都拉出来snmpwalk -v2c -c public 192.168.1.100 .1.3.6.1.4.1.38391这条命令会输出设备上所有支持的OID和值我拿到之后第一件事就是核对MIB文档把温度、湿度、告警状态三个OID对应值确认一遍免得文档写一套、实际响应另一套。4.3 Trap主动上报与轮询互补SNMP不光能“拉”还能“推”——Trap就是设备主动上报告警的机制。我给设备配置了Trap目标地址为机房网管服务器同时设置温度上限25℃、湿度上限60%RH。一旦超限设备会主动向网管发Trap包网管这边就能立刻看到告警而不必等到下一次轮询才发现。用Linux验证Trap接收可以起一个最基本的Trap监听net-snmp自带的snmptrapdsnmptrapd -f -Lo -c /etc/snmp/snmptrapd.conf 127.0.0.1:162收到Trap后日志大概长这样2024-06-12 14:32:05 192.168.1.100(via UDP) TRAP, SNMP v1, community public enterprises.38391.1.1.5.0 Enterprise Specific Trap (5) Uptime: ...说明设备在超限时确实能主动上报。实际部署中网管的告警平台收到Trap后再发短信/企业微信通知这就是另一套联动的活了。我的体会是SNMP的轮询和Trap一定要配合使用。Trap负责第一时间上报异常轮询负责定期确认设备还活着、当前数值是多少。两条腿走路才能真正把SNMP用明白。5. 大屏接入与联动配置数据到了大屏只是开始5.1 数据汇聚与存储设计TCP通道和SNMP通道采集到的数据要汇聚到同一个地方。我这边是双重写入最新值直接放内存历史数据定时落库。落库用SQLite就够了机房温湿度数据量很小一天也就86400条左右SQLite一点压力都没有。库表设计我用了最简单的这张表CREATE TABLE env_history ( id INTEGER PRIMARY KEY AUTOINCREMENT, temp REAL, hum REAL, source TEXT, -- tcp 或 snmp create_time DATETIME DEFAULT CURRENT_TIMESTAMP );这里有个细节TCP和SNMP采集到的数据可能会略有差异。TCP是设备按需返回SNMP是Agent查询结果两者本质上是同一块传感器理论上应该一致但不同时间点读到的值本身就会波动。所以我落库时把数据来源标记清楚便于后面排查差异问题。5.2 大屏与告警联动大屏不止要显示温度、湿度还要显示设备和通信状态。我单独定义了一个“健康度”指标包括TCP连接状态正常/断开SNMP轮询状态正常/超时最近一次数据更新时间多少秒前这些状态叠加起来比单纯显示一个温度更有价值。大屏前端可以这样展示TCP正常时数据卡片绿色TCP断了但SNMP还能读到数据时显示黄色同时提示“TCP通道异常正在使用SNMP备用数据”两个通道都断了显示红色。告警联动分两级设备本身超限告警通过SNMP Trap率先上报到网管平台。采集服务判断连续多次TCP读取失败这时候去SNMP轮询确认如果SNMP也失败直接在大屏置红并推送告警到工作群。5.3 双协议数据一致性校验双协议融合之后必须有一个自检机制。我在采集服务里加了一个定时任务每隔10分钟做一次双通道数据对比从TCP通道读取当前温度、湿度。同时用snmpget读取同一时刻的温度、湿度。如果温度相差超过0.5℃或湿度相差超过5%RH就记录一条告警日志。这个设计帮我排查过一次问题当时设备探头有点受潮TCP读到的湿度和SNMP读到的湿度出现了明显偏差如果没有交叉校验这个问题会一直潜伏下去直到温湿度明显异常才暴露。双协议的价值在这种场景下体现得特别明显——它不只是备用更是一套可以互相矫正的监控体系。6. 常见问题与排查技巧实录6.1 高频问题速查现象可能原因排查思路与解决办法TCP connect超时设备IP冲突或网线松动设备未开机ping设备IP是否通查看设备指示灯检查交换机端口是否被禁用TCP连接频繁断开设备端TCP保活时间设置过短或客户端未做心跳保活客户端定期发送指令设备端开启TCP keepalive减少中间NAT设备影响大屏数据长时间不变TCP读取停滞可能半包或粘包导致解析异常检查应用层心跳与超时机制打印原始hex报文查看应答是否正常SNMP get超时设备SNMP Agent未启动或团体名错误snmpget -v2c -c public测试检查管理页SNMP开关检查防火墙UDP 161端口SNMP返回值和TCP返回值不一致探头采集时序不同或换算公式理解错误核对两个通道各自拿到的是瞬时值还是缓存值检查MIB中采样时间Windows下没有snmpwalk命令net-snmp工具未安装安装net-snmp或用第三方MIB浏览器工具代替设备重启后TCP重连失败设备重启后TCP Server端口短暂不可用重试机制加延迟退避1s、2s、4s、8s最多尝试32s大屏显示湿度负数探头故障或接线问题查看设备本地LCD显示确认是设备问题还是解析问题6.2 调试中的三个小技巧技巧一tcpdump抓包定位问题。排查TCP和SNMP问题时直接用tcpdump抓包看最直观tcpdump -i eth0 host 192.168.1.100 and (port 5000 or port 161) -w cap.pcap抓完用Wireshark打开重点看TCP的SYN/SYN-ACK/ACK三次握手是否完成看SNMP的GetRequest和Response报文有没有对应上。有一次我排查SNMP时发现设备回复了Response但内容老是“No Such Instance”抓包一看才发现是OID写错了MIB文档上的节点编号和设备的实际实现不一致。技巧二用脚本模拟大屏轮询。前端大屏数据更新问题不一定非要在浏览器里排查。我在后端直接写一个curl脚本模拟大屏请求while true; do curl -s http://127.0.0.1:18080/api/env echo sleep 1 done这样能先把后端服务的返回逻辑测通再让大屏前端对接。一次排查大屏数据不刷新时最后发现是后端服务OOM被系统杀了用这个命令一下就暴露了。技巧三TCP重传风暴和dup ack的排查。如果tcpdump抓包看到大量TCP Dup ACK或重传基本可以断定网络链路有问题可能是交换机端口协商成了半双工、网线质量差、或设备网卡故障。机房这种环境内网交换机一般不至于出这个错但设备自带的是普通RJ45网口如果用的网线超过20米且质量差重传率会明显上升。换根好点的超五类线问题就消失了。6.3 我给新手的建议如果是从零开始做这类项目我的建议是先别急着玩“双协议融合”分两步走。第一步先把TCP通道单独打通实现“记录仪→采集程序→大屏”的完整链路。这一步跑通后你对设备协议、解析逻辑、大屏展示就都有底了。第二步再在已经稳定的TCP链路上叠加SNMP通道。SNMP配置完验证OID和Trap确认网管平台能收到数据和告警。两条路都通了最后再做数据一致性校验。我在实际项目中最大的体会是TCP和SNMP不是竞争关系它们是互补的。TCP解决“实时和交互”SNMP解决“标准和告警”。双协议融合部署的核心不是把两个东西硬塞在一起而是让它们各管一段、各司其职最后在一个服务层完成数据汇聚和统一展示。这套架构跑下来已经快半年了除了那一次探头受潮问题被交叉校验揪出来之外整体非常稳。最后再分享一个小技巧设备上电初始化时TCP连接不一定会立刻恢复我给的方案是采集程序启动后先做5次快速重试间隔1秒如果还是失败就进入30秒一次的慢重试循环。这个策略在大批设备同时断电恢复时特别管用不会出现“几十台设备同时重连把采集服务挤爆”的情况。如果后续设备数量继续增加还可以考虑在前端加一层nginx做TCP反向代理统一接入四层转发加上连接数限制可以扛住更大的并发——不过那是另一个项目的事了。