Linux UDP 抓包有数据但应用收不到:接收缓冲与 RcvbufErrors 排查
Linux 主机上 tcpdump 能看到 UDP 数据报到达网卡,应用日志却缺少部分消息;低流量正常,高峰期丢包明显,重启进程后短暂恢复。这类故障可能发生在网卡 ring、软中断 backlog、IP/UDP 处理、socket receive buffer 或应用读取阶段。UDP 没有 TCP 的重传和流量控制,内核缓冲满时可以直接丢弃数据报,发送端未必收到明确错误。
本文目录(21 节)
先固定流量与丢失定义
记录源/目的 IP、端口、数据报大小、包速率、字节速率、突发持续时间和应用期望序列号。没有业务序列号时,只凭“日志条数少”无法区分发送端未发、网络丢失、重复去重或应用处理失败。
在发送端、接收主机网卡和应用入口建立同一时间线。抓包需限制长度、文件大小和敏感载荷,优先只保存头部与自有测试序列号。
tcpdump 看到不等于 socket 收到
抓包点可能位于网卡/内核路径较早阶段。数据报被 tcpdump 捕获后,仍可能因校验、IP 分片、路由、防火墙、socket 不存在、receive buffer 满或 BPF规则而丢弃。
同时观察接口统计、IP/UDP SNMP 计数、socket 队列和应用 recv 计数。只有逐层计数,才能找出第一个出现差额的位置。
查看 UDP 全局统计
使用 netstat -su、nstat 或读取 /proc/net/snmp 相关字段,记录 Udp: InDatagrams、NoPorts、InErrors、RcvbufErrors、InCsumErrors 等在故障窗口的增量。不同内核版本展示字段可能不同,以当前系统文档为准。
RcvbufErrors 增长强烈指向 socket 接收队列无空间;NoPorts 表示目标端口没有匹配 socket;checksum errors 则应检查发送端、网卡 offload 展示和真实线上校验。
Socket receive buffer 为什么溢出
每个 UDP socket 有接收缓冲。应用调用 recv/
查看 ss -u -a -n -m 等输出中的 Recv-Q 和内存信息,并在高峰期连续采样。事后 Recv-Q 为 0 不代表未曾溢出,因为应用恢复后队列会被读空。
SO_RCVBUF 与 rmem_max
应用可通过 SO_RCVBUF 请求接收缓冲大小,但会受系统 net.core.rmem_max 等上限和 Linux 记账语义影响。设置成功后用 getsockopt 或 socket 工具确认实际值,不要只看配置代码。
增大缓冲可以吸收短突发,却不能修复长期处理能力不足。还会增加内存使用和排队延迟。根据最大 burst、数据报大小和允许延迟计算,并做容量测试。
UDP 全局内存压力
Linux 还维护 UDP 全局内存阈值,例如 udp_mem,以及每 socket 最小值相关设置。大量 UDP socket、多个高流量服务或容器共享同一内核时,单个 socket 配置合理也可能遇到全局压力。
同时采集系统内存、socket 数、UDP memory 和容器/主机边界。不要在容器内只调整看似独立的 sysctl 后假设宿主内核资源已改变。
应用是否足够快地读取
常见瓶颈是单次只读一个数据报,然后同步解析、写磁盘、调用数据库或打印大量日志。读取线程被慢操作阻塞时,内核队列很快溢出。
把网络读取与业务处理解耦,使用有界内部队列和背压/丢弃指标。Linux recvmmsg 可批量读取多个消息,减少系统调用开销;使用前应按语言库和并发模型正确实现。
应用内部队列也会丢
内核成功交付并不代表业务最终处理。线程池队列满、channel 非阻塞发送失败、采样、去重或异常解析都可能丢消息。为 ingress、decode、enqueue、process、persist 每一阶段计数。
队列必须有明确容量和溢出策略。无限队列只会把 UDP 丢包变成内存 OOM;静默 drop 会让故障无法观测。
SO_RXQ_OVFL 观察 socket 丢包
Linux SO_RXQ_OVFL 可通过辅助消息提供自 socket 创建以来因接收队列溢出而丢弃的包计数。应用需要显式启用并解析 control message。
将该计数暴露为指标,有助于把丢失归因到具体 socket,而不只看全局 RcvbufErrors。计数回绕、进程重启和多 socket 分片都要在监控中处理。
网卡 ring 与 softnet backlog
包可能在进入 UDP socket 前就因 NIC RX ring、驱动或 softnet backlog 满而丢弃。查看 ip -s link、ethtool -S、/proc/ 等增量,并检查 CPU softirq 饱和和 RSS/RPS 队列分布。
如果单个 RX queue/CPU 被打满,总 CPU 平均值仍可能很低。调整 ring、队列和中断亲和性前先确认硬件支持,并在变更后测延迟与乱序。
多核 SO_REUSEPORT 分流
多个 socket 使用 SO_REUSEPORT 监听同一端口时,内核会按规则分配数据报。哈希不均、某个 worker 卡住或 BPF reuseport 程序错误,可能只让单个队列溢出。
按进程/socket 观察 Recv-Q、CPU 和丢包,不要只汇总端口。Worker 数增加也不保证均衡,流量五元组单一时可能集中到一个队列。
数据报截断不是丢包
应用提供的接收缓冲小于数据报时,UDP 消息可能被截断,并出现 MSG_TRUNC 等信号。若程序忽略标志,后续解析失败会被误报为丢包。
记录实际 datagram length、用户 buffer size 和 recv flags。不要假设 MTU 以内就一定小于应用缓冲,封装和协议头也会影响。
IP 分片与路径 MTU
大型 UDP 数据报可能在 IP 层分片。任一分片丢失,整个原始数据报无法重组;高并发分片还会消耗重组队列。RFC 8085 建议应用避免依赖 IP 分片并遵守拥塞控制原则。
在发送端控制 datagram 大小,优先使用应用层分块和序列校验。抓包检查 fragment 标志与 ICMP,比较不同大小的丢包率。不要只调大接收缓冲掩盖路径分片问题。
防火墙与 conntrack
iptables/
检查规则计数器、conntrack 使用率和 drop 日志。高频逐包日志会进一步压垮系统,应使用计数和限速采样。
多播的额外边界
UDP multicast 需要正确加入组、选择接口,并依赖交换机 IGMP snooping/
检查组成员、绑定地址、网卡和 source-specific multicast 条件。多进程绑定时还要确认 reuse 语义和每个 socket 是否都加入组。
发送端也可能丢
发送端 socket buffer 满、应用忽略 send 错误、网卡队列丢弃或限速器都可能减少实际线上数据。UDP send 成功只说明数据交给本地内核,不保证对端接收。
发送端同样记录序列、send 返回值、接口计数和实际抓包。端到端可靠性需要应用确认、重传或 FEC 等机制,不能期待 UDP 自带交付保证。
一套逐层排查流程
第一步,建立发送序列号和速率基线。第二步,对比发送端/接收端抓包。第三步,读取接口、softnet、IP/UDP 全局计数。第四步,按 socket 观察 Recv-Q 与 SO_RXQ_OVFL。第五步,检查应用 recv、内部队列和处理计数。第六步,测试数据报大小与分片。第七步,检查 reuseport、多播和防火墙。第八步,用可控 burst 复现并调优。
修复后测试稳定流量、短突发、超大数据报、单一五元组、多 worker、CPU 压力和应用下游变慢。记录端到端丢失率、延迟、缓冲峰值和所有 drop 计数。
常见错误
常见误区包括:tcpdump 看见就认定应用收到;只看平均 PPS;无限增大 rmem;读取线程同步写日志/数据库;忽略内部队列 drop;多核总 CPU 低就排除软中断;把 MSG_TRUNC 当丢包;依赖 IP 分片;以及 UDP send 成功就当交付成功。
常见问题
RcvbufErrors 增长说明什么?
通常表示 UDP socket 接收队列因缓冲空间不足而丢弃数据。应同时检查 Recv-Q、应用读取速度和 SO_RCVBUF,而不只是调大系统上限。
增大 SO_RCVBUF 能完全解决吗?
只能吸收有限突发。若长期到达速率高于处理速率,缓冲最终仍满,还会增加排队延迟和内存使用。
tcpdump 有包为何应用 recvfrom 没返回?
包可能在抓包点之后被校验、路由、防火墙、分片重组或 socket 缓冲丢弃,也可能送到另一个 reuseport socket。
UDP 数据报被截断怎么识别?
检查 recv flags 中的 MSG_TRUNC、实际报文长度和用户缓冲大小。解析失败不应直接归为网络丢包。
UDP 怎样保证消息不丢?
UDP 本身不保证交付。需要应用层序列号、确认、重传、去重、超时或 FEC,并遵循拥塞控制和数据报大小建议。
总结
Linux UDP 丢包必须从网卡到应用逐层计数。抓包可见只是进入路径的早期证据,RcvbufErrors、Recv-Q、SO_RXQ_OVFL、softnet 和应用内部队列才能定位真实丢失点。通过受控 burst、合理缓冲、快速批量读取、分片控制和应用层可靠机制,可以减少丢包并让剩余损失可观测、可恢复。
来源资料
- Linux man-pages, udp(7): https:
/ / man7. org/ linux/ man- pages/ man7/ udp. 7. html - Linux man-pages, socket(7): https:
/ / man7. org/ linux/ man- pages/ man7/ socket. 7. html - Linux Kernel, IP sysctl: https:
/ / docs. kernel. org/ networking/ ip- sysctl. html - RFC Editor, RFC 8085 — UDP Usage Guidelines: https:
/ / www. rfc- editor. org/ rfc/ rfc8085