
物联网 · 盛世宏博 · RJ45温湿度传感器、TCP与UDP协议对比、机房环境监控、通信方案选型

RJ45 以太网温湿度传感器上电配好 IP 就能 ping 通,但"通"不等于"好用"。传输层选 TCP 还是 UDP,直接决定了:
某数据中心机房项目,初期 48 台传感器全部采用 TCP 长连接,网关轮询采集。运行一周后发现问题:3 台传感器因网络闪断导致 TCP 连接半开不关,网关线程池耗尽,后续采集全部阻塞。改造成"关键区 TCP + 普通区 UDP + 边缘缓存补传"混合模式后,采集成功率从 96.3% 恢复到 99.95%,网关 CPU 占用从 78% 降到 22%。
维度 | TCP | UDP |
|---|---|---|
连接方式 | 面向连接(三次握手) | 无连接 |
可靠性 | 确认+重传,数据不丢不重不乱 | 尽最大努力交付,不保证到达 |
有序性 | 序列号保证有序 | 无序,后发可能先到 |
拥塞控制 | 有(慢启动、拥塞避免) | 无,想发就发 |
头部开销 | 20~60 字节 | 8 字节 |
连接状态 | 维护连接表、滑动窗口、缓冲区 | 无状态 |
单包大小 | 受 MSS 限制(通常 1460 字节) | 受 MTU 限制(通常 1472 字节) |
适用场景 | 控制指令、告警、配置下发 | 高频数据上报、广播发现 |
网关主动发起请求 → 传感器响应数据
TCP 方式:
网关与每台传感器建立 TCP 连接
发送读取命令(Modbus TCP 功能码 0x03)
等待响应,解析数据
连接可复用或每次新建
UDP 方式:
网关向传感器 UDP 端口发送请求包
传感器返回数据报
无连接,每个请求独立传感器定时主动发送数据 → 网关监听端口接收
TCP 方式:
传感器作为 Client 连接网关
建立长连接后持续推送
断线自动重连
UDP 方式:
传感器向网关 IP:Port 发送数据报
无需建立连接,定时发送
丢包不重传(除非应用层实现)场景 | 理由 |
|---|---|
控制指令下发(启停、参数修改) | 必须可靠,丢包后果严重 |
告警事件上报 | 不能丢,需要确认 |
设备配置(IP、采样周期) | 配置失败可能导致设备失联 |
关键区域(配电室、特藏库) | 数据完整性优先 |
设备数量 < 64 台 | TCP 连接数可控 |
class TcpPoller:
def __init__(self, sensors: list, pool_size=16):
self.sensors = sensors
self.semaphore = asyncio.Semaphore(pool_size)
self.timeout = 5
async def poll_one(self, sensor: dict):
async with self.semaphore:
try:
reader, writer = await asyncio.wait_for(
asyncio.open_connection(sensor['ip'], sensor.get('port', 502)),
timeout=3
)
# 发送 Modbus TCP 读取请求
req = build_modbus_read_request(sensor.get('slave', 1), 0, 2)
writer.write(req)
await writer.drain()
resp = await asyncio.wait_for(
reader.read(256), timeout=self.timeout
)
writer.close()
await writer.wait_closed()
if len(resp) < 9:
return {**sensor, 'status': 'SHORT_RESP'}
temp = struct.unpack('>h', resp[9:11])[0] / 10
hum = struct.unpack('>h', resp[11:13])[0] / 10
return {**sensor, 'temp': temp, 'hum': hum, 'status': 'OK'}
except asyncio.TimeoutError:
return {**sensor, 'status': 'TIMEOUT'}
except Exception as e:
return {**sensor, 'status': 'ERR', 'msg': str(e)}
async def poll_all(self):
tasks = [self.poll_one(s) for s in self.sensors]
return await asyncio.gather(*tasks)class TcpConnectionPool:
def __init__(self, max_idle=300):
self._pools = {} # ip -> (reader, writer, last_used)
self.max_idle = max_idle
async def get_connection(self, ip: str, port: int = 502):
now = time.time()
if ip in self._pools:
reader, writer, last = self._pools[ip]
if now - last < self.max_idle:
self._pools[ip] = (reader, writer, now)
return reader, writer
else:
writer.close()
reader, writer = await asyncio.open_connection(ip, port)
self._pools[ip] = (reader, writer, now)
return reader, writer
async def cleanup(self):
now = time.time()
for ip, (reader, writer, last) in list(self._pools.items()):
if now - last > self.max_idle:
writer.close()
del self._pools[ip]场景 | 理由 |
|---|---|
高频数据上报(5s 粒度) | 低开销,不占连接资源 |
大批量传感器(>100 台) | 无连接状态,网关压力小 |
广播/组播发现设备 | UDP 支持广播,TCP 不支持 |
网络质量较好(局域网) | 局域网丢包率通常 <0.1% |
非关键区域(走廊、缓冲区) | 偶尔丢一包不影响整体判断 |
class UdpReceiver:
def __init__(self, listen_port: int):
self.port = listen_port
self.buffer = {} # device_id -> latest reading
self.transport = None
async def start(self):
loop = asyncio.get_event_loop()
self.transport, _ = await loop.create_datagram_endpoint(
lambda: UdpProtocol(self),
local_addr=('0.0.0.0', self.port)
)
def on_packet(self, data: bytes, addr: tuple):
# 解析传感器 UDP 数据报
# 格式:设备ID(4B) + 温度(float) + 湿度(float) + 序列号(2B)
if len(data) < 12:
return
device_id = struct.unpack('>I', data[:4])[0]
temp = struct.unpack('>f', data[4:8])[0]
hum = struct.unpack('>f', data[8:12])[0]
seq = struct.unpack('>H', data[12:14])[0] if len(data) >= 14 else 0
self.buffer[device_id] = {
'temp': temp,
'hum': hum,
'seq': seq,
'addr': addr,
'ts': time.time()
}
def get_latest(self, device_id: int = None):
if device_id:
return self.buffer.get(device_id)
return self.buffer
class UdpProtocol(asyncio.DatagramProtocol):
def __init__(self, receiver: UdpReceiver):
self.receiver = receiver
def datagram_received(self, data, addr):
self.receiver.on_packet(data, addr)class UdpReliableSender:
"""UDP 发送端实现简单重传机制"""
def __init__(self, remote_addr: tuple, retry=3, timeout=2):
self.remote = remote_addr
self.retry = retry
self.timeout = timeout
self.seq = 0
self._transport = None
async def send_with_ack(self, payload: bytes):
"""发送数据并等待 ACK"""
self.seq += 1
pkt = struct.pack('>H', self.seq) + payload
for attempt in range(self.retry):
self._transport.sendto(pkt, self.remote)
try:
# 等待 ACK(简化:通过接收回调匹配 seq)
ack = await asyncio.wait_for(
self._wait_ack(self.seq), timeout=self.timeout
)
return True
except asyncio.TimeoutError:
continue
return False
async def _wait_ack(self, seq: int):
# 实际实现需维护等待队列
pass┌─────────────────────────────────────────────────────────────────┐
│ 控制面(TCP) │ 数据面(UDP) │
├─────────────────────────┼─────────────────────────────────────┤
│ • 设备配置/参数修改 │ • 温湿度周期上报 │
│ • 启停指令下发 │ • 状态心跳 │
│ • 告警事件 │ • 批量广播发现 │
│ • 固件升级 │ • 非关键区高频采集 │
│ • 阈值配置 │ │
└─────────────────────────┴─────────────────────────────────────┘
推荐组合:
关键区(配电室/特藏库) → TCP 全量采集 + UDP 心跳辅助
普通区(走廊/缓冲区) → UDP 主动上报 + TCP 按需查询
控制指令 → 永远走 TCP
告警上报 → TCP + SMS 双通道┌──────────────────┐
│ 边缘网关 │
│ │
│ TCP Server │←── 控制指令下发
│ (Port 502) │
│ │
│ UDP Server │←── 传感器主动上报
│ (Port 9000) │
│ │
│ 决策引擎 │
│ (数据融合) │
└────────┬─────────┘
│
┌──────────────┼──────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 关键区 │ │ 普通区 │ │ 缓冲区 │
│ TCP 轮询 │ │ UDP 上报 │ │ UDP 上报 │
│ 16台 │ │ 32台 │ │ 8台 │
└──────────┘ └──────────┘ └──────────┘class HybridCollector:
def __init__(self, tcp_sensors: list, udp_port: int):
self.tcp_poller = TcpPoller(tcp_sensors)
self.udp_receiver = UdpReceiver(udp_port)
self.results = {}
async def start_udp(self):
await self.udp_receiver.start()
async def collect_cycle(self):
# TCP 轮询关键区
tcp_results = await self.tcp_poller.poll_all()
for r in tcp_results:
self.results[r['id']] = {**r, 'protocol': 'tcp'}
# UDP 收集普通区
udp_data = self.udp_receiver.get_latest()
for dev_id, data in udp_data.items():
if time.time() - data['ts'] < 60: # 60s 内有效
self.results[f"UDP-{dev_id}"] = {
**data, 'protocol': 'udp', 'status': 'OK'
}
else:
self.results[f"UDP-{dev_id}"]['status'] = 'STALE'
return self.results某机房 64 台传感器,相同硬件环境(千兆局域网,交换机 H3C S5130):
指标 | TCP 轮询(64台) | UDP 上报(64台) | 混合(16 TCP + 48 UDP) |
|---|---|---|---|
单轮耗时 | 8.2~15.6s | 0.3s(接收等待) | 2.1~4.8s |
网关 CPU | 68~78% | 12~18% | 22~28% |
内存占用 | 185MB(连接缓冲) | 32MB | 78MB |
数据到达率 | 99.8%(含重传) | 99.2%(无重传) | 99.7% |
断网恢复 | 自动重连 3~15s | 下次发送自动恢复 | 混合恢复 |
交换机负载 | 高(连接表满) | 低 | 中 |
条件 | 选 TCP | 选 UDP | 选混合 |
|---|---|---|---|
设备数量 | <64 台 | >100 台 | 64~100 台 |
网络质量 | 一般/跨网段 | 局域网良好 | 混合环境 |
数据重要性 | 关键区域 | 非关键区域 | 关键+普通混合 |
实时性要求 | 中(秒级) | 高(亚秒级) | 分级处理 |
控制指令 | 必须 TCP | 不适合 | TCP 控制 + UDP 数据 |
开发复杂度 | 低(现成库多) | 中(需自处理可靠性) | 中高 |
项目 | TCP 标准 | UDP 标准 | 混合标准 |
|---|---|---|---|
数据到达率 | ≥99.9% | ≥99.0%(应用层补传后≥99.5%) | ≥99.5% |
响应延迟 | <5s | <1s | 关键区<5s,普通区<2s |
断线重连 | <15s | 无连接概念 | TCP<15s |
并发能力 | ≥64 连接 | ≥256 设备 | ≥128 设备 |
CPU 占用 | <70% | <30% | <50% |
TCP 和 UDP 不是"谁好谁坏",而是"各管各的活"。控制面用 TCP 保可靠,数据面用 UDP 保效率,关键区 TCP 兜底,普通区 UDP 减负——混合方案把两种协议的优点拼在一起,让 64 台甚至更多传感器在局域网内各司其职、互不拖累。
关键词:TCP、UDP、RJ45温湿度传感器、协议对比、机房环境监控、网关轮询、主动上报、混合通信架构、连接池、可靠性、数据到达率、传输层选型
标签:#TCP #UDP #RJ45温湿度传感器 #协议对比 #机房监控 #网关轮询 #主动上报 #混合通信 #连接池 #可靠性 #传输层 #通信方案选型
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。