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

如何提升C语言UDP套接字的上传吞吐量?

UDP上传吞吐量优化方案(从65%提升至80%-85%)

针对你的UDP上传测试代码,以下是从代码逻辑、系统调优、线程模型等维度的优化建议,直接解决吞吐量瓶颈问题:

一、修复核心逻辑错误(最关键)

当前代码误用writev实现批量发包:UDP套接字调用writev时,会将所有iov数据拼接成单个UDP包发送,而非批量发送多个独立数据包。这会导致数据包远超MTU引发IP分片,大幅降低传输效率,完全违背了批量发包的设计初衷。

解决方案:改用sendmmsg系统调用

sendmmsg是Linux专门为批量发送UDP/TCP数据包设计的接口,一次系统调用可发送多个独立数据包,能显著减少系统调用开销,提升发包效率。

替换核心发送逻辑示例:

// 预批量发包的消息结构
struct mmsghdr msgs[packets_to_send];
struct iovec iov_single;
iov_single.iov_base = buffer;
iov_single.iov_len = params->mtu;

// 初始化批量消息
for (int i = 0; i < packets_to_send; ++i) {
    memset(&msgs[i], 0, sizeof(struct mmsghdr));
    msgs[i].msg_hdr.msg_iov = &iov_single;
    msgs[i].msg_hdr.msg_iovlen = 1;
    // 已connect套接字,无需设置msg_name
}

// 发送循环中替换writev为sendmmsg
int ret = sendmmsg(client_socket, msgs, packets_to_send, 0);
if (ret < 0) {
    if (errno == EAGAIN || errno == EWOULDBLOCK) {
        // 后续结合epoll优化等待逻辑
        continue;
    } else {
        log_error("sendmmsg error: %s", strerror(errno));
        break;
    }
}

二、优化非阻塞套接字的等待逻辑

当前遇到EAGAIN时直接continue,会导致CPU空转(忙等),浪费大量CPU资源,反而限制吞吐量。

解决方案:用epoll监听可写事件

采用epoll边缘触发模式监听套接字的可写状态,避免忙等,降低CPU占用的同时保证及时发包:

// 初始化epoll(在套接字设置非阻塞后)
int epoll_fd = epoll_create1(0);
if (epoll_fd == -1) {
    log_error("epoll create failed");
    close(client_socket);
    exit(EXIT_FAILURE);
}

struct epoll_event ev;
ev.events = EPOLLOUT | EPOLLET; // 边缘触发
ev.data.fd = client_socket;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_socket, &ev) == -1) {
    log_error("epoll add socket failed");
    close(epoll_fd);
    close(client_socket);
    exit(EXIT_FAILURE);
}

// 发送循环修改为:
struct epoll_event events[1];
while (time_compare(&current_time, &delta_time) != -1) {
    int ready = epoll_wait(epoll_fd, events, 1, 10); // 10ms超时
    if (ready == -1) {
        if (errno == EINTR) continue;
        log_error("epoll wait error: %s", strerror(errno));
        break;
    }
    if (ready == 0) {
        get_time(&current_time);
        continue;
    }
    // 套接字可写,持续批量发包直到缓冲区满
    while (1) {
        int ret = sendmmsg(client_socket, msgs, packets_to_send, 0);
        if (ret < 0) {
            if (errno == EAGAIN || errno == EWOULDBLOCK) break;
            log_error("sendmmsg error: %s", strerror(errno));
            goto cleanup;
        }
    }
    get_time(&current_time);
}

cleanup:
close(epoll_fd);
close(client_socket);

三、线程模型优化

1. 线程绑定CPU核心

将每个发送线程绑定到固定CPU核心,减少上下文切换开销,提升缓存局部性:

// 在udp_client_thread开头添加
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
// 假设ThreadParams新增thread_index字段,标记线程序号
int core_id = params->thread_index % sysconf(_SC_NPROCESSORS_ONLN);
CPU_SET(core_id, &cpuset);
if (pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset) != 0) {
    log_warn("set thread affinity failed");
}

同时修改线程创建逻辑,为每个线程分配独立的ThreadParams并传入序号:

// 在udp_user_space_test的线程创建循环中
for (int i = 0; i < num_of_threads; ++i) {
    ThreadParams *thread_params = malloc(sizeof(ThreadParams));
    memcpy(thread_params, params, sizeof(ThreadParams));
    thread_params->thread_index = i;
    if (pthread_create(&threads[i], NULL, udp_client_thread, thread_params) != 0) {
        log_error("create thread %d failed", i);
        free(thread_params);
        return -1;
    }
}
// 线程结束时释放参数
// 在udp_client_thread末尾添加free(params);

2. 合理设置线程数量

线程数量并非越多越好,建议默认设置为CPU核心数(含超线程):

// 替换num_of_threads的默认值
num_of_threads = sysconf(_SC_NPROCESSORS_ONLN);

四、内存与缓存优化

1. 避免栈上大缓冲区

如果MTU较大(如9000字节),栈上分配buffer[params->mtu]可能引发栈溢出,改用堆分配并保证内存对齐:

// 替换栈上buffer为堆分配
char *buffer = posix_memalign(64, params->mtu);
if (!buffer) {
    log_error("alloc buffer failed");
    exit(EXIT_FAILURE);
}
memset(buffer, 'a', params->mtu);
// 线程结束时free(buffer);

内存对齐到64字节(CPU缓存行大小)可提升缓存命中率。

2. 减少循环内的系统调用

当前每次循环调用get_time(&current_time),可减少调用频率,比如每发送10次批量包后再检查时间,降低系统调用开销。

五、系统参数调优

通过调整内核参数提升UDP发送能力:

# 增大UDP发送缓冲区上限
sysctl -w net.core.wmem_max=4194304
sysctl -w net.core.wmem_default=262144
# 调整UDP内存阈值(单位:页,每页4KB)
sysctl -w net.ipv4.udp_mem="8388608 12582912 16777216"
# 增大网卡接收队列(避免丢包)
sysctl -w net.core.netdev_max_backlog=10000
# 开启UDP分片卸载(网卡硬件处理分片)
ethtool -K eth0 uf on
# 调整网卡发送队列数(与CPU核心数匹配)
ethtool -L eth0 tx $(nproc)

六、其他细节优化

  • 关闭调试日志:线程创建时的log_info会增加IO开销,测试阶段建议关闭或仅输出关键日志。
  • 验证MTU有效性:确保get_mtu获取的是实际生效的MTU(如开启Jumbo帧后需确认网卡支持)。

内容的提问来源于stack exchange,提问作者Simao

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 20:37:31