You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何优化用于数据包转发的UDP套接字以提升每秒数据包处理量?

UDP套接字数据包转发性能优化问题

我通过UDP套接字实现数据包转发:流量发生器向UDP套接字发送数据包,套接字交换源目地址后回发给发生器以记录网络指标。环境采用XL710 40GbE网卡,基于内部虚拟网络。

当前实现的最简程序仅能每秒回发约350000个数据包,而发生器每秒发送约5000000个数据包,存在较大优化空间。以下是UDP回包的代码:

#define PORT 12345
#define IP_ADDR "192.168.0.32"

int main(int argc, char *argv[]){
    int socket_fd;
    struct sockaddr_in address, send_address;
    char packet_buffert[2000] = {0};

    if ((socket_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        exit(EXIT_FAILURE);
    }

    address.sin_family = AF_INET;
    address.sin_port = htons(PORT);
    address.sin_addr.s_addr = inet_addr(IP_ADDR);

    if(bind(socket_fd, (struct sockaddr*)&address, sizeof(address))<0){
        return -1;
    }

    socklen_t s = sizeof(send_address);

    while(1){

        if ((n = recvfrom (socket_fd, packet_buffert, sizeof packet_buffert, 0,(struct sockaddr*)&traffic_gen_addr, (socklen_t*)&s)) < 0)
        {
             printf(" %s\n ", strerror(errno));
             return EXIT_FAILURE;
        } 

        if (sendto (socket_fd, packet_buffert, n, 0, (struct sockaddr*)&traffic_gen_addr, (socklen_t)sizeof(traffic_gen_addr)) < 0)
        {
            printf("%s \n", strerror(errno));
            return EXIT_FAILURE;
        }
    } 
} 

我考虑过数据包批处理但尚未成功实现,请问需对UDP套接字做哪些修改以提升吞吐量、获取更高的每秒数据包处理量?该实现遗漏了哪些关键的网络性能优化概念?


优化方案与遗漏的性能概念

一、针对UDP套接字的具体修改措施

1. 实现批量收发

放弃单包recvfrom+sendto的模式,改用批量系统调用减少上下文切换开销:

  • 批量接收:用recvmmsg一次读取多个UDP包,可通过MSG_WAITFORONE或设置等待超时,确保单次调用能获取足够多的数据包。
  • 批量发送:用sendmmsg批量发送数据包,给每个包添加MSG_MORE标志,让内核攒够一批再发送,降低网卡中断频率。
    示例框架:
#define BATCH_SIZE 64
struct mmsghdr msgs[BATCH_SIZE];
struct iovec iovs[BATCH_SIZE];
struct sockaddr_in addrs[BATCH_SIZE];
char buffers[BATCH_SIZE][2000];

// 初始化iovs与msgs关联
for (int i = 0; i < BATCH_SIZE; i++) {
    iovs[i].iov_base = buffers[i];
    iovs[i].iov_len = sizeof(buffers[i]);
    msgs[i].msg_hdr.msg_iov = &iovs[i];
    msgs[i].msg_hdr.msg_iovlen = 1;
    msgs[i].msg_hdr.msg_name = &addrs[i];
    msgs[i].msg_hdr.msg_namelen = sizeof(addrs[i]);
}

while(1) {
    int recv_count = recvmmsg(socket_fd, msgs, BATCH_SIZE, MSG_WAITFORONE, NULL);
    if (recv_count <= 0) continue;

    // 给每个待发送的包标记MSG_MORE
    for (int i = 0; i < recv_count; i++) {
        msgs[i].msg_hdr.msg_flags = MSG_MORE;
    }
    sendmmsg(socket_fd, msgs, recv_count, 0);
}

2. 增大套接字缓冲区

通过setsockopt调整UDP收发缓冲区大小,避免高流量下因缓冲区溢出丢包:

int buf_size = 16 * 1024 * 1024; // 16MB
setsockopt(socket_fd, SOL_SOCKET, SO_RCVBUF, &buf_size, sizeof(buf_size));
setsockopt(socket_fd, SOL_SOCKET, SO_SNDBUF, &buf_size, sizeof(buf_size));

同时需要修改系统参数,允许内核使用大缓冲区:

sysctl -w net.core.rmem_max=16777216
sysctl -w net.core.wmem_max=16777216

3. 绑定CPU与网卡队列

利用XL710的多队列特性,将套接字绑定到特定CPU核,减少跨核调度开销:

  • 用ethtool配置网卡多队列数,比如ethtool -L eth0 combined 8
  • 将进程绑定到对应CPU核,同时通过SO_INCOMING_CPU让套接字的数据包固定到指定队列:
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(2, &cpuset); // 绑定到CPU核心2
sched_setaffinity(getpid(), sizeof(cpu_set_t), &cpuset);

int cpu = 2;
setsockopt(socket_fd, SOL_SOCKET, SO_INCOMING_CPU, &cpu, sizeof(cpu));

4. 启用零拷贝

使用MSG_ZEROCOPY标志(Linux 4.14+支持),避免用户态与内核态之间的数据拷贝:

sendto(socket_fd, packet_buffert, n, MSG_ZEROCOPY, 
      (struct sockaddr*)&traffic_gen_addr, sizeof(traffic_gen_addr));

5. 消除冗余开销

  • 去掉循环内的printf调用,避免IO阻塞;
  • 缓存流量发生器的地址,无需每次recvfrom都重新解析;
  • 禁用不必要的套接字选项,确保UDP默认的无延迟特性生效。

二、遗漏的关键性能优化概念

  1. 系统调用开销:单包模式下每次循环触发两次系统调用,上下文切换的开销在高PPS场景下占比极高,批量收发核心就是减少系统调用次数。
  2. 内核缓冲区瓶颈:默认UDP缓冲区过小,高流量下会出现丢包,直接限制了处理能力。
  3. 中断与CPU负载均衡:单网卡队列会导致单个CPU核被中断占满,多队列+CPU亲和性才能分散负载,避免CPU成为瓶颈。
  4. 数据拷贝开销:用户态与内核态之间的两次拷贝(收包时内核到用户、发包时用户到内核)是高PPS场景的主要性能损耗点,零拷贝可消除这部分开销。
  5. 批量处理的内核优化:内核对批量收发有专门的合并逻辑,能减少网卡的发送/接收次数,降低中断频率,提升整体吞吐量。

内容的提问来源于stack exchange,提问作者Agent smith 2.0

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 23:50:27