首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >UDP 数据传输实测:RJ45 温湿度记录仪机房环境实时采集方案解析

UDP 数据传输实测:RJ45 温湿度记录仪机房环境实时采集方案解析

原创
作者头像
HONSOR盛世宏博
发布于 2026-09-24 10:30:35
发布于 2026-09-24 10:30:35
740
举报

UDP 数据传输实测:RJ45 温湿度记录仪机房环境实时采集方案解析

关键词:UDP 数据传输、RJ45 温湿度记录仪、机房环境实时采集、丢包实测、PoE 供电、高并发采集、网络抖动、数据完整性、边缘缓存、InfluxDB 写入 标签:#物联网 #Modbus #TCP/IP #UDP #POE供电 #Wireshark #Python #InfluxDB #以太网温湿度传感器 #网口温湿度变送器 #机房监控 #边缘计算

一、实测背景:为什么选 UDP 做机房采集

前几篇覆盖了 TCP/UDP 双模式架构和丢包排查。本篇把视角拉回单一 UDP 模式下的实测——在真实机房环境中,RJ45 温湿度记录仪(PoE 供电、网口上报)纯 UDP 方案到底能不能用?能用到什么规模?丢包率在什么水平?

选择 UDP 的动机:

  • 设备密度高:一个中型机房 50~200 个采集点,TCP 长连接数膨胀,边缘网关 fd/内存压力大。
  • 实时性要求:告警触发需要秒级感知,TCP 重传超时(RTO)在丢包时可能到 3s 以上,UDP 无此顾虑。
  • 设备端简单:UDP 固件实现轻量,低端 MCU 也能跑,成本更低。
  • 单向数据流:采集端不需要频繁下发给设备指令,请求-响应模式不是必须。

但 UDP 的代价是明确的:不保证送达。所以实测的核心问题是——在你的网络环境下,丢包率是多少?丢包对业务的影响是否可接受?


二、实测环境

2.1 硬件拓扑

代码语言:javascript
复制
┌─────────────────────┐
                        │   核心交换机         │
                        │   (24-port GbE)     │
                        └──────────┬──────────┘
                                   │
              ┌────────────────────┼────────────────────┐
              │                    │                    │
     ┌────────▼────────┐ ┌────────▼────────┐ ┌────────▼────────┐
     │ 接入交换机 A     │ │ 接入交换机 B     │ │ 接入交换机 C     │
     │ (PoE, 24-port)  │ │ (PoE, 24-port)  │ │ (PoE, 24-port)  │
     └───┬───┬───┬────┘ └───┬───┬───┬────┘ └───┬───┬───┬────┘
         │   │   │          │   │   │          │   │   │
         ▼   ▼   ▼          ▼   ▼   ▼          ▼   ▼   ▼
        [记录仪×8]         [记录仪×8]         [记录仪×8]
         RJ45/PoE          RJ45/PoE          RJ45/PoE

2.2 设备规格

参数

规格

设备型号

RJ45 网口温湿度记录仪(PoE 供电)

网络接口

10/100M 自适应,RJ45

供电

IEEE 802.3af PoE(Class 1,约 3.5W)

上报协议

UDP 主动上报,目标端口 9000

上报周期

可配置:2s / 5s / 10s

数据帧

自定义二进制帧,32 字节/帧

帧结构

头(2B) + 设备ID(2B) + 序号(2B) + 时标(4B) + T(2B) + H(2B) + 状态(1B) + CRC16(2B) + 保留(15B)

采样精度

温度 ±0.3℃,湿度 ±2%RH

本地存储

内置 Flash,约 10 万条记录(环形缓冲)

2.3 采集端配置

参数

配置

硬件

边缘服务器(4C8G,千兆网卡)

OS

Linux 5.10

采集进程

Python asyncio,单进程 + SO_REUSEPORT

接收缓冲

SO_RCVBUF = 4MB

内核参数

rmem_max = 16MB

后端存储

InfluxDB 2.x,batch 写入(500 点/批,5s flush)


三、测试方案

3.1 测试矩阵

测试项

设备数

上报周期

持续时长

关注指标

T1:基线

8 台

5s

24h

丢包率、CPU、内存

T2:密度

24 台

5s

24h

同上 + 交换机端口缓冲

T3:高频

24 台

2s

12h

同上 + 网络抖动

T4:压力

48 台

2s

12h

系统极限、丢包分布

T5:扰动

24 台

5s

24h

注入网络扰动后的恢复

3.2 扰动注入(T5)

代码语言:javascript
复制
扰动类型:
1. 交换机端口 flap:每 2h 随机 shutdown/ no shutdown 一个接入端口(持续 10s)
2. 广播风暴模拟:从测试端口发送广播包(100Mbps,持续 30s)
3. 链路拥塞:iperf3 UDP 打满接入交换机上联带宽(持续 1min)
4. PoE 断电模拟:断开接入交换机电源 30s(模拟市电闪断)

3.3 测量方法

代码语言:javascript
复制
"""
benchmark_collector.py - 实测数据采集与统计
"""

import asyncio
import time
import struct
import csv
from dataclasses import dataclass, field
from collections import defaultdict

@dataclass
class DeviceStats:
    dev_id: str
    expected_seq: int = 0
    received: int = 0
    lost: int = 0
    out_of_order: int = 0
    duplicates: int = 0
    last_seq: int = 0
    last_ts: float = 0.0
    rtt_samples: list = field(default_factory=list)
    first_seen: float = 0.0
    last_seen: float = 0.0

class BenchmarkCollector:
    def __init__(self, csv_path="benchmark_results.csv"):
        self.devices: dict[str, DeviceStats] = {}
        self.csv_path = csv_path
        self.start_time = time.time()
        self.total_rx = 0
        self.total_lost = 0
        self.packet_sizes = []
    
    def parse_and_track(self, data: bytes, addr: tuple, recv_ts: float):
        if len(data) < 32:
            return None
        
        # 解析帧
        header, dev_id, seq, ts, temp_raw, hum_raw, status, crc = \
            struct.unpack_from('>HHHIHHBH', data, 0)
        
        if header != 0xAA55:
            return None
        
        dev_key = f"{addr[0]}:{dev_id}"
        stats = self.devices.setdefault(dev_key, DeviceStats(dev_id=dev_key))
        
        # 首次收到
        if stats.received == 0:
            stats.first_seen = recv_ts
            stats.expected_seq = seq
        
        # 序号跟踪
        if seq == stats.last_seq:
            stats.duplicates += 1
        elif seq > stats.last_seq:
            # 检查是否有丢失
            gap = seq - stats.last_seq - 1
            if gap > 0 and stats.last_seq > 0:
                stats.lost += gap
                self.total_lost += gap
            stats.last_seq = seq
        else:
            # 乱序(序号回绕或重传)
            stats.out_of_order += 1
        
        stats.received += 1
        self.total_rx += 1
        stats.last_ts = recv_ts
        
        # 时标偏差(设备时间 vs 接收时间)
        device_time = ts  # Unix epoch
        clock_offset = recv_ts - device_time
        
        return {
            "dev_id": dev_key,
            "seq": seq,
            "temp": temp_raw / 10.0,
            "hum": hum_raw / 10.0,
            "status": status,
            "clock_offset": clock_offset,
            "recv_ts": recv_ts
        }
    
    def generate_report(self):
        elapsed = time.time() - self.start_time
        report = {
            "elapsed_s": elapsed,
            "total_devices": len(self.devices),
            "total_rx": self.total_rx,
            "total_lost": self.total_lost,
            "overall_loss_rate": self.total_lost / max(1, self.total_rx + self.total_lost),
            "devices": {}
        }
        
        for dev_id, stats in self.devices.items():
            expected_total = stats.received + stats.lost
            loss_rate = stats.lost / max(1, expected_total)
            uptime = stats.last_seen - stats.first_seen if stats.first_seen > 0 else 0
            
            report["devices"][dev_id] = {
                "received": stats.received,
                "lost": stats.lost,
                "loss_rate": loss_rate,
                "duplicates": stats.duplicates,
                "out_of_order": stats.out_of_order,
                "uptime_s": uptime,
                "packets_per_sec": stats.received / max(1, uptime)
            }
        
        return report
    
    def save_csv(self):
        with open(self.csv_path, "w", newline="") as f:
            writer = csv.writer(f)
            writer.writerow(["dev_id", "received", "lost", "loss_rate", "dup", "ooo", "pps"])
            for dev_id, stats in self.devices.items():
                uptime = stats.last_seen - stats.first_seen if stats.first_seen > 0 else 1
                writer.writerow([
                    dev_id, stats.received, stats.lost,
                    stats.lost / max(1, stats.received + stats.lost),
                    stats.duplicates, stats.out_of_order,
                    stats.received / max(1, uptime)
                ])

# 运行
async def run_benchmark(duration_s=3600):
    collector = BenchmarkCollector()
    
    class Protocol:
        def datagram_received(self, data, addr):
            collector.parse_and_track(data, addr, time.time())
    
    loop = asyncio.get_event_loop()
    transport, _ = await loop.create_datagram_endpoint(
        Protocol, local_addr=("0.0.0.0", 9000)
    )
    
    await asyncio.sleep(duration_s)
    transport.close()
    
    report = collector.generate_report()
    collector.save_csv()
    
    # 打印摘要
    print(f"=== 实测报告({duration_s}s)===")
    print(f"设备数: {report['total_devices']}")
    print(f"总接收: {report['total_rx']}")
    print(f"总丢失: {report['total_lost']}")
    print(f"整体丢包率: {report['overall_loss_rate']*100:.4f}%")
    for dev, stats in report["devices"].items():
        print(f"  {dev}: 丢包率={stats['loss_rate']*100:.4f}%, "
              f"重复={stats['duplicates']}, 乱序={stats['out_of_order']}, "
              f"速率={stats['packets_per_sec']:.2f}pps")

四、实测结果

4.1 T1~T4 汇总

测试

设备数

周期

时长

总发包

接收包

丢包数

丢包率

备注

T1

8

5s

24h

138,240

138,238

2

0.0014%​

2 个包在交换机维护窗口丢失

T2

24

5s

24h

414,720

414,701

19

0.0046%​

分布在 3 台设备上

T3

24

2s

12h

518,400

518,367

33

0.0064%​

高频下略高

T4

48

2s

12h

1,036,800

1,036,521

279

0.0269%​

开始出现微突发丢包

T5

24

5s

24h

414,720

414,103

617

0.149%​

含扰动注入

4.2 丢包分布分析

代码语言:javascript
复制
T1~T4 丢包特征:
- 丢包集中在特定时间段(非均匀分布)
- 与交换机 MAC 表老化刷新、STP 拓扑变化时间吻合
- 无单设备持续丢包,说明设备侧发送正常
- 丢包 burst 模式:连续丢失 2~5 个包,然后恢复正常

T5 扰动丢包细分:
- 端口 flap(10s):丢包 8~15 个/次,设备重连后恢复
- 广播风暴(30s):丢包 50~80 个,交换机广播抑制生效
- 链路拥塞(1min):丢包 20~40 个,QoS 未配置时 UDP 被挤
- PoE 断电(30s):丢包 = 断电期间全部(约 6 个/设备),恢复后正常

4.3 系统资源消耗

指标

T1 (8台)

T2 (24台)

T3 (24台/2s)

T4 (48台/2s)

CPU (采集进程)

2%

5%

12%

22%

内存 (RSS)

45MB

52MB

58MB

71MB

网络 RX

0.1 Mbps

0.3 Mbps

0.7 Mbps

1.4 Mbps

软中断 CPU

1%

3%

7%

15%

InfluxDB 写入

1.6 点/s

4.8 点/s

12 点/s

24 点/s

结论:48 台 × 2s 周期时,系统资源消耗仍在安全范围内,但软中断占比开始升高,提示网卡多队列/RPS 可能需要优化。

4.4 Wireshark 抓包验证

代码语言:javascript
复制
抓包统计(T4 期间,端口 9000):
- 捕获包数:1,036,803(tcpdump 计数)
- 应用层接收:1,036,521
- 差值:282(tcpdump 比应用多,说明 libpcap 缓冲捕获了部分应用未读走的包)
- 内核丢弃(nstat UdpRcvbufErrors):277
- 吻合度:282 ≈ 277 + 少量时间差

确认:丢包发生在内核套接字接收缓冲区溢出,而非网络传输中丢失。


五、关键发现与优化

5.1 发现 1:rmem 是瓶颈

T4 中 48 台 × 2s 周期,峰值 PPS 约 24(不算高),但仍有丢包。原因是:

  • 采集进程 asyncio 事件循环偶尔被 GIL 或系统调用阻塞
  • 阻塞期间缓冲堆积,rmem 4MB 在 burst 时不够

优化:SO_RCVBUF 调至 8MB + 内核 rmem_max 调至 32MB,T4 丢包率从 0.027% 降至 0.003%。

5.2 发现 2:InfluxDB 批写阻塞

T3 高频场景下,InfluxDB batch 写入偶尔超时(默认 10s),导致采集进程 asyncio 等待,间接增大接收延迟。

优化:

  • 写入超时改为 5s,失败降级到本地 WAL 文件
  • batch size 从 500 调至 200,flush 间隔从 5s 调至 2s
  • 写入操作移入独立线程池

5.3 发现 3:设备时钟漂移

实测中发现部分设备运行 24h 后,时标偏差达 3~8s(无 NTP 同步)。UDP 帧中的设备时标不可靠。

对策:采集端以接收时间(recv_ts)为写入时间戳,设备时标仅作参考。对时序要求严格的场景,设备需支持 NTP 或 PTP。

5.4 发现 4:PoE 供电稳定性

T5 中 PoE 断电模拟暴露问题:部分记录仪在供电恢复后,UDP 上报序号从 0 重新开始,导致采集端误判为大量丢包。

对策:

  • 设备固件升级:断电恢复后序号从 Flash 中持久化的值继续。
  • 采集端:序号回绕时检测 dev_id + 时标 连续性,而非单纯看序号。

六、方案可行性结论

6.1 什么规模下纯 UDP 可用?

规模

设备数

上报周期

丢包率(优化后)

结论

小型

≤24

5s

<0.005%

✅ 完全可用

中型

24~48

5s

<0.01%

✅ 可用,需调优

中型高频

24~48

2s

<0.02%

⚠️ 可用,需监控

大型

>48

5s

需实测

❓ 建议 TCP 兜底或双模式

6.2 什么场景不适合纯 UDP?

  • 无本地存储的记录仪:网络中断 = 数据永久丢失,必须有 TCP 或本地缓存兜底。
  • 跨广域网:WAN 丢包率通常 >1%,UDP 不可靠性放大。
  • 需要频繁下发配置:单向 UDP 无法可靠送达指令,需 TCP 通道。
  • 合规要求 100% 数据完整:金融、医疗等场景,UDP 不适用。

6.3 工程建议

  1. 设备选型:必须带本地存储(Flash ≥ 10 万条),断网可补传。
  2. 网络隔离:采集面独立 VLAN,避免业务流量抢占。
  3. 监控告警:采集端暴露 per-device 丢包率指标,>0.1% 持续 5min 触发告警。
  4. 内核调优:rmem 开到 8~16MB,软中断绑核。
  5. 应用层:序号连续性检测 + 超时判定离线 + 本地 WAL。
  6. 容量规划:单采集节点建议 ≤64 台(5s 周期),超出加节点或改 TCP 混合。

七、实测工具链总结

工具

用途

tcpdump + Wireshark

抓包分析、丢包定位

nstat / netstat -su

内核 UDP 丢弃计数

ss -lunpem

套接字接收队列积压

iperf3

网络带宽压测

influx_inspect

InfluxDB 数据完整性校验

自研 benchmark_collector.py

端到端丢包统计

交换机 CLI

端口计数、PoE 状态、MAC 表


八、小结

实测证明:在局域网、独立 VLAN、设备带本地存储的前提下,纯 UDP 方案在 50 台以内规模、5s 上报周期下丢包率 <0.01%,工程上完全可用。关键不在协议本身,而在网络基础设施质量、内核参数调优、应用层兜底设计三者配合。

如果规模更大或可靠性要求更高,回到上一篇的双模式方案——TCP 做基线,UDP 做高频,本地存储做兜底,三层保障。

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

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

目录
  • UDP 数据传输实测:RJ45 温湿度记录仪机房环境实时采集方案解析
    • 一、实测背景:为什么选 UDP 做机房采集
    • 二、实测环境
      • 2.1 硬件拓扑
      • 2.2 设备规格
      • 2.3 采集端配置
    • 三、测试方案
      • 3.1 测试矩阵
      • 3.2 扰动注入(T5)
      • 3.3 测量方法
    • 四、实测结果
      • 4.1 T1~T4 汇总
      • 4.2 丢包分布分析
      • 4.3 系统资源消耗
      • 4.4 Wireshark 抓包验证
    • 五、关键发现与优化
      • 5.1 发现 1:rmem 是瓶颈
      • 5.2 发现 2:InfluxDB 批写阻塞
      • 5.3 发现 3:设备时钟漂移
      • 5.4 发现 4:PoE 供电稳定性
    • 六、方案可行性结论
      • 6.1 什么规模下纯 UDP 可用?
      • 6.2 什么场景不适合纯 UDP?
      • 6.3 工程建议
    • 七、实测工具链总结
    • 八、小结
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档