首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >机房物联网采集:POE 温湿度传感器 UDP 数据包抓包与丢包排查实战

机房物联网采集:POE 温湿度传感器 UDP 数据包抓包与丢包排查实战

原创
作者头像
HONSOR盛世宏博
发布于 2026-09-23 17:37:59
发布于 2026-09-23 17:37:59
860
举报

机房物联网采集:POE 温湿度传感器 UDP 数据包抓包与丢包排查实战

关键词:机房物联网采集、PoE 温湿度传感器、UDP 数据包、抓包分析、丢包排查、网络抖动、缓冲区溢出、Modbus UDP、syslog 上报、边缘采集 标签:#物联网 #Modbus #TCP/IP #UDP #POE供电 #Wireshark #Python #InfluxDB #以太网温湿度传感器 #网口温湿度变送器 #机房监控 #边缘计算

一、先厘清场景:UDP 在机房采集链路里出现在哪

PoE 网口温湿度变送器,南向通信通常是 Modbus TCP(TCP/502),但工程现场出现 UDP 的地方有几类,排查前先定位:

  1. 设备固件走私有 UDP 上报:部分厂家网口变送器支持主动上报模式,定时向采集端发 UDP 数据报,端口自定义(如 9000/UDP),载荷为私有帧或简化 Modbus 封装。
  2. 边缘网关转发层:采集服务经 UDP 中继、mirror、syslog-ng 转发,或自研 agent 用 UDP socket 上报平台。
  3. BACnet/IP 广播/通知:BACnet 用 UDP/47808,但温湿度点读取走 ReadProperty,通知走 COV;别和采集面混淆。
  4. SNMP trap / syslog:交换机 PoE 状态、端口 up/down 经 UDP 上送。
  5. 自研采集协议:为降开销,采集端用 UDP 请求-响应或推模式。

本文以最常见情形展开:PoE 温湿度变送器主动 UDP 上报 + 边缘采集进程接收,辅以抓包定位丢包环节。TCP 场景此前已覆盖,这里聚焦 UDP 特有故障面。

二、排查框架:丢包可能在五层中的任一层

别上来就 tcpdump。先分层定位,再下钻。

代码语言:javascript
复制
应用层:采集进程 socket 未读走 / 处理逻辑阻塞 / 缓冲满丢弃
────────────────────────────────────────────────────
传输层:UDP 无重传,socket 接收缓冲区溢出(rmem)、recvmsg 不及时
────────────────────────────────────────────────────
网络层:路由/ACL/多播/源地址校验、conntrack 误丢、RPFilter
────────────────────────────────────────────────────
链路层:PoE 端口丢包、交换机缓冲拥塞、VLAN 错放、镜像口截断
────────────────────────────────────────────────────
物理层:PoE 协商降额、线缆、端口错连、双工不匹配

判定顺序:本机抓包看是否收到 → 应用层看是否处理 → 内核/套接字看丢弃计数 → 网络设备看端口计数。

三、环境准备与抓包

3.1 确认采集端监听与设备发送端

代码语言:javascript
复制
# 采集端监听 UDP 端口
ss -lunp | grep 9000
# 或
netstat -lunp | grep 9000

# 确认设备配置的上报目的 IP/端口(Web/CLI/配置工具)
# 同时确认源端口随机还是固定,影响抓包过滤

注意:UDP 无连接,监听套接字只要绑定 0.0.0.0:9000 即可收;若绑定特定 IP,跨网段时源进不来。确认防火墙/iptables/nftables 放行,SELinux/kysec 放行。

3.2 tcpdump 抓包

代码语言:javascript
复制
# 主机侧抓,保留完整载荷
sudo tcpdump -i eth0 -nn -vv 'udp port 9000' -s0 -B 4096 -w /tmp/udp_cap.pcap

# 同时抓 ICMP(看分片/不可达)
sudo tcpdump -i eth0 -nn '(udp port 9000) or icmp' -s0 -w /tmp/udp_cap.pcap

# 多接口/VLAN 时指定
sudo tcpdump -i any -nn 'vlan and udp port 9000' -s0 -w /tmp/udp_cap.pcap
  • -B 增大抓包缓冲,避免 tcpdump 自身丢包。
  • 后台跑时加 -C 100 -W 5 滚动,或交送 ringbuffer。
  • 别在采集进程同机高负载时跑,libpcap 开销会干扰。

3.3 Wireshark 分析要点

打开 pcap,过滤 udp.port==9000。关注:

  • 报文是否到达:捕获中是否有设备源 IP 发来的 UDP datagram。
  • 目的 MAC/IP 是否正确:目的 IP 是否为采集端,目的 MAC 是否经路由/直连。
  • 载荷长度:len 字段,是否截断;IP total length、UDP length 校验。
  • 发送节奏:按设备上报周期(如 5s)应有规律间隔,看 IAT(包间隔)。
  • 源端口变化:每次上报源端口可能变,别在过滤时写死。
  • Checksum:UDP checksum verification disabled by capture driver 时别误判;开启时校验失败说明中间修改或 offload。
  • 重复/乱序:UDP 无序号,应用层若需保序自行加序号;抓包看是否有明显重发(设备层重发逻辑)。

辅助过滤:

代码语言:javascript
复制
udp.dstport == 9000
ip.src == 10.20.1.11
udp.length < 64   # 异常小包

四、定位丢包环节

4.1 抓到了,但应用没收到 —— 本机传输/应用层

分两种情况:

(a) tcpdump 看到,应用层没处理

  • 进程是否真在监听该端口、该地址;容器/Pod 端口映射、hostNetwork 问题。
  • 多 NIC 时源 IP 进错表;反向路径过滤(RPFilter)丢包。
  • SELinux/nftables 在 Netfilter 层丢弃,tcpdump 在 AF_PACKET 层抓到,应用层收不到——这是经典坑。

核查:

代码语言:javascript
复制
# 接收队列是否有积压
cat /proc/net/udp | awk 'NR>1{print $2,$4,$5}'   # hex lport, rx_queue, drops
# 或更直观
ss -lunpem

(b) 内核套接字丢弃计数

代码语言:javascript
复制
# nstat / netstat -su
nstat -az | grep -E 'Udp|RcvbufErrors|InErrors|Drops'
netstat -su | grep -E 'receive errors|RcvbufErrors|dropped'

关键计数器:

  • UdpRcvbufErrors / RcvbufErrors:套接字接收缓冲满,内核丢。
  • UdpInErrors:校验和、长度错误。
  • packet receive errors(ss -lunpem 显示 drops)。

4.2 没抓到 —— 网络/链路层

  • 镜像口没配好,或抓包在错误接口;确认管理面与数据面。
  • 交换机 ACL/ACL 限速、CoPP 丢控制面无关,但数据面端口隔离。
  • VLAN:设备在上联 VLAN 20,采集端在 VLAN 10,跨 VLAN 未路由或 ACL 拦 UDP。
  • PoE 端口供电降额导致设备重启/离线,根本没发——查交换机 PoE 计数器。
  • 组播/广播上报时,IGMP snooping、未知单播泛洪限制。

交换机侧核查(以 Linux 网桥/switchdev 或厂商 CLI 为例):

代码语言:javascript
复制
show interfaces counters errors
show poe power-usage
show mac address-table | include <sensor_mac>
show spanning-tree inconsistentports

4.3 收到了但乱序/重复/部分丢失 —— 应用层协议面

UDP 上报天然可能丢,需区分:

  • 网络层丢包:链路口错误、拥塞丢包(交换机 egress 缓冲)。
  • 主机侧丢包:中断亲和、NAPI、软中断瓶颈,高 PPS 时 userspace 来不及 recv。
  • 应用层丢包:单线程阻塞处理,epoll 边缘触发未 drained,Python asyncio 协程堆积。

五、工程化接收与统计

5.1 Python 采集端参考实现

代码语言:javascript
复制
"""
udp_collector.py - PoE 温湿度传感器 UDP 上报采集
- SO_RCVBUF 调大、非阻塞事件循环批量 recv
- 解析私有帧(示例:头0xAA 0x55 + dev_id + t + h)
- 统计丢包:按设备上报序号缺口 + 周期超时未到
"""

import asyncio, struct, logging, time
from influxdb_client import InfluxDBClient, Point
from influxdb_client.client.write_api import SYNCHRONOUS

HOST = "0.0.0.0"
PORT = 9000
INFLUX = {"url":"http://localhost:8086","token":"","org":"dc","bucket":"env"}

logging.basicConfig(level=logging.INFO)

def parse_frame(data: bytes):
    if len(data) < 12 or data[0]!=0xAA or data[1]!=0x55:
        return None
    dev_id, seq = struct.unpack_from("<HH", data, 2)
    t_raw, h_raw = struct.unpack_from("<hh", data, 6)
    t = t_raw/10.0; h = h_raw/10.0
    return {"dev_id":dev_id,"seq":seq,"t":t,"h":h}

class Collector:
    def __init__(self):
        self.transport = None
        self.last_seq = {}      # dev_id -> last seq
        self.last_ts = {}       # dev_id -> last recv mono time
        self.expected_period = 5.0
        self.influx = InfluxDBClient(**INFLUX) if False else \
            InfluxDBClient(url=INFLUX["url"],token=INFLUX["token"],org=INFLUX["org"])
        self.wapi = self.influx.write_api(write_options=SYNCHRONOUS)
        self.rx = 0
        self.app_drop = 0

    def connection_made(self, transport):
        self.transport = transport
        sock = transport.get_extra_info("socket")
        if sock:
            sock.setsockopt(__import__("socket").SOL_SOCKET,
                           __import__("socket").SO_RCVBUF, 4<<20)

    def datagram_received(self, data, addr):
        self.rx += 1
        pkt = parse_frame(data)
        if pkt is None:
            logging.debug("bad frame from %s", addr)
            return
        dev = pkt["dev_id"]
        # 序号连续性检查
        ls = self.last_seq.get(dev)
        if ls is not None and pkt["seq"] != (ls+1) & 0xFFFF:
            gap = (pkt["seq"] - ls) & 0xFFFF
            if gap != 1:
                logging.warning("dev %s seq gap: last=%s got=%s", dev, ls, pkt["seq"])
        self.last_seq[dev] = pkt["seq"]
        self.last_ts[dev] = time.monotonic()
        p = Point("env").tag("dev", str(dev)).field("temperature",pkt["t"]).field("humidity",pkt["h"]).field("quality",1)
        try:
            self.wapi.write(bucket=INFLUX["bucket"],org=INFLUX["org"],record=p)
        except Exception as e:
            self.app_drop += 1
            logging.warning("write fail: %s", e)

    def poll_watchdog(self):
        # 周期外未收到即判丢
        now = time.monotonic()
        for dev, ts in self.last_ts.items():
            if now - ts > self.expected_period*3:
                p = Point("env").tag("dev",str(dev)).field("quality",0).field("stale",1)
                self.wapi.write(bucket=INFLUX["bucket"],org=INFLUX["org"],record=p)
                logging.warning("dev %s stale", dev)

async def main():
    loop = asyncio.get_event_loop()
    collector = Collector()
    transport, proto = await loop.create_datagram_endpoint(
        lambda: collector, local_addr=(HOST,PORT))
    try:
        while True:
            await asyncio.sleep(1)
    finally:
        transport.close()

if __name__ == "__main__":
    asyncio.run(main())

要点:

  • 别在回调里做重 IO;写 InfluxDB 批量化或交异步队列。
  • SO_RCVBUF 用户态设置受内核 net.core.rmem_max 限制,需系统侧配合。
  • Python asyncio UDP 协议工厂在 3.11+ 稳定,注意事件循环亲和。
  • 多进程时避免多绑定同端口无 SO_REUSEPORT,否则分发不均。

5.2 内核/系统侧调优

代码语言:javascript
复制
# 接收缓冲上限
sysctl -w net.core.rmem_max=16777216
sysctl -w net.core.rmem_default=4194304
# UDP 特定
sysctl -w net.ipv4.udp_rmem_min=16384
# 中断/RPS
irqbalance off + 手动绑核;多队列网卡 RPS/RFS
# RPFilter
sysctl -w net.ipv4.conf.all.rp_filter=2   # loose,或按环境设0/1
# conntrack 对 UDP 短时流无妨,但别让 NOTRACK 缺失导致误丢

应用层 socket 选项:

代码语言:javascript
复制
// 概念参考,Python 侧已示
setsockopt(SOL_SOCKET, SO_RCVBUF, 4MB);
// 更大后端缓冲:应用层环形队列 + 批处理

六、丢包复现与验证

6.1 主动压测

代码语言:javascript
复制
# 用 scapy/nping 模拟设备侧多发
nping --udp -p 9000 --rate 500 --count 10000 10.20.1.250
# 或自研发送端回放 pcap
tcpreplay-edit --intf1=eth0 --portmap=9000:9000 /tmp/udp_cap.pcap

观察:

  • 抓包计数 vs 应用处理计数 vs InfluxDB 写入点数。
  • nstat 计数器增量。
  • 软中断 CPU 占比 /proc/softirqs。

6.2 端到端对账

代码语言:javascript
复制
发送端日志/计数器 N_send
交换机端口出方向计数
采集端 tcpdump 计数 (tcpdump -i eth0 -nn 'udp port 9000' | wc -l 不准确,用 capinfos)
应用层 rx 计数
平台侧入库点数

差额在哪段,就修哪段。常见组合:

  • 发送<N_send:设备/边缘转发丢。
  • 抓包<N_出方向:网络链路/ACL。
  • 应用rx<抓包:套接字/内核丢弃。
  • 入库<rx:处理阻塞或批写失败。

6.3 BPF/ebpf 辅助定位

代码语言:javascript
复制
# 内核 drop 点跟踪(bcc/bioprobe)
biosnoop / funccount 'udp_rcv'
# 或简单看 tracepoint
perf trace -e 'net:*' -p $(pidof collector)

不展开,排障时按需。

七、设备侧与交换机侧协同

7.1 PoE 供电相关

  • PoE 协商后设备重启,UDP 上报中断;交换机 show poe 看重置计数。
  • 接入交换机 PoE 控制器过热降额,端口周期性掉电——此时抓包会看到设备 ARP 重学、DHCP 请求。
  • 建议:关键采集点独立 PoE 预算,或外部 DC 供电。

7.2 交换机端口

  • 端口缓冲小,突发上报微突发丢包;开启风暴控制反而误伤,谨慎。
  • 出向队列调度:数据面与管理面共口时,DSCP 标记 + QoS 保障。
  • 镜像口截断:SPAN 会话缓冲满丢镜象包,别把抓包当真实面。
  • ACL 状态化防火墙对 UDP 返回流未放行:采集端回包(若有 ACK 式响应)被拦。

7.3 采集服务韧性

  • UDP 无连接,采集端需心跳/超时判定离线,别依赖 TCP 式断连事件。
  • 边缘侧本地缓存,断网/进程重启后补传;缓冲水位监控。
  • 多采集实例时,UDP 单播目的地址固定,避免多活脑裂;或用任播+状态外置。

八、典型坑

  1. 抓包在回环/管理口,实际流量在业务 VLAN 子接口——抓不到误判设备没发。
  2. tcpdump 没加 -i any,多队列时漏收;或 nsenter 进容器 netns 抓。
  3. 防火墙 rich-rule 允许但 nftables 默认 DROP,tcpdump 见包、应用收不到。
  4. SO_RCVBUF 设置后内核翻倍旧行为,以为调大实际未生效;用 getsockopt 校验。
  5. Python 回调阻塞写库,背压传不回套接字,缓冲满后静默丢。
  6. 设备私有帧无序号,丢包无法检测,靠周期超时——误报离线或滞后发现。
  7. NTP 未同步,时标错乱,InfluxDB 写入后曲线断层,误判采集丢包。
  8. 跨网段时设备广播发现后填错目的 IP,或目的为子网广播,采集端未绑多播组。
  9. UDP checksum offload 致 Wireshark 标记错误,实际正常,别过度处理。
  10. 容器平台 Calico/Cilium eBPF 转发路径绕过 netfilter,抓包点选错。

九、交付与运维

  • 交付物:抓包记录、计数器基线、点表/端口映射、调优参数、回滚配置。
  • 监控:采集端暴露 metrics(rx/s、drop/s、stale_devices),Grafana 面板。
  • SOP:丢包时分层定位脚本化;重启采集服务前 dump 状态,别盲目重启丢现场。
  • 容量:边缘节点 PPS 评估,预留余量;规模扩大时改批处理/多消费者。
  • 回退:UDP 不可靠,关键库区保留 Modbus TCP 轮询通道作冗余,双通道融合。

十、小结

UDP 采集排障,关键是把"丢包"拆开:抓包确认是否到达主机,内核/套接字计数确认是否丢弃,应用层确认是否处理,网络/交换机确认是否转发。工程上别指望 UDP 自带可靠,接收侧做缓冲、序号、超时、补传和双通道冗余;排障时 tcpdump+Wireshark+nstat+ss 四件套定位到层,再对症调优或改架构。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 机房物联网采集:POE 温湿度传感器 UDP 数据包抓包与丢包排查实战
    • 一、先厘清场景:UDP 在机房采集链路里出现在哪
    • 二、排查框架:丢包可能在五层中的任一层
    • 三、环境准备与抓包
      • 3.1 确认采集端监听与设备发送端
      • 3.2 tcpdump 抓包
      • 3.3 Wireshark 分析要点
    • 四、定位丢包环节
      • 4.1 抓到了,但应用没收到 —— 本机传输/应用层
      • 4.2 没抓到 —— 网络/链路层
      • 4.3 收到了但乱序/重复/部分丢失 —— 应用层协议面
    • 五、工程化接收与统计
      • 5.1 Python 采集端参考实现
      • 5.2 内核/系统侧调优
    • 六、丢包复现与验证
      • 6.1 主动压测
      • 6.2 端到端对账
      • 6.3 BPF/ebpf 辅助定位
    • 七、设备侧与交换机侧协同
      • 7.1 PoE 供电相关
      • 7.2 交换机端口
      • 7.3 采集服务韧性
    • 八、典型坑
    • 九、交付与运维
    • 十、小结
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档