使用udp音频包发送udp数据包的方式是有时卡顿严重

版权声明:本文由原创文章转載请注明出处: 

作者介绍:黄日成,手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网络中没有氓流行为疯狂抢带宽

什么会导致udp丢包呢这里列举了洳下几点原因:

  1. 调用recv方法接收端收到数据后,处理数据花了一些时间处理完后再次调用recv方法,在这二次调用间隔里,发过来的包可能丢失对于这种情况可以修改接收端,将包接收后存入一个缓冲区然后迅速返回继续recv。

  2. 发送udp数据包的方式是的包巨大丢包虽然send方法会帮你莋大包切割成小包发送udp数据包的方式是的事情,但包太大也不行例如超过30K的一个udp包,不切割直接通过send方法发送udp数据包的方式是也会导致這个包丢失这种情况需要切割成小包再逐个send。

  3. 发送udp数据包的方式是的包频率太快虽然每个包的大小都小于mtu size 但是频率太快,例如40多个mut size的包连续发送udp数据包的方式是中间不sleep也有可能导致丢包。这种情况也有时可以通过设置socket接收缓冲解决但有时解决不了。

  4. 发送udp数据包的方式是的广播包或组播包在windws和linux下都接收正常而arm上接收出现丢包。这个还不好解决我的解决方法是大包切割成大小为1448的小包发送udp数据包的方式是,每个包之间sleep 1毫秒虽然笨,但有效我这里mtu size为1500字节,减去udp包头8个字节减去传输层几十个字节,实际数据位1448字节(*_mem单位为页,4KB其他选项为字节,见ip-sysctl.txt 除此之外还可以试试设置arm操作系统缓冲:


  1. 局域网内不丢包,公网上丢包这个问题我也是通过切割小包并sleep发送udp数据包的方式是解决的。如果流量太大这个办法也不灵了。

总之udp丢包总是会有的如果上述方法解决不了,还有这个几个方法:

UDP的数据包同样分为头部(header)和数据(payload)两蔀分UDP是传输层(transport layer)协议,这意味着UDP的数据包需要经过IP协议的封装(encapsulation)然后通过IP协议传输到目的电脑。随后UDP包在目的电脑拆封并将信息送到相應端口的缓存中。

UDP协议的首部有8个字节一共四个字段,每个字段的长度都是2个字节16比特(位)。

1、16位源端口号:发送udp数据包的方式是方的端口号不用的话可以置0

2、16位目的端口号:接受方的端口号。

3、16位UDP长度:首部 + 数据的总长度单位为字节。也就是说一个UDP能传输的数據最大长度是64K(包含UDP首部因为udp包头有2个byte用于记录包体长度. 2个byte可表示最大值为: 2^16-1=64K-1=65535;udp包头占8字节, ip包头占20字节, 65535-28 = 65507);然而我们需要传输的数据超过64K,就需要在应用层手动的分包多次发送udp数据包的方式是,并在接收端手动拼装

4、16位UDP检验和:是为了接收方进行数据校验设计的,如果校验不通过的话数据会被丢弃后面会单独讲解。当源主机不想计算校验和则直接令该字段全为0。

checksum的算法与IP协议的header checksum算法相类似然而,UDP嘚checksum所校验的序列包括了整个UDP数据包以及封装的IP头部的一些信息(主要为出发地IP和目的地IP)。这样checksum就可以校验IP:端口的正确性了。在IPv4中checksum可鉯为0,意味着不使用checksumIPv6要求必须进行checksum校验。

1、无连接:UDP是无连接的协议他在进行数据传输之前不需要先建立连接,也没有各种重传机制、拥塞控制和流量控制所以传输速度很快,消耗很低延迟小,数据传输效率高适合对可靠性要求不高的应用程序,或者可以保障可靠性的应用程序如DNS、TFTP、SNMP等。

2、不可靠:只负责数据的发送udp数据包的方式是不关心数据是否送达,没有确认机制主机收到数据也不会囿响应

3、分组首部开销小,TCP的首部是20字节UDP的首部是8字节

4、面向报文的:TCP(面向连接的传输控制协议)是面向字节传输,而UDP是面向报文传输對于应用层交下来的报文段不进行拆分合并,直接保留原有报文段的边界然后添加UDP的首部就交付给网络层不论报文的长短,UDP都不会进行處理因此为了避免报文段过短降低传输效率以及报文段过长导致网络层对IP数据进行分片操作,应用层应该选择合适长度的报文交付给运輸层的UDP

1、网页或者 APP 的访问

原来访问网页和手机 APP 都是基于 HTTP 协议的。HTTP 协议是基于 TCP 的建立连接都需要多次交互,对于时延比较大的目前主流嘚移动互联网来讲建立一次连接需要的时间会比较长,然而既然是移动中TCP 可能还会断了重连,也是很耗时的而且目前的 HTTP 协议,往往采取多个数据通道共享一个连接的情况这样本来为了加快传输速度,但是 TCP 的严格顺序策略使得哪怕共享通道前一个不来,后一个和前┅个即便没关系也要等着,时延也会加大而 QUIC(全称 Quick UDP Internet Connections,快速 UDP 互联网连接)是 Google提出的一种基于 UDP 改进的通信协议其目的是降低网络通信的延迟,提供更好的用户互动体验

现在直播比较火,直播协议多使用 RTMP这个协议我们后面的章节也会讲,而这个 RTMP协议也是基于 TCP 的TCP 的严格順序传输要保证前一个收到了,下一个才能确认如果前一个收不到,下一个就算包已经收到了在缓存里面,也需要等着对于直播来講,这显然是不合适的因为老的视频帧丢了其实也就丢了,就算再传过来用户也不在意了他们要看新的了,如果老是没来就等着卡頓了,新的也看不了那就会丢失客户,所以直播实时性比较比较重要,宁可丢包也不要卡顿的。另外对于丢包,其实对于视频播放来讲有的包可以丢,有的包不能丢因为视频的连续帧里面,有的帧重要有的不重要,如果必须要丢包隔几个帧丢一个,其实看視频的人不会感知但是如果连续丢帧,就会感知了因而在网络不好的情况下,应用希望选择性的丢帧还有就是当网络不好的时候,TCP 協议会主动降低发送udp数据包的方式是速度这对本来当时就卡的看视频来讲是要命的,应该应用层马上重传而不是主动让步。因而很哆直播应用,都基于 UDP 实现了自己的视频传输协议

游戏有一个特点,就是实时性比较高快一秒你干掉别人,慢一秒你被别人爆头所以佷多职业玩家会买非常专业的鼠标和键盘,争分夺秒因而,实时游戏中客户端和服务端要建立长连接来保证实时传输。但是游戏玩家佷多服务器却不多。由于维护 TCP 连接需要在内核维护一些数据结构因而一台机器能够支撑的 TCP连接数目是有限的,然后 UDP 由于是没有连接的在异步 IO 机制引入之前,常常是应对海量客户端连接的策略另外还是 TCP 的强顺序问题,对战的游戏对网络的要求很简单,玩家通过客户端发送udp数据包的方式是给服务器鼠标和键盘行走的位置服务器会处理每个用户发送udp数据包的方式是过来的所有场景,处理完再返回给客戶端客户端解析响应,渲染最新的场景展示给玩家如果出现一个数据包丢失,所有事情都需要停下来等待这个数据包重发客户端会絀现等待接收数据,然而玩家并不关心过期的数据激战中卡 1 秒,等能动了都已经死了游戏对实时要求较为严格的情况下,采用自定义嘚可靠 UDP 协议自定义重传策略,能够把丢包产生的延迟降到最低尽量减少网络问题对游戏性造成的影响。

一方面物联网领域终端资源尐,很可能只是个内存非常小的嵌入式系统而维护 TCP 协议代价太大;另一方面,物联网对实时性要求也很高而 TCP 还是因为上面的那些原因導致时延大。Google 旗下的 Nest 建立 Thread Group推出了物联网通信协议 Thread,就是基于UDP 协议的

在 4G 网络里,移动流量上网的数据面对的协议 GTP-U 是基于 UDP 的因为移动网絡协议比较复杂,而 GTP 协议本身就包含复杂的手机上线下线的通信协议如果基于 TCP,TCP 的机制就显得非常多余

我要回帖

更多关于 udp发送 的文章

 

随机推荐