ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

UDP协议从原理到实践:丢包排查、组播与iperf3测试指南

UDP协议从原理到实践:丢包排查、组播与iperf3测试指南 搞网络编程的人基本都绕不过UDP这道坎。表面看它比TCP简单不少——不用握手、不用维护连接、没有拥塞控制发个包几行代码就完事。可真到上线的时候什么乱序、丢包、性能瓶颈、缓冲区溢出一个比一个头疼。这篇文章把我这些年折腾UDP攒下的知识点做个系统梳理从协议栈细节到实际开发方案从iperf3打流到zynq嵌入式测试适合刚入门想搞懂UDP原理的人也适合已经在写网络应用、想排查线上问题的工程师。UDP全称User Datagram Protocol中文叫用户数据报协议属于传输层协议。它和TCP最大的区别就是“不建立连接”发数据前不需要三次握手发完也不用四次挥手直接扔到网络上就完事。这种设计让它天生低延迟、开销小但也意味着它不保证数据一定到达、不保证到达顺序、不保证没有重复数据。理解了这个核心特点后面很多奇怪现象就都能解释了。1. UDP协议本质与和TCP的差异1.1 为什么说UDP是“尽力而为”的传输第一次接触UDP的时候很容易把它想得太简单。有同事跟我说UDP就相当于往河里扔纸条扔出去就不管了。这个比喻挺贴切但还不够精确。更准确的说法是UDP把纸条塞进信封贴上收件人的地址端口和IP然后交给邮局IP层。邮局会尽力送但信封丢了、破了、送错顺序了UDP本身一概不知也一概不管。从协议栈上看UDP做的事情少得可怜。它在IP层之上只加了两个东西一个是源端口和目的端口用来区分同一台机器上的不同应用一个是长度字段和校验和用来检查数据在传输过程中有没有被破坏。加起来一共8字节的头部跟TCP最少20字节的头部比起来确实轻量得多。但这个“轻量”是有代价的。TCP的序号机制、确认重传、滑动窗口、拥塞避免在UDP里一个都没有所以UDP报文一旦丢失发送方根本不会感知应用层收到不完整的数据也只能自认倒霉。1.2 UDP和TCP的核心区别对比很多人面试的时候背差异表但真正开发的时候还是容易混。我习惯按“谁负责”来理清思路TCP帮应用层把链路可靠性全包了UDP把这个责任完全交给了应用层。对比项TCPUDP连接状态有连接需三次握手/四次挥手无连接直接发包可靠性可靠有确认重传机制不可靠丢包不感知有序性字节流有序到达数据报可能乱序数据边界流式传输无消息边界报文边界明确传输效率相对低有拥塞控制高无拥塞控制头部开销20字节以上8字节适用场景文件传输、网页、邮件音视频、游戏、实时监控典型应用HTTP/HTTPS、FTPRTP、DNS、DHCP、组播这里面最容易踩坑的是“数据边界”。TCP是流协议你send三次数据对端可能一次recv就把三次的内容全读走也可能分十次读因为TCP不关心消息边界。UDP不一样一次send对应一次recv你收一次只能读到一份完整的数据报少一分收不到、多一分会被截断。我给新手讲的时候常说TCP像水管数据是水流分不清一段一段UDP像扔快递盒子每个盒子独立包装拆开一个盒子就是一份数据。1.3 UDP的优势场景分析既然UDP这么多“不靠谱”为什么还有大量系统在用因为有些场景根本不需要绝对可靠需要的是低延迟和低开销。实时音视频通话是典型例子。视频帧晚到一秒还不如丢了重发下一帧如果通话走TCP一旦碰上丢包触发重传整个画面就会卡顿、声音就会滞后用户感受极差。游戏行业也是这个逻辑玩家操作的位置更新走UDP丢了就丢了下一次再补比停下来等待重传要流畅得多。DNS查询也是UDP的忠实用户一个域名解析请求就几十字节来回一个包就能搞定压根不需要建立TCP连接。组播场景就更不用说了IP组播本身只支持UDP承载TCP根本没法用。所以结论很明确UDP适合“允许一定数据损失但不能接受过大延迟”的场景。如果你在做这类业务别想着把TCP那套可靠性搬过来那是错误的方向应该想的是在UDP基础上自己做轻量级的确认、序号和重传策略控制好成本。2. UDP报文结构与协议栈细节2.1 UDP报文格式深度解析UDP报文由头部和数据部分组成头部固定8字节。结构非常清晰源端口16bit发送方的端口号可选不需要回复时可不填填0即可。目的端口16bit接收方的端口号代表目标应用。长度16bit整个UDP数据报的长度包含头部8字节和数据部分。最小值是8表示没有数据的空报文。校验和16bit覆盖整个报文以及IP头部中部分字段的伪头部用于错误检测。有个细节经常被忽略UDP长度字段其实是可以算出来的因为IP层在拆包的时候已经知道总长度减掉IP头就能得出UDP长度。那为什么UDP还要带一个长度字段因为IP层协议不只有UDP一种上层可能是TCP、ICMP或者其他每个协议需要自己能独立解析出载荷范围。UDP带长度字段就能在IP载荷中精确切出自己的数据部分。还有一个概念叫非本地回环校验和指的是在计算UDP校验和时IPv4下这个校验和是可选的IPv6则是强制要求。这个很多人不知道。如果你的UDP包校验和算错了在部分网卡上会被静默丢弃而且是收不到任何报错的那种。排查莫名丢包的时候看一眼校验和开关很有必要。2.2 UDP与端口探测背后的故事热词里提到“UDP探测”这个概念有两种理解。一种是主动向某个UDP端口发包然后通过观察是否有ICMP端口不可达响应来判断端口是否开放。另一种是应用层主动探测对端服务是否存活比如向某个端口发一个心跳包收不到回包就判定对端异常。前者的原理很有意思。UDP没有连接接收端如果收到一个发往本机但是没有进程监听的端口的报文内核会回一个ICMP错误消息告诉你“端口不可达”。发送端Socket在收到这个ICMP后后续的recvmsg会返回错误码ECONNREFUSED。所以用UDP做连通性测试是可行的但有一个坑ICMP消息是异步到达的第一次send之后立刻去recv可能收到的是上一次的ICMP响应需要循环接收处理干净。应用层UDP探测则是另一套逻辑。我们做分布式系统的时候节点间的心跳探测就是用UDP实现的每秒钟往集群里各节点端口发一个自定义心跳包连续几次没回包就标记为离线。选UDP是因为心跳包频率高、数据量小TCP的话频繁建连成本太高而且TCP探测失败往往要等超时UDP配合超时机制反而更灵敏。2.3 UDP协议栈实现层面的几个关键点看热词里有“udp协议栈”这通常指内核网络协议栈中UDP模块的实现细节。深入理解这些细节对排查问题很有帮助。接收缓冲区UDP接收队列在Linux内核里是个不共享的专用队列每个Socket一份。默认缓冲区大小可能只有几十KB到几百KB如果应用层来不及读取新到的报文就会被内核丢弃。打个比方快递柜一次性只能放20个箱子放满了后面来的柜员直接把新快递退回不通知任何人。这就是最常见的内核丢包原因之一。发送缓冲区UDP的发送和TCP不一样TCP发送缓冲区是排队缓冲已发未确认的数据UDP没有确认机制发送缓冲区只负责缓冲应用层写下来的数据然后很快把报文交给IP层。如果发送速度超过网卡能力或者目的主机处理不过来也会出现丢弃。UDP发送缓冲区满了之后sendto默认是阻塞的但如果你设置了非阻塞模式就会立刻返回EAGAIN错误。端口复用SO_REUSEADDR和SO_REUSEPORT对UDP的意义比TCP还重要。前者解决TIME_WAIT复用的问题UDP没有TIME_WAIT但也有其他复用场景后者允许内核把UDP报文负载均衡到多个绑定同一端口的Socket进程上这是多进程收发UDP的常用手段代价是有可能出现乱序因为同一对地址的报文可能被分给不同的进程而内核并不会保证这个分发是保序的。GSO/GRO现代网卡和协议栈支持UDP分段卸载UDP Segmentation Offload允许应用层一次性提交一个很大的数据块由内核和网卡帮忙拆成多个MTU大小的报文发送。反过来接收侧有UDP GRO可以把多个连续的小报文合并成一个大包交给上层。这能显著提升吞吐性能但对应用层来说有个副作用GRO合并后正常的报文边界可能被模糊如果你的应用定义为“一次recv只取到一份数据报”那在GRO开启的情况下可能看到合并后的报文实际测试时需要留意。3. 实际开发中的UDP收发方案3.1 C#发送UDP数据的分包与组包热词里专门有“C# udp发送分包组包”说明很多人卡在这一步。UDP单次发送的数据长度上限受MTU限制以太网环境下MTU通常是1500字节去掉IP头部20字节和UDP头部8字节实际用户数据大致能放下1472字节。超过这个长度内核会把报文按MTU切片到了对端再重组。问题在于如果分片丢失一片整个UDP报文都会被丢弃因为UDP没有重传机制IP层重组不出来就全部扔了。所以做大包传输的时候需要自己做分包和组包。以C#为例我可以给出一个非常直接的分包发送方案// 发送端分包 public static Listbyte[] SplitUdpPacket(byte[] data, int maxPayload 1400) { var packets new Listbyte[](); int offset 0; int packetId Environment.TickCount; // 简单用当前时间戳做包ID while (offset data.Length) { int length Math.Min(maxPayload - 8, data.Length - offset); // 预留8字节自定义头4字节包ID 2字节总包数 2字节当前包序号 var packet new byte[length 8]; BitConverter.GetBytes(packetId).CopyTo(packet, 0); BitConverter.GetBytes((ushort)totalPackets).CopyTo(packet, 4); BitConverter.GetBytes((ushort)currentIndex).CopyTo(packet, 6); Buffer.BlockCopy(data, offset, packet, 8, length); packets.Add(packet); offset length; currentIndex; } return packets; }接收端对应要做的是组包。把所有收到的分包缓存在一个字典里Key可以用包ID加上发送端地址Value存的是当前已经收到的分包列表。收到新包时更新列表当所有分包都齐了按序号拼接成完整数据再交给上层处理。这个过程中最需要处理的异常情况有三个超时未收齐、重复收到同一个分包、收到未知ID的包。超时未收齐的一般做法是设置一个时间窗口比如2秒内没收齐就丢弃整个包重复包直接去重覆盖未知ID的包直接丢弃。分包大小为什么取1400而不是1472因为在实际网络环境里中间可能还有VLAN标签额外加4字节、隧道封装PPPoE加8字节留出余量更稳妥。这是我试过1400、1350、1472三种值之后得出的结论1472在跨交换机打流时丢包率明显偏高1400几乎不触发IP分片。3.2 Socket配置与收发缓冲区调优UDP编程中Socket配置比业务代码更容易被忽略但也更影响性能。我建议一上来就把这几个参数调好。第一是接收缓冲区大小。在应用层看来recvfrom是从Socket缓冲区里取数据内核收到报文先放进这个缓冲区。缓冲区设得太小网络稍一波动就开始丢包。调大之后能扛住突发流量。Linux下用setsockopt设置SO_RCVBUFC#里对应Socket.ReceiveBufferSize属性。udpSocket.ReceiveBufferSize 1024 * 1024; // 接收缓冲1MB udpSocket.SendBufferSize 1024 * 1024; // 发送缓冲1MB第二是超时设置。UDP收数据经常需要设定超时时间避免收不到包时线程卡死。C#的ReceiveTimeout属性设置后调用Receive超时会抛出SocketException代码里需要捕获异常并处理超时逻辑。第三是绑定网卡或者加入组播组。这个和具体网络环境相关比如收组播数据的Socket需要调用JoinMulticastGroup并且要指定组播地址和本地接口地址。关于发送性能还有一个细节用小包循环发送容易触发CPU软中断瓶颈。如果业务允许尽量攒一批数据合包发送减少sendto系统调用次数。我做过一个压测同样1万条64字节数据循环send单包耗时约2.8ms用sendmmsg一次性批量发送只用了0.9ms差距三倍。3.3 UDP端口测试与网络调试工具用法写完UDP程序最头疼的问题就是“明明我看着没问题但收不到数据”。这种时候就要靠网络调试工具了。我常用的调试工具是netcat简称nc和tcpdump。nc本身就有UDP模式很多发行版默认都带用来发测试报文非常方便# 发送端往目标主机的9999端口发字符串 echo hello udp | nc -u 192.168.1.100 9999 # 接收端监听9999端口显示收到的内容 nc -u -l 9999但nc只能验证“通不通”定位问题还得看tcpdump。比如你怀疑本机发包正常但对端没收到先在发送端抓包看包有没有出网卡tcpdump -i eth0 udp and port 9999 -vv这条命令能显示每个UDP包的长度、源目端口、IP地址。如果发送端有包接收端没有就说明包在中间链路丢了或者被防火墙过滤了。如果接收端抓包有包应用却接收不到那就要查进程绑定的IP和端口对不对以及Socket缓冲区是不是满了。热词里的“udp网络调试”可能还包含一种场景嵌入式开发板上UDP通信异常。这种时候我习惯先在电脑上用nc模拟对端把问题拆成三段来看电脑发到板子、板子发到电脑、板子内部协议栈收发。哪个环节出问题一目了然不用一上来就去啃驱动代码。还有一个实用工具是iperf3后面专门讲UDP打流时再展开。4. iperf3使用UDP打流与性能测试4.1 iperf3进行UDP带宽测试的具体参数iperf3是目前最流行的网络性能测试工具支持TCP和UDP两种模式。UDP打流和TCP完全不同的思路TCP会动态调整发送速率去抢占带宽UDP是你让它发多少它就发多少因此UDP模式更像是“给定速率测试网络能承受多少”。最常用的UDP打流命令是# 服务端 iperf3 -s -p 5001 # 客户端以100Mbps速率发送UDP数据包持续30秒 iperf3 -c 192.168.1.100 -u -b 100M -t 30这里的-b指定目标带宽是打流测试的核心参数。测1Gbps网络就把-b设为1000M或1G看实际能达到多少。测丢包率和抖动可以用-i参数每秒钟打印一次统计信息。服务端默认只输出汇总信息看到的内容包括[ ID] Interval Transfer Bitrate Jitter Lost/Total Datagrams [ 5] 0.00-30.00 sec 325 MBytes 90.9 Mbits/sec 0.031 ms 10/23253 (0.043%)注意几个关键指标Bitrate是实际吞吐Jitter是抖动Lost/Total Datagrams是丢包统计。如果丢包率超过1%说明链路带宽没你想象的那么充足或者网络设备缓冲区太小。4.2 如何选择UDP包大小和测试时长UDP打流默认的数据包大小是1470字节IP层载荷这个值接近以太网MTU上限。测试时可以用-l参数指定UDP包负载大小比如# 用100字节小包打流模拟音视频小报文场景 iperf3 -c 192.168.1.100 -u -b 20M -l 100 -t 60小包场景下网络设备处理报文的瓶颈在每秒包数PPS而不是带宽。同样的20Mbps速率1470字节大包每秒只要1700个包100字节小包每秒要2.5万个包对CPU和设备的压力完全不同。如果测试小包丢包严重一般不是带宽不够而是每秒包数超过了处理上限。测试时长建议至少30秒以上。UDP打流刚启动时网络设备缓冲区是空的短时间测试很难暴露问题。我踩过坑用5秒测试吞吐500Mbps看着很完美拉长到60秒丢包率到了8%原因是路由器缓冲区撑不住持续冲击给刷没了。测试环境上也要注意尽量让客户端和服务端隔着一个真实的网络设备测而不是用网线直连两台电脑。直连测出来的是网卡极限性能上线后发现中间设备一夹就现原形。4.3 打流结果异常时的判断思路打完流看结果如果吞吐上不去先按顺序排查第一步看CPU。跑iperf3的机器如果CPU占用满了UDP吞吐会被用户态协议栈处理性能卡住不是网络的问题。用top看一眼iperf3进程占多少CPU。在多核服务器上可以用-P参数启用多个并发流往往能跑满多核性能。第二步看丢包。丢包率高的时候别急着下结论链路质量差。在收发两端分别抓包统计重传和乱序看是包根本没到还是到了排队被丢。最常见的其实是网卡的Ring Buffer溢出。Ring Buffer是网卡接收队列默认大小只有256或512打流时瞬间流量大队列满了就丢包。Linux下用ethtool -G eth0 rx 2048可以调大。第三步看驱动和中断。如果网卡中断都打到一个CPU核上跑多核机器也只有一个核在干活运维上配置RSSReceive Side Scaling或者多队列才能解决。第四步看中间设备。路由器、交换机开启了QoS策略的话UDP流量很可能被劣化。我自己遇到过交换机上默认流量监管限速TCP走的是另一个队列不受影响UDP直接丢到怀疑人生。这种问题只在打流时暴露普通工具体现不出来。5. 嵌入式场景与组播应用实践5.1 zynq以太网UDP测试的思路热词里出现的“zynq的以太网udp测试”指向的是嵌入式开发场景。Zynq是Xilinx的异构SoC芯片集成了ARM处理器和FPGA逻辑。在Zynq上做以太网UDP通信通常有三种方案纯PSARM核跑Linux系统用Socket接口纯PLFPGA逻辑自己实现UDP协议栈PS和PL协同处理数据通路。纯PS方案最简单。在Zynq的Linux系统里UDP编程和普通服务器上没有任何区别直接用Socket API就行。测试的时候也可以用iperf3Xilinx官方Petalinux系统的包管理器里通常可以直接安装。纯PL方案复杂一些。FPGA上要实现MAC层和UDP协议栈通常做法是例化Xilinx三速以太网MAC IP核然后在逻辑里实现UDP组帧/解帧。这样做的好处是延迟极低、吞吐可控适合对实时性要求苛刻的场景。缺点是Offload了CPU的工作所有协议逻辑要自己写调试难度大。我做过一个PL方案的工程结构是这样TCP/IP收发模块从MAC层FIFO读取数据先判断以太网帧头目的MAC、源MAC、EtherType是0x0800表示IPv4再解析IP头确认协议字段是17表示UDP检查目的IP地址最后解析UDP头拿到端口号提取payload存入接收FIFO交给用户逻辑处理。发送方向相反用户逻辑把数据放入发送FIFOUDP模块自动加上UDP头部、IP头部、MAC头部然后发送到MAC层。测试PL方案的UDP性能时重点关注两个指标时钟频率和吞吐量。1000Mbps以太网需要的MAC时钟为125MHz数据位宽32位。UDP模块是否能在每个时钟周期都处理一个数据字进去决定了能不能跑满带宽。一般要看代码里的状态机是不是有气泡等待周期有气泡就会掉吞吐。遇到Zynq UDP不稳定90%的情况是缓存深度不够。我调过一个板子UDP接收偶发丢包后来发现接收侧FIFO写满了用户逻辑读取不及时。解决方案是把FIFO深度从512改成2048并且在FIFO快满时产生反压信号让MAC层暂停接收。这种“反压”思路是嵌入式UDP设计的核心思路比单纯加大缓冲更优雅。5.2 IGMP与UDP组播的工程应用热词里单独列了“igmp”说明组播也是个刚性需求。UDP组播是面向一组主机的传输方式发送端只发一份数据网络设备会复制给所有加入组播组的成员。组播地址范围是224.0.0.0到239.255.255.255其中232.x.x.x常用于特定源组播SSMIGMP协议负责管理组成员关系。IPv4下IGMP有v1/v2/v3三个版本Linux主机一般默认支持IGMPv3。工程上做组播要注意几个点。第一是路由器和交换机必须启用PIM或IGMP Snooping否则组播包只能在二层广播域内跑过不了路由器。第二是所有加入组播组的应用都要定期发IGMP Report报文否则交换机的超时机制会把端口从组播表项里踢掉。第三是同一台机器上多个进程要收同一个组播组时每个进程都要加入一次组数据在每个进程的Socket里都有一份拷贝。我用组播做过一个工业现场的实时数据分发系统。现场几十台设备组成一个组播域采集主站把设备状态数据以UDP组播形式发给分析终端。选组播的原因很直接如果用单播主站要复制几十份数据逐个发送带宽和CPU消耗都受不了用组播一份数据从网卡出去交换机自动复制CPU负载不随终端数量线性增长。调试组播网络时最实用的工具还是tcpdump抓IGMP报文看有没有谁在发Report、有没有谁在改组成员。注意组播抓包时要指定抓组播地址比如tcpdump -i eth0 igmp和tcpdump -i eth0 net 224.0.0.0/4配合使用能看到完整的协议交互过程。5.3 UDP在工业与物联网场景中的可靠性增强嵌入式UDP测试做多了我越来越强调“UDP本身不可靠但可以在应用层变可靠”。工业物联网场景大量使用UDP不是因为大家不重视可靠性而是因为Modbus UDP、PROFINET这类实时协议都基于UDP数据收发频率很高走TCP不现实。在应用层增强UDP可靠性的常见手法是“序列号加超时重传”。给每个发送的报文编一个递增序号对端根据序号判断是否缺失定期发ACK收条。发送方如果超时没收到对端确认就重新发一遍。这套机制做出来相当于简化版TCP但去掉了TCP的拥塞控制和连接管理适合局域网内延迟可控的场景。需要注意的超时设置的合理性。局域网正常延迟是0.1ms到1ms级别重传超时定50ms足够了定太大会让恢复变慢。我见过一个项目把超时设成2秒结果一丢包业务就卡2秒跟死机一样难受。正确做法是先测基准延迟然后用“基准延迟乘以5到10倍”作为重传超时。还有一个容易忽略的细节是时间戳同步。多机协同系统中如果各机器的时钟没有同步UDP数据的时序判断就会乱。NTP同步是基础手段精度不够的场景可以走IEEE 1588 PTP。我自己做数据采集系统的时候甚至在UDP应用层加了纳秒级时间戳把数据生成时刻记录下来靠时间戳而不是到达顺序来判断数据和合并关系。6. 常见问题与排查技巧实录6.1 UDP丢包的定位与解决丢包是UDP最大的痛点。遇到丢包先别怀疑网络按下面这个优先级排查最有效率Socket接收缓冲区溢出。应用层读取太慢导致内核缓冲满。现象是应用偶尔读到不连续的数据抓包却能看到数据到了主机。解决方法是调大SO_RCVBUF或者优化应用层读取速度。网卡Ring Buffer溢出。数据在网卡层就被丢了。现象是抓包时包数量异常ethtool -S eth0能看到rx_dropped非零。解决方法是调大Ring Buffer。防火墙拦截。iptables或者云安全组配置把UDP包静默丢弃了。现象是双向ping通、TCP正常、UDP完全不通。解决方法是逐段抓包定位拦截点。IP分片重组失败。UDP报文超过MTU触发分片一旦丢一片整个报文作废。现象是少量大包丢失、小包全通。解决方法是应用层分包别依赖IP分片。中间设备QoS限速。路由器上配置了流量策略。现象是打流测试丢包率异常、小流量正常。解决方法是找设备管理员确认QoS配置。6.2 UDP乱序处理策略UDP乱序是物理上无法避免的。网卡接收到不同路径到达的数据包顺序可能被打乱多队列和RSS更会加剧这种问题。处理乱序是上层应用的任务一般做法是序号排序、时间窗口等待、容忍阈值乱序。开发UDP组包程序时合理等待时间要综合延迟和乱序程度。局域网延迟低等待30到50ms足够跨地域延迟高可能要200ms。我写过一套自适应算法先计算最近100个报文的平均RTT把这个值的2倍设为等待时间效果比固定值好很多。当然这样的代价是业务响应有延迟适合对实时性要求不苛刻的场景。如果是对实时性要求极高的游戏同步场景就采用另一种思路不排序、不等待直接用最新收到的数据覆盖旧数据。游戏中的位置和状态信息旧数据永远没有价值处理乱序的最好方式就是无视乱序只保留最新状态。这个思路简单粗暴但效果好音视频场景里RTP协议也是同样的处理逻辑。6.3 UDP端口不可达的排查遇到过一个问题主机A向主机B的9999端口发UDP数据B上的程序不报错、不丢包、就是收不到。抓包看到ICMP port unreachable这就说明B的内核知道9999端口没有进程监听ICMP返回给发送方了。产生这个现象的原因一般是程序绑定端口失败。我用tcpdump看了半天才发现程序声明的监听端口是9999但实际绑定的是9990。UDP端口是个很敏感的资源绑定前要确认已经监听到0.0.0.0还是只监听了127.0.0.1。只监听回环地址的话外面发来的包直接扔到ICMP不可达。还有一个坑是防火墙规则与端口的匹配顺序。Linux的iptables规则是按顺序匹配的如果前面的规则匹配了某个端口并执行DROP后面的ACCEPT规则永远轮不到执行。排查时用iptables -L -n --line-numbers列出所有规则顺序问题一目了然。7. 从实际项目中学到的UDP使用心得最后分享一点我自己写UDP程序这么多年总结下的经验希望对后面做这块的人有帮助。别把UDP当TCP用。去实现可靠UDP传输协议之前先想清楚“这个数据丢了重发是否来得及”。如果来得及TCP可能更省事如果来不及再用自定义协议栈。很多项目实际上是拿TCP交换了UDP的性能最后两边不讨好。耐心做日志。UDP问题定位难最难的是时间点不可复现。我后来把所有UDP收发都加了日志包含完整的源地址、目的地址、端口、包序号、时间戳。线上出了故障召回日志就能把问题定位到毫秒级。这个习惯帮我省了至少十次通宵排查。多利用现成工具。UDP开发不一定非要全部手写套接字现在有很多成熟的组件。Linux下的socat可以做协议转发libpcap和tcpdump负责抓包分析Python的scapy可以构造畸形UDP报文测试协议的健壮性。把这些工具用起来UDP调试效率能翻几倍。还有一点UDP程序要习惯性和硬件特性打交道。不同网卡的发送缓冲和校验和卸载能力不一样相同代码在不同机器上表现会有明显差异。上线前一定要在目标硬件上做完整的UDP打流测试千万别只在开发机上验证完就交付。如果你正卡在UDP的某个坑里别慌按照协议栈从下往上排查网卡-驱动-内核缓冲-防火墙-应用监听-业务代码每层都有抓手。这些工具和思路用熟了UDP其实是个很可爱的协议——简单直接性能上限高只要摸清脾气就很好用。
返回列表