Linux网络编程-UDP接收数据丢包解决方案

序言

项目涉及基于UDP的socket通讯,该部分的基本状况以下:html


  • 发端程序:主函数开启4个发包线程,每一个线程发送必定量的数据,经过限制发包速率限制发包流量。


  • 收端程序:主函数对应开启4个收包线程,每一个线程收取对应端口的数据,收到数据包即时封装处理。


  • 其余说明web

    • 本地收发。若是不限制发包速率将会很是快vim

    • 基于UDP。使用recvfrom()函数收包缓存

    • recvfrom()接收后当即将包加入队列并封装处理,即一次处理单个包socket

    • 发包流量最大160Mbps = 20MBpstcp

    • 多种流量:160Mbps,80Mbps,40Mbps等进行测试svg


  • 测试方式
    • 发送必定时间的数据。
      • 如设置发送1s,发送数据量160Mbits,即160Mbps流量
    • 收到数据包后进行包计数。
      • 统计收包率/丢包率
    • 记录收端处理数据耗时,对比发端耗时。
      • 即测试收端处理速度,不过应该注意此处的耗时只是处理所收到数据包的耗时,并不必定是全部数据量的耗时,若是存在丢包的话。


  • 可是如今测试出现的问题以下函数

    • 收包率低/丢包率高。测试

      • 丢包率最高时可达67%
        • 速率逐渐下降,收包率有上升趋势
        • 到必定速率,速率继续下降,收包数也不继续上升
    • 最大容量下耗时超过了发包耗时大数据

      • 耗时最大可达发送时间的2倍以上
        • 速率逐渐下降,耗时有减少趋势
        • 到必定速率,速率继续下降,因为只能处理必定收包数,耗时基本稳定


1. 问题分析


  • 收包率低/丢包率高的缘由分析

    • (1) 缓存过小,不能及时接收数据。

      • 连续多个UDP包超过了UDP接收缓冲区大小
        • 如:UDP包过大
        • 如:UDP发包速率过快,突发大数据流量超过了缓冲区上限
    • (2)recvfrom()接收到数据以后处理速度太慢

      • 若是数据接收和处理/渲染是连续进行的,那么可能因为数据处理过慢,两次recvfrom调用的时间间隔里发过来的包丢失


2. 问题验证和解决措施


  • (1) 缓存过小不能及时接收数据

    • [1] 分析:

      • UDP无需真正的发送缓冲区:

        • UDP是不可靠链接,没必要保存应用进程的数据拷贝,所以无需真正的发送缓冲区(TCP须要)。应用进程的数据在沿协议栈往下传递,以某种形式拷贝到内核缓冲区,然而数据链路层在送出数据以后将丢弃该拷贝
      • UDP是没有流量控制的:

        • 较快的发送端能够很容易淹没较慢的接收端,致使接收端的UDP丢弃数据报。
        • UDP套接字的缓冲区是以一个个报文为单位进行排队的,调用一次recvfrom表示提取一个报文,和TCP基于字节流的方式是不一样的
      • 所以,若是socket接收缓存设置太小,就会由于UDP包过大或者发包速率过快而丢包


    • [2] 解决方法:从新设置UDP接收缓冲区大小

      • UDP接收缓冲区默认值:cat /proc/sys/net/core/rmem_default
        • 本系统:212992 = 208K
      • UDP接收缓冲区最大值:cat /proc/sys/net/core/rmem_max,UDP最大可设置值的一半
        • 本系统:212992 = 208K,即最大值425984 = 416K
      • UDP接收缓冲区最小值:sysctl -a | grep rmem

        • 本系统:net.ipv4.udp_rmem_min = 4096 = 2K,由内核的宏决定
      • UDP发送缓冲区默认值:cat /proc/sys/net/core/wmem_default

        • 本系统:212992 = 208K
      • UDP发送缓冲区最大值:cat /proc/sys/net/core/wmem_max
        • 本系统:212992 = 208K,即最大值425984 = 416K
      • UDP发送缓冲区最小值:sysctl -a | grep wmem
        • 本系统:net.ipv4.udp_wmem_min = 4096 = 2K,由内核的宏决定

    • [3] 解决步骤:

      • 调整UDP缓冲区大小:使用函数setsockopt()函数修改接收缓冲区大小

        • 缓冲区改大能够处理突发的大流量数据,不至于数据(视音频等)变化、流量忽然增大的时候缓冲区溢出
      • 从新设置系统缓冲区最大值,再调整UDP缓冲区大小:

        • 打开配置文件:sudo vim /etc/sysctl.conf

        • 在文件末尾添加:net.core.rmem_max = 6291456

          • 将接收缓冲最大值设置为12582912 = 12M
        • 执行配置:sysctl -p

        • 从新查看最大值:cat /proc/sys/net/core/rmem_max

        • 发现系统接收缓冲最大值已改变,此时能够经过setsockopt函数设置更大接收缓存。发送缓冲最大值也能够经过相似方式修改:net.core.wmem_max = 6291456,sysctl -p

/* setsockopt()函数修改:*/

//函数原型
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

sockfd: 标识一个套接字的描述字
level: 选项定义的层次:支持SOL_SOCKET, IPPROTO_TCP, IPPROTO_IP,和IPPROTO_IPV6
optname:需设置得选项 SO_RCVBUF(接收缓冲区),SO_SNDBUF(发送缓冲区)
optval:指针,指向存放选项待设置的新值的缓冲区
optlen:optval的大小


//示例
int recv_size = 2 * 1024 * 1024;    //设置为4M
setsockopt(s,SOL_SOCKET, SO_RCVBUF, (const char *)&recv_size,sizeof(recv_size));


  • (2)recvfrom()接收到数据以后处理速度太慢

    • [1] 分析:

      • UDP无需真正的缓冲区,没必要保存应用进程的数据拷贝

        • 这就对数据处理的实时性提出了很高要求
      • recvfrom()接收速率并非系统受限因素

        • recvfrom()处理速度是由CPU速度决定的(看到过这种说法,待验证),若是线程正常调用,几百Mbps的速度该函数确定能跟上
      • 数据处理是速度受限因素之一

        • 程序在recvfrom接收到数据以后还进行了封装处理,即数据接收和数据处理是绑定的,处理速度跟不上接收速度(渲染速度跟不上socket接收速度)
      • 线程挂起再唤醒耗时受限速度之二

        • 尽管由CPU决定的recvfrom接收速度足够快,但若是接收数据线程从挂起再到唤醒接收数据,这个过程耗时可达数百毫秒

    • [2] 解决方法:

      • 数据处理速度受限:数据接收和数据处理分离
        • 接收数据单独存储,而后直接返回,保证recvfrom不丢数据或超时
        • 数据处理由其余函数或线程独立完成
      • 线程响应速度受限:保持线程一直在运行或监听状态
        • 由于数据接收和处理函数都在同一线程,可将数据接收和处理从逻辑上分开
        • 若是不在统一线程,因为数据到了再起线程速度过慢,可考虑使用线程池技术

    • [3] 解决步骤:

      • recvfrom和数据处理函数再也不顺序执行一次只处理一个包。
      • 逻辑上分离数据接收和处理:
        • recvfrom负责接收数据并存储,设置单独计数1
        • 数据处理函数负责从从存储中拿数据并处理,设置单独计数2
      • 抓包线程一直处于运行状态:while
        • 数据处理能够等待唤醒
        • 但数据接收须要一直进行,不然不免丢包


补充:TCP缓冲区查看和修改命令

  • TCP接收缓冲区大小

    • TCP接收缓冲区默认值:cat /proc/sys/net/ipv4/tcp_rmem
      • 本系统:87380,约85K
    • TCP接收缓冲区最大值:cat /proc/sys/net/core/rmem_max ,TCP最大可设置值的一半(与UDP同)
      • 本系统:212992 = 208K,即最大值425984 = 416K。
      • 已被我修改成:3145728 = 3M,即最大值6291456 = 6M
    • TCP接收缓冲区最小值:sysctl -a | grep rmem
      • 本系统:net.ipv4.udp_rmem_min = 4096 = 2K
    • 通用命令:cat /proc/sys/net/ipv4/tcp_rmem
      • 4096 87380 6291456,依次为最小值、默认值、最大值
  • TCP接收缓冲区大小

    • TCP发送缓冲区默认值:cat /proc/sys/net/ipv4/tcp_wmem
      • 本系统:16384 = 16K
    • TCP发送缓冲区最大值:cat /proc/sys/net/core/wmem_max,TCP最大可设置值的一半
      • 本系统:212992 = 208K,即最大值425984 = 416K
    • TCP发送缓冲区最小值:sysctl -a | grep wmem
      • 本系统:net.ipv4.udp_wmem_min = 4096 = 2K
    • 通用命令:cat /proc/sys/net/ipv4/tcp_wmem
      • 4096 16384 4194304,依次为最小值、默认值、最大值

    注:经过cat /proc/sys/net/ipv4/tcp_wmem命令和cat /proc/sys/net/core/wmem_max命令得出的TCP发送缓冲区最大值不一致,我以为应该以cat /proc/sys/net/ipv4/tcp_wmem为准,即最大值为4M。



Acknowledgements:
http://blog.csdn.net/getnextwindow/article/details/24328117?utm_source=tuicool&utm_medium=referral
http://blog.csdn.net/ljh0302/article/details/49738191
http://blog.csdn.net/herecles/article/details/8146017
http://blog.sina.com.cn/s/blog_858820890100sfpx.html
http://bbs.csdn.net/topics/390958727

2017.07.29 文中错误和不足部分,欢迎交流指正