TCP/UDP丢包常见问题小结 增长 udp缓存

TCP/UDP常见问题小结

1,udp丢包html

困扰几天的udp内网传输部分终于作通了,解决的关键就在于setsockopt的调用,设置接收缓冲。linux

遇到的问题是这样的,主机端发送udp数据包:windows

    应用层的包大小为1452byte大小,这样拆包是根据以太网的MTU为1500字节而考虑的(固然外网状态下并不必定就是以太网网络,路由MTU可能更加小),由于在网络层和传输层还有8byte的udp包头和20byte的ip包头,因此以太网帧大小为1452+8+20 = 1480byte。服务器

   主机端(linux)如今接了11路视频数据,发送的数据量仍是很大的,但通过测试,数据是能够发送出去的,发送端没有问题。我在客户端用一个线程专门接包,而后进行处理,可老是处理不过来,当链接路数比较多的时候,即码流增大时,出现接收不过来的状况。开始觉得是主机端问题,进行写文件测试发现主机端彻底能够承受11路数据的发送(udp数据包),而接收端对每一个部分都进行了详细测试,都没有效率问题而影响接包的处理,最后将目光放在了接收缓冲的问题上,通过查证,windows程序默认的udp socket的接收和发送缓冲都是8kB, 而将接收缓冲调大后,立刻解决了丢包现象: 网络

int n = 512*1024;  setsockopt(m_hRcvSock, SOL_SOCKET, SO_RCVBUF, (const char*)&n, sizeof(n));socket

    可见对于通常而言,8kB是足够了,可是对于要接收大量数据时,默认的接收缓冲(udp)是不够的。须要进行手工设置,不然会形成包的丢失从而数据错误。之因此一开始没有想到这上面是由于咱们原来的网络是用tcp进行的视频传输(暂时没有对tcp的接收和发送缓冲进行查证),而tcp状态下咱们能够很好的在内网传输16路的实时视频数据,故觉得在发送和接收上udp和tcp同样不存在瓶颈问题。后查得tcp是会进行流量控制的,下面是一段摘抄:tcp

说到流量控制,不得不提到TCP的另外一个重要概念-—窗口。窗口表示了接收主机能接收的最大数据量,而且,窗口大小是随着主机资源和主机当前正在接收多少个传输数量而变化的。主机将窗口字段用于流量控制,也就是说,流量控制是TCP窗口的一个功能。TCP采用流量控制管理进入接收主机缓冲区的数据流量。若是发送主机传输数据的速度比接收主机处理数据的速度更快以致接收主机缓冲区已满不能处理更多的数据时,则接收主机就会请求发送主机下降数据发送速度直到接收主机能够接收更多的数据为止;相反,若是接收主机可以处理更多的数据,则会请求发送主机加快数据的发送速度,这就是流量控制的用途,它保证了数据在传输的过程当中完整的传送到接收主机。函数

    能够看出因为tcp是基于链接的,因此其在传输过程当中会牺牲不少来进行传输的保证,故即便速度降低也会保证接收端的有序和正确接收。而udp是非链接的,其发送后不进行任何处理在保证数据的传输,高效但无保障,一切检验和有序以及完整处理均须要应用层来完成(这让人想起了RTCP)。post

 

2,绑定失败测试

还有一个setsockopt的选相是SO_REUSEADDR, 今天在绑定一个地址来进行侦听的时候,处理上是每来一个链接,便侦听其地址发送来的udp端口,因为要屡次绑定,而开始时总遇到地址重复的错误,后来一查发现,同一个地址进行端口绑定,好像有一个时间间隔限制,该限制之内不能重复绑定,故我进行了以上的设置,就能够屡次重复绑定了~

通过查证,我遇到的是地址使用错误的问题:

       使用 bind API 函数来绑定一个地址(一个接口和一个端口)到一个套接字端点。能够在服务器设置中使用这个函数,以便限制可能有链接到来的接口。也能够在客户端设置中使用这个函数,以便限制应当供出去的链接所使用的接口。bind最多见的用法是关联端口号和服务器,并使用通配符地址(INADDR_ANY),它容许任何接口为到来的链接所使用。bind 广泛遭遇的问题是试图绑定一个已经在使用的端口。该陷阱是也许没有活动的套接字存在,但仍然禁止绑定端口(bind 返回EADDRINUSE),它由 TCP 套接字状态 TIME_WAIT 引发。该状态在套接字关闭后约保留 2 到 4 分钟。在 TIME_WAIT 状态退出以后,套接字被删除,该地址才能被从新绑定而不出问题。

等待 TIME_WAIT 结束多是使人恼火的一件事,特别是若是您正在开发一个套接字服务器,就须要中止服务器来作一些改动,而后重启。幸运的是,有方法能够避开 TIME_WAIT 状态。能够给套接字应用 SO_REUSEADDR 套接字选项,以便端口能够立刻重用。

     一样,我在每次UDP侦听socket使用完毕后,使用closesocket将使用的socket清除,这样手动释放后,不须要再进行setsockopt的设置也能够重复绑定了。今后可看出,本来我建立的socket为局部的,但其释放好像并不一样与普通的c变量的释放方式,故在函数下次调用时候,出现地址重复的错误,而手动清除是保险的。该错误在linux和windows平台均有,但好像windows再调用setsockopt后,socket接收有异常,偶尔接收不了UDP报文,而linux下没有此种现象。

 

什么会致使udp丢包呢,我这里列举了以下几点缘由:

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

2.发送的包巨大丢包。虽然send方法会帮你作大包切割成小包发送的事情,但包太大也不行。例如超过30K的一个udp包,不切割直接经过send方法发送也会致使这个包丢失。这种状况须要切割成小包再逐个send。

3.发送的包较大,超过mtu size数倍,几个大的udp包可能会超过接收者的缓冲,致使丢包。这种状况能够设置socket接收缓冲。之前遇到过这种问题,我把接收缓冲设置成64K就解决了。
int nRecvBuf=32*1024;//设置为32K
setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));

4.发送的包频率太快,虽然每一个包的大小都小于mtu size 可是频率太快,例如40多个mut size的包连续发送中间不sleep,也有可能致使丢包。这种状况也有时能够经过设置socket接收缓冲解决,但有时解决不了。

5.发送的广播包或组播包在windws和linux下都接收正常,而arm上接收出现丢包。这个还很差解决,个人解决方法是大包切割成大小为1448的小包发送,每一个包之间sleep 1毫秒,虽然笨,但有效。我这里mtu size为1500字节,减去udp包头8个字节,减去传输层几十个字节,实际数据位1448字节。
除此以外还能够试试设置arm操做系统缓冲:
//设置mtu size 1500最大
ifconfig eth0 mtu 1500
//查看接收缓冲最大和默认大小。
sysctl -A | grep rmem
//设置接收缓冲的最大大小
sysctl -w net.core.rmem_max=1048576
sysctl -w net.core.rmem_default=1048576
sysctl -w net.ipv4.udp_mem=1048576
sysctl -w net.ipv4.udp_rmem_min=1048576

6,局域网内不丢包,公网上丢包。这个问题我也是经过切割小包并sleep发送解决的。若是流量太大,这个办法也不灵了。

总之udp丢包老是会有的,若是出现了用个人方法解决不了,还有这个几个方法: 要么减少流量,要么换tcp协议传输,要么作丢包重传的工做