Linux网络编程-UDP接收数据丢包解决方案
序言
项目涉及基于UDP的socket通信,该部分的基本情况如下:
发端程序:主函数开启4个发包线程,每个线程发送一定量的数据,通过限制发包速率限制发包流量。
收端程序:主函数对应开启4个收包线程,每个线程收取对应端口的数据,收到数据包即时封装处理。
其他说明:
本地收发。如果不限制发包速率将会非常快
基于UDP。使用recvfrom()函数收包
recvfrom()接收后立即将包加入队列并封装处理,即一次处理单个包
发包流量最大160Mbps = 20MBps
多种流量:160Mbps,80Mbps,40Mbps等进行测试
测试方式:
发送一定时间的数据。
- 如设置发送1s,发送数据量160Mbits,即160Mbps流量
收到数据包后进行包计数。
- 统计收包率/丢包率
记录收端处理数据耗时,对比发端耗时。
- 即测试收端处理速度,不过应该注意此处的耗时只是处理所收到数据包的耗时,并不一定是所有数据量的耗时,如果存在丢包的话。
但是现在测试出现的问题如下:
收包率低/丢包率高。
- 丢包率最高时可达67%
- 速率逐渐降低,收包率有上升趋势
- 到一定速率,速率继续降低,收包数也不继续上升
- 丢包率最高时可达67%
最大容量下耗时超过了发包耗时
- 耗时最大可达发送时间的2倍以上
- 速率逐渐降低,耗时有减小趋势
- 到一定速率,速率继续降低,由于只能处理一定收包数,耗时基本稳定
- 耗时最大可达发送时间的2倍以上
一、问题分析
收包率低/丢包率高的原因分析
(1) 缓存太小,不能及时接收数据。
- 连续多个UDP包超过了UDP接收缓冲区大小
- 如:UDP包过大
- 如:UDP发包速率过快,突发大数据流量超过了缓冲区上限
(2)recvfrom()接收到数据之后处理速度太慢
如果数据接收和处理/渲染是连续进行的,那么可能由于数据处理过慢,两次recvfrom调用的时间间隔里发过来的包丢失。
二、问题验证和解决措施
(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,依次为最小值、默认值、最大值
注:通过cat /proc/sys/net/ipv4/tcp_wmem命令和cat /proc/sys/net/core/wmem_max命令得出的TCP发送缓冲区最大值不一致,我觉得应该以cat /proc/sys/net/ipv4/tcp_wmem为准,即最大值为4M。
更多推荐
所有评论(0)