做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

相关推荐
一条破秋裤1 小时前
TCP 套接字:API 流程、地址结构与网络字节序
网络·tcp/ip·php
smaller_maple2 小时前
中兴 E2623 移动定制版路由器 破解 & 关闭远程服务 详细教程
网络·智能路由器
fhq_fn_jgd2 小时前
ARM I2C
网络
phltxy2 小时前
C 语言文件操作:让数据从内存走向持久化
c语言·网络·数据库
一条破秋裤3 小时前
TCP 并发服务器:连接分析、多线程与多进程
服务器·网络·tcp/ip
北京恒星科通刘军3 小时前
灾害监测预警系统中,应急疏散广播的“应急叫应”实际送达率与有效性研究
网络·数据库·人工智能
TDengine (老段)3 小时前
集群从“能用“到“高可用“:副本、选主与故障切换到底怎么配
大数据·网络·数据库·物联网·oracle·时序数据库·tdengine
rest10244 小时前
做io测试
linux·服务器·网络
一条破秋裤4 小时前
Linux 网络编程基础:网络分类、分层模型与数据封装
linux·运维·网络