项目背景
在局域范围内实现极速下载,服务端需要将升级程序文件和相关配置分发到百台量级设备。在传输层协议中,TCP是高可靠通信协议,而UDP协议是高性能协议,为了达到极致性能,本项目使用自定义UDP协议,在高效的基础上通过增加算法和自定义帧来确保数据安全。
网卡开启RSS多队列,TX队列
为了让发送的包充分使用硬件,发送的包应该均匀的分散在不同Tx队列,提高发送的可靠性,需要开启网卡TX队列。同时应该关闭防火墙,减少对网络流的控制。
根据经验直接将通道塞满
由于服务端可以严格控制下载,所以服务端根据当前网络带宽。如果是千兆网卡(1000MBit/s 差不多1.25亿字节每秒),正常ip的MTU=1500字节(包含IP头默认20字节,UDP头8字节),为了提高极致效率,在切片时,每包发送量不要超过1472字节,避免分包导致效率变低。理论上,每秒可以发送8万多包,考虑到路由器的性能等因素,起初这里不必要一下子将带宽占满,每秒发送1万包,之后根据根据策略逐步提高发送包数量。如果出现丢包现象,此时应该就接近网路通道极限,发送速度应该进一步降低。
开启多个Socket多线程发送
单个socket在linux可以调用sendmmsg进行批量发送,然而单个soket还是有可能将包都放入同一个网卡Tx序列,需要创建多个Socket分布在不同线程,并且各个线程应该设置对CPU的亲和性,同时设置CPU和Tx队列的绑定,让发送效率进一步提高。
不需要对下载文件进行副本拷贝
在下载时,服务端会开启多个线程给不同的设备下发升级。由于担心各个线程同时访问同一地址文件发生访问冲突,所以准备给每个线程都拷贝一份副本。结果测试完全没有必要,因为下载时,各个线程都是自读不写,并不会引发访问冲突。
接收数据处理时,需要将接收缓存数据进行映射,并异步处理数据提高数据处理能力
不同于TCP协议算法高并发时主要是内核为了维持链接导致开销很大,UDP通信的主要局限就在于收取数据处理速度的局限。不要尝试使用recvfrom函数来将内核态数据拷贝到指定地址,而是要使用mmap来共享一块内存。
而使用mmap来做的话

数据包到达和取数据完全分开,但是注意如果取数太慢,导致共享区域出现问题还是会丢包。但是应用层可以通过getsockopt(PACKET_STATISTICS)来获取丢包情况。另外尽量使用批处理
···
poll(fd, &pfd, 1, -1);
// 批量遍历整个环形缓冲区,一次性处理所有就绪报文
while (1) {
struct tpacket3_hdr *hdr = get_current_frame(ring, idx);
uint32_t status = READ_ONCE(hdr->tp_status);
if (status != TP_STATUS_USER)
break; // 没有更多待处理包,退出批量循环
process_one_packet(hdr); // 处理单条报文
WRITE_ONCE(hdr->tp_status, TP_STATUS_KERNEL); // 归还帧
idx = next_frame_idx(ring, idx);
}···处理单条报文时可以采用异步方式,将报文进行异步解析并处理返回,C++20可以考虑使用协程来完成来进一步减小数据处理的压力。
总结
通过以上方式可以实现在局域范围内将下载性能拉满,但是具体下载速度也和局域网的硬件有很大关系,具体量化指标这里就不细说了,但是总体而言要比基于TCP协议的下载速度快很多。