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

序言

项目涉及基于UDP的socket通信,该部分的基本情况如下:


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


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


  • 其他说明

    • 本地收发。如果不限制发包速率将会非常快

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

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

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

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


  • 测试方式
    • 发送一定时间的数据。
      • 如设置发送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
文中错误和不足部分,欢迎交流指正

你可能感兴趣的:(网络编程)