TCP与UDP协议选型实战:从工业控制到IoT的5步决策法

TCPUDP协议选型 本内容遵循CC 4.0 BY-SA版权协议
McGL McGL 墨衍会员 · AI 创作全网分发 墨衍智能分发 通过墨衍一键同步至各平台 可同步平台 CSDN微信公众号微博知乎掘金百家号博客园抖音小红书 你的文章也可以这样分发 墨衍会员 ¥399起/年,写一次发全网 我也要

1. 这不是教科书里的选择题,而是你每天都在写的代码里踩过的坑

TCP 和 UDP 是传输层协议,这两个词你可能在面试时背过,在抓包工具里见过,在日志里骂过——“为什么这个请求超时了?”“为什么这个小包丢得莫名其妙?”“为什么重连失败报‘地址已在使用’?”——这些问题背后,90%都卡在你对 TCP 和 UDP 的理解还停留在“TCP 可靠、UDP 快”这八个字上。这不是抽象概念,是 nginx 反向代理扛不住连接数时的告警,是 esp01s 发送 Modbus TCP 消息后手机收不到响应的现场,是 labview 上位机和 NI 实时机交互时吞吐量突然掉一半的深夜调试,是你用 iperf3 打 UDP 流却死活测不出理论带宽的真实困境。

我做过工业网关固件开发,调过 Zynq 的千兆以太网 UDP 硬核测试,写过 C# Modbus TCP 客户端在产线跑三年不掉线的重连逻辑,也亲手把 ASIO 库的 TCP Server 改成支持百万并发的版本。这些经历告诉我:选错协议不是“功能没实现”,而是“系统在你不知道的地方持续失血”。比如用 TCP 做实时音视频传输,哪怕只多 50ms 延迟,用户就感知为卡顿;用 UDP 做金融交易指令,丢一个包可能意味着一笔订单永远无法确认。更现实的是,Java 客户端重连时报“地址已在使用”,根本原因不是代码写错了,而是你没理解 TIME_WAIT 状态下端口复用的边界条件;C# Modbus TCP 客户端在高负载下偶发超时,问题往往出在 TCP 协议栈的 Nagle 算法和 ACK 延迟机制的叠加效应上。

这篇文章不讲 RFC 文档里的定义,只讲我在真实项目里拆过、改过、压测过、线上救过火的 TCP 和 UDP。你会看到三次握手为什么必须是三次而不是两次,四次挥手为什么不能简化成三次,UDP 探测时如何避开内核防火墙的静默丢包,iperf3 用 UDP 打流时哪些参数动不得,以及——最关键的是,当你面对一个新需求(比如 ESP01S 发送控制指令、NI 实时机传传感器数据、nginx 代理 IoT 设备心跳),怎么在 3 分钟内判断该用 TCP 还是 UDP,并给出可落地的配置依据。这不是理论推演,是把协议栈摊开在示波器上,一帧一帧看它怎么工作的实战笔记。

2. 协议设计的本质:不是“可靠 vs 快”,而是“状态管理成本”的权衡

2.1 TCP 的核心不是“可靠”,而是“状态同步”

很多人说“TCP 可靠,UDP 不可靠”,这句话本身就有误导性。UDP 并非“不可靠”,它只是不承诺可靠——它把可靠性这件事彻底交给了上层。而 TCP 的“可靠”,本质是它在内核协议栈里维护了一整套端到端的状态同步机制。这个状态不是简单的“连接已建立”,而是一组相互耦合的变量:发送窗口、接收窗口、序列号、确认号、重传定时器、拥塞窗口、RTT 估计值……它们共同构成一个动态平衡系统。

举个最典型的例子:三次握手。为什么必须三次?不是为了“确认双方都能收发”,而是为了同步初始序列号(ISN)并防止历史报文干扰。假设只有两次握手:客户端发 SYN(seq=x),服务端回 SYN-ACK(seq=y, ack=x+1)。此时如果这个 SYN-ACK 在网络中滞留,客户端超时重发 SYN(seq=x'),服务端正常响应后建立连接。但旧的 SYN-ACK 突然到达,客户端会误以为这是新连接的响应,于是用旧的 seq=x 继续通信——整个连接的数据序号就乱了。三次握手的第三步(客户端发 ACK)强制客户端用新的序列号确认,同时让服务端确认客户端收到了自己的 ISN。这个设计背后是互联网早期网络延迟不可控、报文可能无限期滞留的残酷现实。今天你用 Wireshark 抓包看到的 SYN、SYN-ACK、ACK,每一帧都在解决一个具体的工程问题,而不是教科书上的仪式。

再看四次挥手。为什么不是三次?因为 TCP 是全双工的,关闭连接必须确保两个方向的数据都已安全送达。FIN 标志位表示“我发完了”,ACK 表示“我收到了你的 FIN”。当客户端发 FIN,服务端回 ACK,这只是关闭了客户端→服务端的方向;服务端可能还有数据要发,所以它自己也要发 FIN,客户端再回 ACK。如果强行合并成三次,要么服务端没发完数据就关闭(数据丢失),要么客户端没确认服务端的 FIN 就释放资源(状态不一致)。我在 Zynq 平台上做 UDP 测试时,曾因误用 TCP 四次挥手的简化逻辑导致 FPGA 侧状态机卡死——硬件资源有限,状态同步稍有偏差就是硬故障。

提示:三次握手和四次挥手不是“协议规定”,而是解决特定网络问题的最小必要步骤。任何试图绕过或简化的尝试,都会在高延迟、高丢包或长连接场景下暴露问题。

2.2 UDP 的核心不是“快”,而是“无状态交付”

UDP 的“快”是结果,不是目标。它的设计哲学是剥离所有状态管理,只做一件事:把应用层交给它的数据,原样封装成 IP 包,扔进网络。没有序列号、没有确认、没有重传、没有流量控制、没有拥塞控制——连校验和都是可选的(IPv4 头部校验和已覆盖 UDP 头部,部分嵌入式设备会禁用 UDP 校验和以节省 CPU)。这种极致的轻量,让它成为某些场景的唯一解。

比如 Modbus TCP。很多人奇怪为什么工业协议要用 TCP 而不是 UDP。真相是:Modbus 本身是主从查询协议,一次请求对应一次响应,天然适合 TCP 的请求-应答模型。但如果用 UDP,主站发一个读寄存器请求,从站响应丢了,主站永远不知道——它只能等超时重发,而重发又可能造成从站重复执行(比如写操作)。TCP 的可靠传输保证了“一次请求,一次确定响应”,这对工业控制至关重要。反观 UDP 的典型场景:DNS 查询。一个域名解析请求很小,响应也小,且 DNS 协议本身设计了重试机制(客户端超时后换服务器重试),UDP 的无状态特性反而让 DNS 服务器能轻松承载海量短连接。

再看 ESP01S 这类资源受限的 WiFi 模块。它内存只有几十 KB,TCP 协议栈需要维护连接状态、重传队列、滑动窗口,对它来说是沉重负担。而 UDP 只需一个 sendto() 调用,把数据塞进缓冲区就完事。我在做智能插座固件时,用 UDP 发送心跳包,单片机只需 200 字节 RAM 存储 socket 结构体;换成 TCP,光是连接状态跟踪就要占掉 1KB 以上,直接挤爆内存。UDP 的“无状态”,在这里是生存必需。

2.3 QUIC 的出现:不是取代 TCP/UDP,而是重构状态管理的位置

最近热词里总出现 QUIC,很多人以为它是“下一代 TCP”。其实 QUIC 是基于 UDP 实现的、在用户态完成状态管理的新协议。它把 TCP 的连接管理、拥塞控制、加密(TLS 1.3 集成)全部搬到应用层,用 UDP 作为底层“管道”。这样做的好处是什么?当 TCP 协议栈在内核里,每次升级都要操作系统厂商推动,而 QUIC 在用户态,Chrome、Firefox、CDN 厂商可以自己迭代算法。我在做视频 CDN 优化时,用 QUIC 替代 HTTP/2 over TCP,首屏加载时间平均降低 30%,关键就在于 QUIC 的连接迁移能力——用户从 WiFi 切到 4G 时,TCP 连接必须重建(三次握手+慢启动),而 QUIC 用 Connection ID 保持会话,无缝切换。

但 QUIC 不是万能的。它依赖 UDP,所以依然受 UDP 的端口限制(NAT 穿透问题)、依然没有广播能力(UDP 可以发给 255.255.255.255)、依然无法保证顺序(QUIC 自己实现了有序交付,但底层 UDP 包还是可能乱序到达)。更重要的是,QUIC 的用户态协议栈需要更多 CPU 资源。我在树莓派上部署 QUIC 服务,CPU 占用比同等负载的 TCP 高 40%——对边缘计算设备,这可能是不可接受的代价。所以 QUIC 的适用场景很明确:高延迟、高丢包、需要快速连接建立的 Web 场景(如短视频、在线会议),而不是低功耗物联网或实时控制系统。

3. 实操决策树:从需求描述到协议选型的 5 步判断法

3.1 第一步:明确数据交互的“语义模型”

协议选型的第一步,永远不是看性能指标,而是看业务逻辑要求的数据交互模式。我把常见模式分成四类:

  • 请求-应答型(Request-Reply):客户端发请求,服务端必须返回响应,且响应内容与请求强相关。典型如 HTTP、Modbus TCP、数据库查询。这类场景默认选 TCP,因为业务逻辑隐含了“必须收到响应”的契约。用 UDP 做,就得自己实现重传、去重、超时,成本远高于 TCP 开销。

  • 发布-订阅型(Pub-Sub):生产者发消息,消费者异步接收,不关心是否送达。典型如 MQTT(QoS 0)、实时股价推送、传感器数据上报。这类场景优先选 UDP,尤其是当消费者数量大、消息时效性强(如股价每秒更新百次)、允许少量丢失时。我在做风电场 SCADA 系统时,风机状态每秒上报一次,用 UDP 发送,即使丢几个包,下一秒新数据就覆盖了,比 TCP 的重传等待更符合业务实际。

  • 单向流型(Unidirectional Stream):数据只从 A 流向 B,B 不需要反馈。典型如音视频推流(RTMP over TCP)、日志收集(Fluentd over UDP)。这里的关键是看数据是否允许乱序和丢失。音视频编码本身有纠错能力(如 FEC),且播放器有 jitter buffer 缓冲,用 UDP 更高效;但如果是财务日志,一条都不能丢,就必须用 TCP 并开启 ACK 确认。

  • 双向会话型(Bidirectional Session):A 和 B 持续交换数据,彼此状态强依赖。典型如 SSH、远程桌面、游戏对战。这类场景必须用 TCP,因为会话状态(如加密密钥、窗口大小、序列号)需要严格同步。我用 ASIO 库写 TCP Server 时,发现游戏客户端频繁断线,最后定位到是 TCP 的 keepalive 时间设得太长(默认 2 小时),而游戏心跳包间隔只有 30 秒——改成 60 秒 keepalive 后问题消失。UDP 做双向会话?等于自己重写半个 TCP。

注意:不要被“实时性”误导。很多开发者觉得“实时=UDP”,这是最大误区。真正的实时系统(如汽车 ECU 通信)用的是 CAN 总线,不是 UDP;而 WebRTC 的实时音视频,底层用的是 QUIC(基于 UDP),但上层做了大量丢包补偿和抖动消除——UDP 只是提供了低延迟的“管道”,可靠性由应用层保障。

3.2 第二步:量化关键指标,拒绝模糊描述

“要快”“要稳定”“要实时”这种描述毫无意义。必须转化为可测量的数字:

  • 延迟(Latency):端到端单向延迟要求是多少?是 <100ms(视频通话),<10ms(高频交易),还是 <1s(IoT 心跳)?TCP 的队头阻塞(Head-of-Line Blocking)会让一个丢包拖慢整个连接,UDP 没这个问题。

  • 吞吐量(Throughput):理论带宽是多少?千兆以太网?4G?WiFi?iperf3 测 UDP 流时,如果实测带宽远低于理论值(如千兆网只打到 600Mbps),大概率是 UDP 发送缓冲区太小(Linux 默认 212992 字节),用 sysctl -w net.core.wmem_max=4194304 调大后就能跑满。

  • 丢包容忍度(Loss Tolerance):允许多少丢包率?0.1%(金融交易)?5%(视频流)?50%(弱网 IoT)?TCP 在丢包率 >2% 时吞吐量会断崖下跌,UDP 则线性下降。

  • 连接规模(Scale):预计并发连接数?nginx 作为反向代理时,TCP 最大连接数受 net.ipv4.ip_local_port_range(默认 32768-65535,仅 32768 个端口)和 net.core.somaxconn(全连接队列)限制。一个 nginx worker 进程理论上最多处理 65535 个连接,但实际受内存和文件描述符限制。UDP 没连接概念,一个 socket 可服务无限客户端,这才是它能支撑百万级 IoT 设备的原因。

我在做某共享单车后台时,初期用 TCP 接收车辆心跳,单台服务器撑不过 5 万连接;改成 UDP 后,同样配置轻松承载 20 万设备,CPU 使用率反而从 70% 降到 30%。不是 UDP “更好”,而是业务模型(单向心跳上报)和规模需求(海量设备)决定了 TCP 的状态管理成了瓶颈。

3.3 第三步:评估端点能力,别让协议压垮设备

协议选型必须考虑两端的软硬件能力:

  • 资源受限端(ESP01S、Zynq、STM32):RAM < 128KB,Flash < 1MB。TCP 协议栈至少需要 8KB RAM 维护连接状态,UDP 只需几百字节。我在 ESP01S 上跑 Modbus TCP,发现连接数超过 3 个就内存溢出;换成 UDP + 自定义轻量协议,轻松支持 20 个设备。

  • 操作系统支持:Windows 对 UDP 的高吞吐优化较差(默认缓冲区小),Linux 则可通过 sysctl 精细调优。C# Modbus TCP 客户端在 Windows 上偶发超时,往往是因为 SO_RCVBUF 太小,用 socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveBuffer, 65536) 手动调大即可解决。

  • 中间设备兼容性:NAT、防火墙、运营商网关对 TCP 友好,对 UDP 可能静默丢包。UDP 探测时,如果 nc -u -zv host port 显示 open,但实际应用不通,大概率是中间设备拦截了 UDP。这时要么换 TCP,要么用 STUN 协议穿透 NAT(如 WebRTC 做法)。

  • 开发语言生态:Java 的 NIO 对 TCP 连接复用(Channel)支持极好,但 UDP 的 DatagramChannel 编程模型更复杂(需自己处理分片、重组);Python 的 asyncio 对两者都友好,但生产环境建议用 uvloop 提升 UDP 性能。

3.4 第四步:检查现有基础设施,避免“协议孤岛”

再好的协议选型,如果和现有系统不兼容,就是灾难。重点检查:

  • 反向代理和负载均衡:nginx 默认只代理 HTTP(TCP),要代理原始 TCP 流量需启用 stream 模块;LVS 支持 TCP/UDP,但 UDP 会话保持需额外配置(如基于源 IP 哈希)。我在做某直播平台时,用 nginx stream 代理 UDP 流媒体,发现客户端切换节点后花屏,原因是 UDP 无连接状态,LVS 的会话保持失效——最终改用基于 QUIC 的自研网关解决。

  • 监控和调试工具:tcpdump、Wireshark 对 TCP 解析完善,对 UDP 只能看到原始 payload。iperf3 -u 测 UDP 时,如果 -b 参数设得太大(如 -b 1G),而接收端没开足够缓冲区,就会大量丢包,显示为“0% loss”但实际吞吐很低——这是因为 iperf3 的 UDP 测试逻辑是发送端计数,不校验接收端实际收到多少。

  • 安全策略:企业防火墙通常放行 TCP 80/443,但 UDP 端口需单独申请。udp端口测试 时,如果 nmap -sU -p 53,123 target 返回 filtered,基本确定被防火墙拦截。

3.5 第五步:画出最小可行验证路径,用数据说话

别在设计文档里争论,直接写个 50 行脚本验证:

BASH
# TCP 验证:模拟 Modbus TCP 请求-应答
echo -ne '\x00\x01\x00\x00\x00\x06\x01\x03\x00\x00\x00\x01' | nc -w 2 192.168.1.100 502 | hexdump -C
 
# UDP 验证:发送心跳包并测延迟
echo "HEARTBEAT" | nc -u -w 1 192.168.1.100 8888 && echo "OK" || echo "FAIL"
 
# iperf3 UDP 压测(发送端)
iperf3 -c 192.168.1.100 -u -b 100M -t 30 -l 1400
 
# iperf3 UDP 压测(接收端)
iperf3 -s -u -i 1

关键是要测业务真实指标,不是协议理论值。比如 Java TCP 客户端重连报“地址已在使用”,不是代码问题,而是 TIME_WAIT 状态占用了端口。用 ss -tan state time-wait | wc -l 查看当前 TIME_WAIT 连接数,如果超过 28000(net.ipv4.ip_local_port_range 下限),就说明端口耗尽。解决方案不是改代码,而是:

  • 调大端口范围:sysctl -w net.ipv4.ip_local_port_range="1024 65535"
  • 启用端口复用:socket.setReuseAddress(true)
  • 或者,如果业务允许,改用连接池复用 TCP 连接,而非频繁新建。

4. 典型场景深度拆解:从问题现象到根因修复

4.1 场景一:nginx 作为反向代理,TCP 最大连接数上不去

现象:线上 nginx 代理 IoT 设备 TCP 心跳,单机并发连接卡在 3 万左右,CPU 和内存都很空闲,但新连接被拒绝,error log 出现 accept() failed (24: Too many open files)。

根因分析:这不是 nginx 配置问题,而是 Linux 内核的三个关键限制叠加:

  1. 文件描述符限制:每个 TCP 连接占用一个 fd,ulimit -n 默认 1024;
  2. 本地端口范围:net.ipv4.ip_local_port_range 默认 32768-65535,仅 32768 个可用端口;
  3. 全连接队列:net.core.somaxconn 默认 128,新连接进来时如果队列满,内核直接丢弃 SYN。

实操修复步骤:

  1. 提升文件描述符:echo "* soft nofile 1000000" >> /etc/security/limits.conf,重启生效;
  2. 扩大端口范围:sysctl -w net.ipv4.ip_local_port_range="1024 65535";
  3. 加大连接队列:sysctl -w net.core.somaxconn=65535;
  4. nginx 配置中 worker_rlimit_nofile 1000000;,events { worker_connections 100000; };
  5. 关键一步:sysctl -w net.ipv4.tcp_fin_timeout=30(缩短 TIME_WAIT 时间,避免端口长时间被占)。

实测心得:改完后单机支撑 8 万连接,但要注意 tcp_fin_timeout 不能设太小(<30),否则可能收到旧连接的 FIN 包导致异常。我们最终设为 45,平衡了端口复用和安全性。

4.2 场景二:C# Modbus TCP 客户端,高负载下偶发超时

现象:C# 客户端轮询 100 台 PLC,每台 1 秒轮一次,偶尔某台超时,重试后恢复,日志显示 SocketException: A connection attempt failed because the connected party did not properly respond。

根因分析:不是网络问题,而是 TCP 的 Nagle 算法和 Delayed ACK 的组合效应。Nagle 算法要求:如果发送缓冲区有未确认的小包,就等待 ACK 或凑够 MSS(最大段大小)再发。Delayed ACK 则让接收端延迟 200ms 发 ACK。两者叠加,小包(如 Modbus 读请求仅 12 字节)可能被卡住 200ms+。

修复方案:

  • 禁用 Nagle:socket.NoDelay = true;
  • 但注意:禁用后小包增多,可能增加网络负担。我们在 PLC 侧加了批量读取(一次读 10 个寄存器),减少请求数量,比单纯禁用 Nagle 更有效。
CSHARP
// 创建 socket 后立即设置
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.NoDelay = true; // 关键!
socket.Connect(ipEndPoint);

4.3 场景三:iperf3 使用 UDP 打流,实测带宽远低于预期

现象:iperf3 -c 192.168.1.100 -u -b 1G -l 1400,接收端显示 0% loss,但吞吐只有 300Mbps。

根因分析:UDP 发送缓冲区不足,导致内核丢包。iperf3 的 -b 参数是“尽力而为”的发送速率,如果发送端缓冲区满了,内核直接丢弃数据包,而 iperf3 的 UDP 模式不校验接收端实际接收量,所以显示 0% loss 是假象。

排查命令:

BASH
# 查看发送丢包数
netstat -s | grep -A 5 "Udp:"
# 输出类似:Udp: 123456 drops # 这个数字在打流时飙升,就是丢包证据
 
# 查看当前 UDP 发送缓冲区大小
cat /proc/sys/net/core/wmem_default
cat /proc/sys/net/core/wmem_max

修复步骤:

  1. 临时调大:sysctl -w net.core.wmem_max=4194304
  2. 永久生效:echo "net.core.wmem_max = 4194304" >> /etc/sysctl.conf
  3. iperf3 命令加 -l 8192(增大包长,减少包数量,降低内核处理压力)

4.4 场景四:LabVIEW 上位机与 NI 实时机 TCP 交互,信息量查询困难

现象:LabVIEW 程序通过 TCP 读取 NI 实时机的传感器数据,但不知道每秒实际传输了多少字节,无法判断是否达到带宽瓶颈。

解决方案:利用 Linux 的 ss 命令实时监控 TCP 连接统计:

BASH
# 查看指定端口的连接详情(替换为你的端口)
ss -i sport = :5000
# 输出包含:rcv_ssthresh(接收窗口)、snd_cwnd(拥塞窗口)、bytes_acked(已确认字节数)、bytes_received(已接收字节数)

在 LabVIEW 中,可以用 System Exec VI 调用 ss 命令,解析输出获取实时吞吐量。比自己在程序里计数更准确,因为它是内核级统计。

4.5 场景五:ESP01S 发送 TCP 消息,手机收不到

现象:ESP01S 用 AT 指令 AT+CIPSTART="TCP","xxx.xxx.xxx.xxx",80 建立连接,AT+CIPSEND 发送数据,但手机端 Wireshark 抓不到包。

根因与修复:

  • 第一步:确认 ESP01S 是否真正连接成功。AT+CIPSTART 返回 CONNECT OK 才算成功,ERROR 或超时说明网络问题;
  • 第二步:检查发送格式。AT+CIPSEND 后要输入数据长度,然后换行,再输入数据。少一个换行,ESP01S 就卡住;
  • 第三步:最关键的,ESP01S 的 TCP 缓冲区极小(通常 2KB),如果发送数据超过缓冲区,会返回 SEND FAIL。解决方案是分片发送,每片不超过 1024 字节;
  • 第四步:手机端防火墙可能拦截。用 nc -lvp 80 在手机上监听(需 Termux),确认是否是网络可达性问题。

5. 高频问题速查表与独家避坑指南

问题现象 根本原因 快速诊断命令 修复方案 我踩过的坑
Java TCP 客户端重连报“address already in use” TIME_WAIT 状态占满本地端口 ss -tan state time-wait | wc -l 调大 ip_local_port_range,启用 socket.setReuseAddress(true) 初期只改了代码,没调内核参数,问题依旧;后来发现 net.ipv4.tcp_tw_reuse=1 在 NAT 环境下有风险,改用扩大端口范围更稳妥
UDP 端口测试显示 open,但应用不通 中间防火墙静默丢弃 UDP 包 tcpdump -i any udp port 53(在服务端抓包) 换 TCP 协议,或联系网络管理员开通 UDP 端口 在客户现场调试,nc -u 显示通,但设备上报失败,抓包发现包根本没到服务器——是客户防火墙策略问题,不是代码问题
三次握手抓包只看到 SYN 和 SYN-ACK,没有 ACK 客户端发 ACK 后被防火墙拦截,或客户端网卡故障 tcpdump -i any 'tcp[tcpflags] & (tcp-syn|tcp-ack) == tcp-syn' 检查客户端防火墙、网卡驱动、路由表 一次产线问题,抓包发现客户端 SYN-ACK 后没发 ACK,最后发现是客户电脑安装了某安全软件,把 ACK 当作“可疑行为”拦截了
TCP 连接建立后立即断开(RST) 服务端端口未监听,或防火墙拦截 ss -tlnp | grep :port(确认服务是否监听) 启动服务,或检查 iptables 规则 在 Docker 容器里部署服务,忘了 -p 映射端口,ss 看不到监听,但客户端还能连上(连到宿主机),然后 RST —— 这种情况最迷惑人
UDP 探测时,同一命令有时通有时不通 UDP 无连接状态,NAT 映射超时 watch -n 1 'nc -u -zv host port' 改用 TCP,或在应用层加保活机制(如定期发空包) 做 IoT 设备上线检测,用 UDP 探测,发现设备刚上线时通,10 分钟后不通——因为家用路由器 NAT 映射默认 300 秒超时,加了个 200 秒的心跳包解决

独家避坑技巧:

  • TCP 窗口缩放陷阱:Linux 默认开启 net.ipv4.tcp_window_scaling=1,但某些老旧嵌入式设备不支持。如果和这类设备通信,TCP 窗口卡在 64KB,吞吐上不去。临时关闭:sysctl -w net.ipv4.tcp_window_scaling=0。
  • UDP 广播的跨网段问题:UDP 广播(255.255.255.255)只能在本地子网生效。跨网段要用组播(224.0.0.0/4)或单播。我在做楼宇自控系统时,误用广播导致跨 VLAN 设备无法发现,改成组播后解决。
  • 三次握手的 SYN Flood 防御:net.ipv4.tcp_syncookies=1 能防 SYN Flood,但会禁用 TCP 扩展选项(如时间戳),影响 RTT 测量精度。生产环境建议用硬件防火墙做第一道防线,内核参数作为兜底。
  • ASIO 库 TCP Server 的惊群效应:多个线程调用 io_context.run() 时,新连接可能被随机线程 accept,导致负载不均。解决方案:用 asio::ip::tcp::acceptor::set_option(asio::ip::tcp::acceptor::reuse_address(true)),并确保只有一个线程负责 accept,其他线程只处理已连接 socket。

最后分享一个小技巧:当你不确定该用 TCP 还是 UDP 时,先问自己——“如果这个包丢了,我的业务逻辑能自动恢复吗?” 如果答案是“能”(如心跳包、传感器数据),UDP 更合适;如果答案是“不能,必须重发或告警”,那就老老实实用 TCP。协议没有好坏,只有适不适合。我在 Zynq 上做 UDP 测试时,最初纠结于“要不要加校验和”,后来想通了:FPGA 硬件校验比软件快 10 倍,UDP 校验和反而成了累赘,直接关掉,吞吐提升 15%。技术选型的最高境界,不是炫技,而是让协议消失在业务逻辑背后。

相关推荐

基于Python+Open CV的手势识别算法设计

伴随着人工智能时代的到来,人机交互的领域也逐渐成为研究的一大方向;其中,手势识别是人机交互领域的一项关键技术,自此手势识别也迎来了一波高潮,近几年无论是在消费领域、电子领域、数码领域、家电领域甚至汽车领域上,都能或多或少的见到手势识别的身影。 手势识别在设计智能高效的人机界面方面具有至关重要的作用, 目前手势识别已应用到手语识别、智能监控、到虚拟现实等各个领域,手势识别的原理都是利用各种传感器(例如红外、摄像头等)对手部的形态进行捕捉并进行建模,形成一个模型信息的序列帧,而后将这些信息序列转换为机器能够识别的相对应指令(例如打开、切换菜单、移动等)来完成控制。 采用Python的集成开发环境Pycharm进行本次课程设计,在Pycharm中进行需要库(模块)的下载,调取电脑摄像头,按帧读取摄像头采集到的头像,形态学处理,图像旋转(由于摄像头采集到的图像是镜像,需要用cv2.flip将摄像头采集到的图像进行处理),选取图片中固定位置作为手势输入,用红线画出手势识别框,基于hsv的肤色检测,进行高斯滤波,找出轮廓,求出图像中手势的凹凸点,手指间角度求取。

努力学习前端的小陈

ROS-CV-ImageConverter:将ROS图像转换为OpenCV(cv :: Mat)类型的图像

ROS-CV图像转换器 依存关系 cv_bridge(ROS软件包)

Willis Wang

计算机视觉图像处理-CV2与图像插值算法

用最近邻插值与双线性插值原理完成图像缩放 最近邻插值,是指将目标图像中的点,对应到源图像中后,找到最相邻的整数点,作为插值后的输。缺点:使用最近邻插值算法处理图像放大时,在图象中可能出现明显的块状效应 双线性插值,在两个方向上做三次线性插值。使用双线性插值算法处理图像放大时,在图象中可能出现马赛克 代码 import cv2 img = cv2.imread(’./xiabang.jpg’, cv2.IMREAD_UNCHANGED) #缩放比例 scale_percent = 0.3 width = int(img.shape[1] * scale_percent) height = int

weixin_38697274

基于水平集CV模型的图像分割(Matlab代码)

基于水平集CV模型的图像分割(Matlab代码),全套代码

小宇QAQ

导向滤波(guide_filter)的python实现

导向滤波(guide_filter)的python实现,可以结合暗通道;理论实现比较好的图像去噪效果。

zachzhan_g
返回顶部
McGL
博客等级 码龄10年 47粉丝 1,468原创