版权声明:本文由原创文章转載请注明出处:
作者介绍:黄日成,手Q游戏中心后台开发腾讯高级工程师。从事C++服务后台开发4年多主要负责手Q游戏中心后台基础系统、複杂业务系统开发,主导过手Q游戏公会、企鹅电竞App-对战系统等项目的后台系统设计有丰富的后台架构经验。
作为文章”《》”的姊妹篇很早就计划写篇关于UDP的文章,尽管UDP协议远没TCP协议那么庞大、复杂但是,要想将UDP描述清楚用好UDP却要比TCP难不少,于是文章从下笔写到朂终写成,断断续续拖了好几个月
3.1 UDP的传输方式:面向报文
面向报文的传输方式决定了UDP的数据发送udp数据包的方式是方式是一份一份的,也僦是应用层交给UDP多长的报文UDP就照样发送udp数据包的方式是,即一次发送udp数据包的方式是一个报文那么UDP的报文大小由哪些影响因素呢?UDP数據包的理论长度是多少合适的UDP数据包应该是多少呢?
(1)UDP报文大小的影响因素主要有以下3个
理论上UDP报文最大长度是65507字节,实际上发送udp数据包的方式是这么大的数据包效果最好吗我们知道UDP是不可靠的传输协议,为了减少UDP包丢失的风险我们最好能控制UDP包在下层协议的传输过程中不要被切割。相信大家都知道MTU这个概念
MTU最大传输单元,这个最大传输单元实际上和链路层协议有着密切的关系EthernetII帧的结构DMAC+SMAC+Type+Data+CRC由于以太網传输电气方面的限制,每个以太网帧都有最小的大小64字节最大不能超过1518字节,对于小于或者大于这个限制的以太网帧我们都可以视之為错误的数据帧一般的以太网转发设备会丢弃这些数据帧。由于以太网EthernetII最大的数据帧是1518字节除去以太网帧的帧头(DMAC目的MAC地址48bit=6Bytes+SMAC源MAC地址48bit=6Bytes+Type域2bytes)14Bytes和帧尾CRC校验部分4Bytes那么剩下承载上层协议的地方也就是Data域最大就只能有1500字节这个值我们就把它称之为MTU。
在下层数据链路层最大传输单元是1500芓节的情况下要想IP层不分包,那么UDP数据包的最大大小应该是1500字节 – IP头(20字节) – UDP头(8字节) = 1472字节不过鉴于Internet上的标准MTU值为576字节,所以建议在进行Internet嘚UDP编程时最好将UDP的数据长度控制在 (576-8-20)548字节以内。
3.2 UDP数据包的发送udp数据包的方式是和接收问题
在阻塞模式下UDP的通信是以数据包作为界限的,即使server端的缓冲区再大也要按照client发包的次数来多次接收数据包server只能一次一次的接收,client发送udp数据包的方式是多少次server就需接收多少次,即客戶端分几次发送udp数据包的方式是过来服务端就必须按几次接收。
(2) UDP数据包的无序性和非可靠性
client依次发送udp数据包的方式是1、2、3三个UDP数据包server端先后调用3次接收函数,可能会依次收到3、2、1次序的数据包收包可能是1、2、3的任意排列组合,也可能丢失一个或多个数据包
由于UDP通信嘚有界性,接收到只能是500或300又由于UDP的无序性和非可靠性,接收到可能是300也可能是500,也可能一直阻塞在recvfrom调用上直到超时返回(也就是什麼也收不到)。
在假定数据包是不丢失并且是按照发送udp数据包的方式是顺序按序到达的情况下server端阻塞模式下接包,先后三次调用:recvfrom( 200)recvfrom( 1000),recvfrom( 1000)接收情况如何呢?
由于UDP通信的有界性第一次recvfrom( 200)将接收第一个500字节的数据包,但是因为用户空间buf只有200字节于是只会返回前面200字节,剩下300字節将丢弃第二次recvfrom( 1000)将返回300字节,第三次recvfrom( 1000)将会阻塞
根据UDP通信的有界性,在buf足够大的情况下接收到的一定是一个完整的数据包,UDP数据在下層的分片和组片问题由IP层来处理提交到UDP传输层一定是一个完整的UDP包,那么recvfrom(9000)将返回8000如果某个IP分片丢失,udp里有个CRC检验如果包不完整就会丟弃,也不会通知是否接收成功所以UDP是不可靠的传输协议,那么recvfrom(9000)将阻塞
在不考虑UDP下层IP层的分片丢失,CRC检验包不完整的情况下造成UDP丢包的因素有哪些呢?
/proc/sys/net/core/rmem_max可以查看socket缓冲区的缺省值和最大值如果socket缓冲区满了,应用程序没来得及处理在缓冲区中的UDP包那么后续来的UDP包会被內核丢弃,造成丢包在socket缓冲区满造成丢包的情况下,可以通过增大缓冲区的方法来缓解UDP丢包问题但是,如果服务已经过载了简单的增大缓冲区并不能解决问题,反而会造成滚雪球效应造成请求全部超时,服务不可用
如果Client发送udp数据包的方式是的UDP报文很大,而socket缓冲区過小无法容下该UDP报文那么该报文就会丢失。
ARP的缓存时间约10分钟APR缓存列表没有对方的MAC地址或缓存过期的时候,会发送udp数据包的方式是ARP请求获取MAC地址在没有获取到MAC地址之前,用户发送udp数据包的方式是出去的UDP数据包会被内核缓存到arp_queue这个队列中默认最多缓存3个包,多余的UDP包會被丢弃被丢弃的UDP包可以从/proc/net/stat/arp_cache的最后一列的unresolved_discards看到。当然我们可以通过echo
在外网通信链路不稳定的情况下有什么办法可以降低UDP的丢包率呢?┅个简单的办法来采用冗余传输的方式如下图,一般采用较多的是延时双发双发指的是将原本单发的前后连续的两个包合并成一个大包发送udp数据包的方式是,这样发送udp数据包的方式是的数据量是原来的两倍这种方式提高丢包率的原理比较简单,例如本例的冗余发包方式在偶数包全丢的情况下,依然能够还原出完整的数据也就是在这种情况下,50%的丢包率依然能够达到100%的数据接收。
相信很多同学都認为UDP无连接无需重传和处理确认,UDP比较高效然而UDP在大多情况下并不一定比TCP高效,TCP发展至今天为了适应各种复杂的网络环境,其算法巳经非常丰富协议本身经过了很多优化,如果能够合理配置TCP的各种参数选项那么在多数的网络环境下TCP是要比UDP更高效的。
(1) 无法智能利用涳闲带宽导致资源利用率低
一个简单的事实是UDP并不会受到MTU的影响MTU只会影响下层的IP分片,对此UDP一无所知在极端情况下,UDP每次都是发小包包是MTU的几百分之一,这样就造成UDP包的有效数据占比较小(UDP头的封装成本);或者UDP每次都是发巨大的UDP包,包大小MTU的几百倍这样会造成下层IP層的大量分片,大量分片的情况下其中某个分片丢失了,就会导致整个UDP包的无效由于网络情况是动态变化的,UDP无法根据变化进行调整发包过大或过小,从而导致带宽利用率低下有效吞吐量较低。而TCP有一套智能算法当发现数据必须积攒的时候,就说明此时不积攒也鈈行TCP的复杂算法会在延迟和吞吐量之间达到一个很好的平衡。
(2) 无法动态调整发包
由于UDP没有确认机制没有流量控制和拥塞控制,这样在網络出现拥塞或通信两端处理能力不匹配的时候UDP并不会进行调整发送udp数据包的方式是速率,从而导致大量丢包在丢包的时候,不合理嘚简单重传策略会导致重传风暴进一步加剧网络的拥塞,从而导致丢包率雪上加霜更加严重的是,UDP的无秩序性和自私性一个疯狂的UDP程序可能会导致这个网络的拥塞,挤压其他程序的流量带宽导致所有业务质量都下降。
可能有同学想到针对UDP的一些缺点在用户态做些調整改进,添加上简单的重传和动态发包大小优化然而,这样的改进并比简单的UDP编程可是比TCP要难不少的,考虑到改造成本为什么不矗接用TCP呢?当然可以拿开源的一些实现来抄一下(例如:libjingle)或者拥抱一下Google的QUIC协议,然而这些都需要不少成本的。
上面说了这么多难道真嘚不该用UDP了吗?其实也不是的在某些场景下,我们还是必须UDP才行的那么UDP的较为合适的使用场景是哪些呢?
5.1 通信实时性和持续性
在分组茭换通信当中协议栈的成本主要表现在以下两方面:
[1] 封装带来的空间复杂度[2] 缓存带来的时间复杂度
以上两者是对立影响的,如果想减少葑装消耗那么就必须缓存用户数据到一定量在一次性封装发送udp数据包的方式是出去,这样每个协议包的有效载荷将达到最大化这无疑昰节省了带宽空间,带宽利用率较高但是延时增大了。如果想降低延时那么就需要将用户数据立马封装发出去,这样显然会造成消耗哽多的协议头等消耗浪费带宽空间。
因此我们进行协议选择的时候,需要重点考虑一下空间复杂度和时间复杂度间的平衡通信的持續性对两者的影响比较大,根据通信的持续性有两种通信类型:[1] 短连接通信 [2]
长连接通信对于短连接通信,一方面如果业务只需要发一两個包并且对丢包有一定的容忍度同时业务自己有简单的轮询或重复机制,那么采用UDP会较为好些在这样的场景下,如果用TCP仅仅握手就需要3个包,这样显然有点不划算一个典型的例子是DNS查询。另一方面如果业务实时性要求非常高,并且不能忍受重传那么首先就是UDP了戓者只能用UDP了,例如NTP
协议重传NTP消息纯属添乱(为什么呢?重传一个过期的时间包过来还不如发一个新的UDP包同步新的时间过来)。如果NTP协议采用TCP撇开握手消耗较多数据包交互的问题,由于TCP受Nagel算法等影响用户数据会在一定情况下会被内核缓存延后发送udp数据包的方式是出去,這样时间同步就会出现比较大的偏差协议将不可用。
对于一些多点通信的场景如果采用有连接的TCP,那么就需要和多个通信节点建立其雙向连接然后有时在NAT环境下,两个通信节点建立其直接的TCP连接不是一个容易的事情在涉及NAT穿越的时候,UDP协议的无连接性使得穿透成功率更高(原因详见:由于UDP的无连接性那么其完全可以向一个组播地址发送udp数据包的方式是数据或者轮转地向多个目的地持续发送udp数据包的方式是相同的数据,从而更为容易实现多点通信)
一个典型的场景是多人实时音视频通信,这种场景下实时性要求比较高可以容忍一定嘚丢包率。比如:对于音频对端连续发送udp数据包的方式是p1、p2、p3三个包,另一端收到了p1和p3在没收到p2的保持p1的最后一个音(也是为什么有時候网络丢包就会听到嗞嗞嗞嗞嗞嗞…或者卟卟卟卟卟卟卟卟…重音的原因),等到到p3就接着播p3了不需要也不能补帧,一补就越来越大嘚延时对于这样的场景就比较合适用UDP了,如果采用TCP那么在出现丢包的时候,就可能会出现比较大的延时
通常情况下,UDP的使用范围是較小的在以下的场景下,使用UDP才是明智的
[1] 实时性要求很高并且几乎不能容忍重传
例子:NTP协议,实时音视频通信多人动作类游戏中人粅动作、位置
[2] TCP实在不方便实现多点传输的情况
[4] 对网络状态很熟悉,确保udp网络中没有氓流行为疯狂抢带宽