做io测试(2)

epoll + io_uring IO 测试方案(send/recv 网络IO)

核心区分:

  • epoll :IO 多路复用,仍然依赖 send/recv 系统调用,只是等待就绪事件,适合高并发 TCP 连接压测;
  • io_uring :Linux 新异步IO框架,可以把 send/recv 打包成异步SQE,减少syscall次数,是当前高性能网络IO的主流。 下面给两套可直接编译运行的最小压测代码,单连接吞吐版,再讲多连接压测、指标采集、调优和对比。
    环境前提:Linux 内核 ≥ 5.10(io_uring推荐5.15+),gcc,-O2编译。

一、epoll 版本:epoll + send/recv 吞吐测试

epoll 只是事件驱动,收发仍然用传统 send/recv。非阻塞socket。 逻辑: server:epoll监听fd,accept拿到新conn,加入epoll;当conn可读,循环recv。 client:连接成功,循环send,非阻塞。

epoll_server.c

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <time.h>

#define PORT 8888
#define BUF_SIZE (1<<20)
#define MAX_EVENTS 16

static int set_nonblock(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    fcntl(fd, F_SETFL, flags | O_NONBLOCK);
    return 0;
}

int main() {
    int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    set_nonblock(listen_fd);

    struct sockaddr_in addr = {0};
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port = htons(PORT);
    bind(listen_fd, (struct sockaddr*)&addr, sizeof(addr));
    listen(listen_fd, 128);

    int epoll_fd = epoll_create1(0);
    struct epoll_event ev;
    ev.data.fd = listen_fd;
    ev.events = EPOLLIN;
    epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &ev);

    char *buf = malloc(BUF_SIZE);
    struct epoll_event events[MAX_EVENTS];
    long long total_recv = 0;
    int conn_fd = -1;
    clock_t start = 0;

    while (1) {
        int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
        for (int i = 0; i < nfds; i++) {
            int fd = events[i].data.fd;
            if (fd == listen_fd) {
                struct sockaddr_in client_addr;
                socklen_t client_len = sizeof(client_addr);
                int cfd = accept4(listen_fd, (struct sockaddr*)&client_addr, &client_len, SOCK_NONBLOCK);
                if (cfd < 0) continue;
                printf("client connected %d\n", cfd);
                ev.data.fd = cfd;
                ev.events = EPOLLIN | EPOLLET; // EPOLLET 边缘触发
                epoll_ctl(epoll_fd, EPOLL_CTL_ADD, cfd, &ev);
                conn_fd = cfd;
                start = clock();
            } else if (events[i].events & EPOLLIN) {
                int n;
                while ((n = recv(fd, buf, BUF_SIZE, 0)) > 0) {
                    total_recv += n;
                }
                if (n == 0 || (n < 0 && errno != EAGAIN)) {
                    // 对端关闭
                    clock_t end = clock();
                    double sec = (double)(end - start)/CLOCKS_PER_SEC;
                    printf("epoll server recv total: %lld bytes, bw: %.2f MB/s\n",
                            total_recv, total_recv / sec / (1<<20));
                    epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, NULL);
                    close(fd);
                    free(buf);
                    close(epoll_fd);
                    close(listen_fd);
                    return 0;
                }
            }
        }
    }
}

epoll_client.c

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <time.h>

#define PORT 8888
#define BUF_SIZE (1<<20)
#define SEND_TOTAL (10LL << 30) // 10GB
#define MAX_EVENTS 8

static int set_nonblock(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    fcntl(fd, F_SETFL, flags | O_NONBLOCK);
    return 0;
}

int main() {
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    set_nonblock(sock);
    struct sockaddr_in serv_addr = {0};
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);
    inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr);
    connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

    int epoll_fd = epoll_create1(0);
    struct epoll_event ev;
    ev.data.fd = sock;
    ev.events = EPOLLOUT | EPOLLET;
    epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sock, &ev);

    char *buf = malloc(BUF_SIZE);
    memset(buf, 'A', BUF_SIZE);
    long long remain = SEND_TOTAL;
    clock_t start = clock();
    struct epoll_event events[MAX_EVENTS];

    while (remain > 0) {
        int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
        for (int i = 0; i < nfds; i++) {
            if (events[i].events & EPOLLOUT) {
                while (remain > 0) {
                    int send_len = remain > BUF_SIZE ? BUF_SIZE : remain;
                    int n = send(sock, buf, send_len, MSG_NOSIGNAL);
                    if (n < 0) {
                        if (errno == EAGAIN) break;
                        perror("send");
                        exit(1);
                    }
                    remain -= n;
                }
                if (remain <= 0) {
                    clock_t end = clock();
                    double sec = (double)(end - start)/CLOCKS_PER_SEC;
                    printf("epoll client send total: %lld bytes, bw: %.2f MB/s\n",
                            SEND_TOTAL, SEND_TOTAL / sec / (1<<20));
                    close(sock);
                    free(buf);
                    close(epoll_fd);
                    return 0;
                }
            }
        }
    }
}

编译&运行:

复制代码
gcc epoll_server.c -o epoll_server -O2
gcc epoll_client.c -o epoll_client -O2
# 窗口1
./epoll_server
# 窗口2
./epoll_client

epoll特点: ✅ 适合大量连接,只等事件就绪; ❌ 就绪后还是要循环调用 send/recv,每个收发都触发syscall,小包场景syscall开销高。

参考链接 :0voice · GitHub

相关推荐
ONLYOFFICE2 分钟前
ONLYOFFICE 10.0:深入了解最新的 40 项安全修复
网络·安全
上海云盾-高防顾问8 分钟前
边缘验签是什么
运维·服务器·网络
CH_Qing36 分钟前
lspci 常见debug 命令
网络
天南散修1 小时前
《802.11-2024》第08讲 · 聚合与块确认:把「一次一帧」变成「一次一批」
linux·网络·wifi·802.11
企业解惑小能手-域智盾系统1 小时前
终端安全管理软件怎么选?终端安全管理不是“装个客户端”:从底层逻辑看企业怎么选
服务器·网络·安全·域智盾软件
济南凡维2 小时前
智能相机 VS 工业相机:机器视觉选型一次讲透
网络·数码相机·视觉检测
kxx6662 小时前
网络原理(初识)
网络
天南散修2 小时前
《802.11-2024》第06讲 · 介质接入:MAC 架构与 DCF(CSMA/CA)
linux·网络·wifi·802.11
QYR-分析3 小时前
2.80 亿增至 4.12 亿美元,CAGR5.6%:肌电诱发电位仪稳健扩容,神经电生理检测设备加速走向便携与数字化
大数据·网络·人工智能
伯恩bourne3 小时前
Reactive Streams 出现的原因
服务器·网络