io_uring的使用示例及其解释

news2025/1/16 9:17:06

io_uring的使用示例及其解释

  • 1 io_uring机制
    • 1.1 io_uring机制
    • 1.2 io_uring系统调用接口功能介绍
      • 1.2.1 io_uring_setup():
      • 1.2.2 io_uring_enter():
      • 1.2.3 io_uring_register():
  • 2 liburing
    • 2.1 liburing简介
    • 2.2 liburing编译
      • 2.2.1 liburing的代码
      • 2.2.2 编译
      • 2.2.3 使用方法
  • 3 io_uring测试例
    • 3.1 io_uring server测试程序
    • 3.2 编译测试程序
      • 3.3 验证方法
    • 3.4 代码解释
      • 3.4.1 setup_listening_socket
      • 3.4.2 submit_accept
      • 3.4.3 handle_connection
      • 3.4.4 main
    • 3.5 io_uring client测试代码

1 io_uring机制

1.1 io_uring机制

io_uring 是 Linux 内核提供的一种高性能的异步 I/O 操作机制,用于提高 I/O 操作的效率和吞吐量。它在 Linux 内核版本 5.1 中首次引入。

以下是 io_uring 机制的一些关键特点和优势:

  • 零拷贝:io_uring 使用了零拷贝技术,将数据从用户空间传输到内核空间,然后再传输到目标设备,避免了数据的多次复制,提高了性能。
  • 内存映射:io_uring 支持内存映射技术,允许用户空间程序直接访问内核空间中的数据,减少了数据的拷贝,提高了效率。
  • 批量提交:io_uring 允许用户在单个系统调用中提交多个 I/O 操作,减少了系统调用的开销,提高了系统的吞吐量。
  • 高性能:相比传统的异步 I/O 模型和线程池模型,io_uring 在大量 I/O 操作的场景下具有更好的性能表现,可以显著减少 CPU 的利用率和系统的开销。
  • 灵活性:io_uring 提供了丰富的系统调用接口,支持各种类型的 I/O 操作,包括文件 I/O、网络
    I/O、定时器等,可以满足不同场景下的需求。
  • 可扩展性:io_uring 可以轻松地与现有的异步编程模型和库(如 libuv、Boost.Asio
    等)集成,提供更加灵活和高效的异步编程解决方案。

io_uring 机制的实现原理涉及到了 Linux 内核的多个子系统,包括块设备、网络设备、文件系统等。它通过使用 ring buffer、completion queue、submission queue 等数据结构,实现了高效的异步 I/O 操作管理和调度机制。同时,io_uring 还提供了3个系统调用接口(io_uring_setup()、io_uring_enter()、io_uring_register()),用于配置和控制 io_uring 环,并提交和处理异步 I/O 操作。

1.2 io_uring系统调用接口功能介绍

这几个系统调用接口都在io_uring.c文件中。

1.2.1 io_uring_setup():

SYSCALL_DEFINE2(io_uring_setup, u32, entries,
                struct io_uring_params __user *, params)                                                                                                                                                           
{
        return io_uring_setup(entries, params);
}

功能:用于初始化和配置 io_uring 。
应用用途:在使用 io_uring 之前,首先需要调用此接口初始化一个 io_uring 环,并设置其参数。

1.2.2 io_uring_enter():

SYSCALL_DEFINE6(io_uring_enter, unsigned int, fd, u32, to_submit,                                                                                                                                                  
                u32, min_complete, u32, flags, const void __user *, argp,
                size_t, argsz)

功能:用于提交和处理异步 I/O 操作。
应用用途:在向 io_uring 环中提交 I/O 操作后,通过调用此接口触发内核处理这些操作,并获取完成的操作结果。

1.2.3 io_uring_register():

SYSCALL_DEFINE4(io_uring_register, unsigned int, fd, unsigned int, opcode,
                void __user *, arg, unsigned int, nr_args)

功能:用于注册文件描述符、缓冲区、事件文件描述符等资源到 io_uring 环中。
应用用途:在进行 I/O 操作之前,需要将相关的资源注册到 io_uring 环中,以便进行后续的异步 I/O 操作。

2 liburing

2.1 liburing简介

liburing 是io_uring的实现者Jens Axboe为了简化用户使用io_uring所实现的一个用户空间的 C 库,用于简化在 Linux 系统上使用 io_uring 的开发。它提供了一组简洁而强大的 API,使开发者可以更轻松地利用 io_uring 的高性能异步 I/O 功能,而无需深入了解 io_uring 的内部工作原理。以下是 liburing 的主要特点和功能:

  • 简单易用:liburing 提供了一组简洁、清晰的 API,方便开发者快速上手使用 io_uring,无需深入了解复杂的底层实现细节。
  • 高性能:liburing 基于 io_uring 机制实现,能够充分利用 Linux 内核提供的高性能异步 I/O
    能力,提供了比传统的异步 I/O 接口更高的性能和吞吐量。
  • 零拷贝:liburing 支持零拷贝技术,可以直接在用户空间和内核空间之间传递数据,避免了数据的多次拷贝,提高了效率。
  • 灵活性:liburing 提供了丰富的功能和选项,可以满足各种异步 I/O 操作的需求,包括文件 I/O、网络 I/O、定时器等。
  • 高级特性:除了基本的异步 I/O 操作外,liburing 还支持诸如批量提交、注册文件描述符、事件通知等高级特性,帮助开发者更好地利用 io_uring 的全部功能。

使用 liburing 进行开发时,通常的步骤如下:

  • 引入 liburing 头文件,并链接 liburing 库到你的项目中。
  • 调用 io_uring_queue_init() 初始化一个 io_uring 环。
  • 使用 io_uring_register_files()、io_uring_register_eventfd() 等函数注册所需的文件描述符、事件通知等资源到 io_uring 环中。
  • 使用 io_uring_prep_* 系列函数准备异步 I/O 操作。
  • 调用 io_uring_submit() 提交异步 I/O 操作到 io_uring 环中。
  • 调用 io_uring_wait_cqe() 等函数等待异步操作完成,并处理结果。
  • 调用 io_uring_cqe_seen() 函数通知内核已经处理完了这些cqe,可以将其从完成队列中移除。
  • 调用 io_uring_queue_exit() 函数释放了所有与 io_uring 环相关的资源,避免资源泄漏和内存泄漏。

2.2 liburing编译

2.2.1 liburing的代码

liburing的代码链接如下所示,可以直接选择master或者特定tag的带下下载下来使用。
axboe/liburing

2.2.2 编译

通过 configure --cc=xxx --cxx=yyy 指定所需平台的编译工具或者交叉编译工具,然后编译即可。

Building liburing
-----------------

    #
    # Prepare build config (optional).
    #
    #  --cc  specifies the C   compiler.
    #  --cxx specifies the C++ compiler.
    #
    ./configure --cc=gcc --cxx=g++;

    #
    # Build liburing.
    #
    make -j$(nproc);

    #
    # Install liburing (headers, shared/static libs, and manpage).
    #
    sudo make install;

2.2.3 使用方法

在使用liburing来使用io_uring功能的程序时,可以通过指定链接liburing库即可

3 io_uring测试例

3.1 io_uring server测试程序

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <liburing.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/time.h>

#define SERVER_IP "127.0.0.1"
#define QUEUE_DEPTH 256
#define BLOCK_SZ    1024

struct io_data {
    int fd;
    struct iovec iov;
};

static int setup_listening_socket(int port) {
    int sfd, flags;
    struct sockaddr_in addr;

    sfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sfd < 0) {
        perror("socket");
        return -1;
    }

    flags = 1;
    if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &flags, sizeof(flags)) < 0) {
        perror("setsockopt(SO_REUSEADDR)");
        close(sfd);
        return -1;
    }

    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = inet_addr(SERVER_IP);

    if (bind(sfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        perror("bind");
        close(sfd);
        return -1;
    }

    if (listen(sfd, 10) < 0) {
        perror("listen");
        close(sfd);
        return -1;
    }

    return sfd;
}

static void submit_accept(struct io_uring *ring, int fd) {
    struct io_uring_sqe *sqe;
    struct io_data *data;

    data = malloc(sizeof(struct io_data));
    data->fd = fd; // 保存监听套接字描述符

    sqe = io_uring_get_sqe(ring);
    if (!sqe) {
        fprintf(stderr, "Could not get sqe.\n");
        exit(1);
    }
    io_uring_prep_accept(sqe, fd, NULL, NULL, 0);
    io_uring_sqe_set_data(sqe, data);
}

static void handle_connection(struct io_uring *ring, int cfd, int client_num) {
    struct io_uring_sqe *sqe;
    struct io_data *data;
    char *buffer;

    buffer = malloc(BLOCK_SZ);
    data = malloc(sizeof(struct io_data));
    data->fd = cfd; // 保存连接套接字描述符
    
    // 发送“Hello client”消息
    sprintf(buffer, "Hello client %d", client_num);
    send(cfd, buffer, strlen(buffer), 0);

    data->iov.iov_base = buffer;
    data->iov.iov_len = BLOCK_SZ;

    sqe = io_uring_get_sqe(ring);
    if (!sqe) {
        fprintf(stderr, "Could not get sqe.\n");
        exit(1);
    }

    // 接收客户端消息
    io_uring_prep_readv(sqe, cfd, &data->iov, 1, 0);
    io_uring_sqe_set_data(sqe, data);
}

int main(int argc, char *argv[]) {
    int sfd;
    struct io_uring ring;
    struct io_uring_cqe *cqe;
    int client_num = 0;
    struct itimerval timer;

    if (argc < 2) {
        printf("Usage: %s <port>\n", argv[0]);
        return 1;
    }

    sfd = setup_listening_socket(atoi(argv[1]));
    if (sfd < 0)
        return 1;

    if (io_uring_queue_init(QUEUE_DEPTH, &ring, 0) < 0) {
        perror("io_uring_queue_init");
        return 1;
    }

    submit_accept(&ring, sfd);

    while (1) {
        if (io_uring_submit(&ring) < 0) {
            perror("io_uring_submit");
            break;
        }

        if (io_uring_wait_cqe(&ring, &cqe) < 0) {
            perror("io_uring_wait_cqe");
            break;
        }

        struct io_data *data = (struct io_data *)io_uring_cqe_get_data(cqe);
        if (cqe->res < 0) {
            fprintf(stderr, "Async read failed.\n");
            return 1;
        } else {
            if (data->fd == sfd) {
                int cfd = cqe->res;
                handle_connection(&ring, cfd, ++client_num);
                submit_accept(&ring, sfd);
                printf("New connection accepted, and the socket fd is %d\n", cfd);
            } else {
                write(STDOUT_FILENO, data->iov.iov_base, cqe->res);
                sprintf(data->iov.iov_base, "Response from server to client %d: %.*s", client_num, cqe->res, (char *)data->iov.iov_base);
                send(data->fd, data->iov.iov_base, strlen(data->iov.iov_base), 0);
            }
        }

        io_uring_cqe_seen(&ring, cqe);
        free(data->iov.iov_base);
        free(data);
    }

    io_uring_queue_exit(&ring);

    return 0;
}

3.2 编译测试程序

gcc -o io_uring_tcp_server io_uring_tcp_server.c -luring

3.3 验证方法

在这里插入图片描述

3.4 代码解释

3.4.1 setup_listening_socket

static int setup_listening_socket(int port) {
    int sfd, flags;
    struct sockaddr_in addr;

    sfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sfd < 0) {
        perror("socket");
        return -1;
    }

    flags = 1;
    if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &flags, sizeof(flags)) < 0) {
        perror("setsockopt(SO_REUSEADDR)");
        close(sfd);
        return -1;
    }

    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = inet_addr(SERVER_IP);

    if (bind(sfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        perror("bind");
        close(sfd);
        return -1;
    }

    if (listen(sfd, 10) < 0) {
        perror("listen");
        close(sfd);
        return -1;
    }

    return sfd;
}

该函数的用途是为服务器程序创建一个监听套接字,并将其绑定到指定的端口上,以便接受客户端的连接请求。在服务器程序启动时,通常会调用此函数来初始化监听套接字,从而开始监听指定端口上的连接请求。

  • 使用 socket() 函数创建一个套接字,指定协议族为 IPv4(AF_INET)、套接字类型为流式套接字(SOCK_STREAM)。
  • 设置套接字选项 SO_REUSEADDR,使得在服务器重启后能够立即使用同一端口。这样可以避免出现"Address already in use"错误。
  • 初始化一个 sockaddr_in 结构体,并将其填充为服务器的地址信息,包括协议族、端口号和 IP 地址。
  • 调用 bind() 函数将套接字与指定的地址进行绑定,即将地址绑定到套接字上。
  • 调用 listen() 函数将套接字设置为监听状态,使其可以接受客户端的连接请求,并设置连接请求的队列长度为 10。

3.4.2 submit_accept

static void submit_accept(struct io_uring *ring, int fd) {
    struct io_uring_sqe *sqe;
    struct io_data *data;

    data = malloc(sizeof(struct io_data));
    data->fd = fd; // 保存监听套接字描述符

    sqe = io_uring_get_sqe(ring);
    if (!sqe) {
        fprintf(stderr, "Could not get sqe.\n");
        exit(1);
    }
    io_uring_prep_accept(sqe, fd, NULL, NULL, 0);
    io_uring_sqe_set_data(sqe, data);
}

这段代码的含义是准备一个接受连接请求的操作,并将其提交到 io_uring 中等待执行。在异步 I/O 编程中,通常会通过类似的方式准备各种 I/O 操作,并将其提交到 io_uring 中,以实现高效的异步 I/O 处理。
这段代码主要的完成的工作为:

  • 分配一个 io_data 结构体的内存空间,并将监听套接字描述符 fd 存储在该结构体中,以便后续操作使用。

  • 调用 io_uring_get_sqe() 函数获取一个提交队列元素(Submission Queue Entry,SQE),用于描述待提交的 I/O 操作。

  • 检查获取的 SQE 是否有效,如果为 NULL,则打印错误信息并退出程序。

  • 调用 io_uring_prep_accept() 函数准备一个接受连接请求的操作,填充到获取的 SQE 中。此处传入的参数为监听套接字描述符 fd,其他参数为 NULL,表示接受连接请求时不需要指定对端地址信息和长度。

  • 调用 io_uring_sqe_set_data() 函数将之前分配的 io_data 结构体指针与 SQE关联,以便在完成操作时能够获取到正确的套接字描述符。

3.4.3 handle_connection

static void handle_connection(struct io_uring *ring, int cfd, int client_num) {
    struct io_uring_sqe *sqe;
    struct io_data *data;
    char *buffer;

    buffer = malloc(BLOCK_SZ);
    data = malloc(sizeof(struct io_data));
    data->fd = cfd; // 保存连接套接字描述符
    
    // 发送“Hello client”消息
    sprintf(buffer, "Hello client %d", client_num);
    send(cfd, buffer, strlen(buffer), 0);

    data->iov.iov_base = buffer;
    data->iov.iov_len = BLOCK_SZ;

    sqe = io_uring_get_sqe(ring);
    if (!sqe) {
        fprintf(stderr, "Could not get sqe.\n");
        exit(1);
    }

    // 接收客户端消息
    io_uring_prep_readv(sqe, cfd, &data->iov, 1, 0);
    io_uring_sqe_set_data(sqe, data);
}

这段代码的作用是向客户端发送一条欢迎消息,并准备接收客户端发送的消息。通过使用 io_uring 提供的异步 I/O 操作,实现了高效的客户端连接处理。

  • 调用 send() 函数将消息发送给客户端,发送完成后,客户端将收到 “Hello client [client_num]” 的消息。
  • 调用 io_uring_get_sqe() 函数获取一个提交队列元素(Submission Queue Entry,SQE),用于描述待提交的 I/O 操作。
  • 调用 io_uring_prep_readv() 函数准备一个接收消息的操作,并填充到获取的 SQE 中。此处传入的参数为客户端连接套接字描述符 cfd、接收缓冲区 buffer 的信息,以及缓冲区长度。
  • 调用 io_uring_sqe_set_data() 函数将之前分配的 io_data 结构体指针与 SQE 关联,以便在完成操作时能够获取到正确的套接字描述符和数据缓冲区。

3.4.4 main

int main(int argc, char *argv[]) {
    int sfd;
    struct io_uring ring;
    struct io_uring_cqe *cqe;
    int client_num = 0;
    struct itimerval timer;

    if (argc < 2) {
        printf("Usage: %s <port>\n", argv[0]);
        return 1;
    }

    sfd = setup_listening_socket(atoi(argv[1]));
    if (sfd < 0)
        return 1;

    if (io_uring_queue_init(QUEUE_DEPTH, &ring, 0) < 0) {
        perror("io_uring_queue_init");
        return 1;
    }

    submit_accept(&ring, sfd);

    while (1) {
        if (io_uring_submit(&ring) < 0) {
            perror("io_uring_submit");
            break;
        }

        if (io_uring_wait_cqe(&ring, &cqe) < 0) {
            perror("io_uring_wait_cqe");
            break;
        }

        struct io_data *data = (struct io_data *)io_uring_cqe_get_data(cqe);
        if (cqe->res < 0) {
            fprintf(stderr, "Async read failed.\n");
            return 1;
        } else {
            if (data->fd == sfd) {
                int cfd = cqe->res;
                handle_connection(&ring, cfd, ++client_num);
                submit_accept(&ring, sfd);
                printf("New connection accepted, and the socket fd is %d\n", cfd);
            } else {
                write(STDOUT_FILENO, data->iov.iov_base, cqe->res);
                sprintf(data->iov.iov_base, "Response from server to client %d: %.*s", client_num, cqe->res, (char *)data->iov.iov_base);
                send(data->fd, data->iov.iov_base, strlen(data->iov.iov_base), 0);
            }
        }

        io_uring_cqe_seen(&ring, cqe);
        free(data->iov.iov_base);
        free(data);
    }

    io_uring_queue_exit(&ring);

    return 0;
}

该程序实现了一个简单的基于异步 I/O 的 TCP 服务器,能够同时处理多个客户端的连接和消息交互,提供了高性能和高并发的网络服务能力。

  • main 函数首先解析命令行参数,获取服务器监听的端口号。如果命令行参数不足,则打印用法提示并退出程序。
  • 调用 setup_listening_socket 函数创建一个监听套接字,并将其绑定到指定端口上。如果创建和绑定套接字失败,则程序返回并退出。
  • 调用 io_uring_queue_init 函数初始化一个 io_uring 环,设置了队列的深度为 QUEUE_DEPTH,并将其赋值给 ring 结构体。如果初始化失败,则打印错误信息并退出程序。
  • 调用 submit_accept 函数向 io_uring 提交一个接受连接请求的操作。
  • 使用 io_uring_submit 函数将提交队列中的操作提交到 io_uring 环中进行处理。
  • 使用 io_uring_wait_cqe 函数等待一个完成事件,一旦有完成事件就绪,就会返回一个指向完成队列元素(CQE)的指针 cqe。
  • 如果是新的连接请求完成,则调用 handle_connection 函数处理新的连接,并继续提交下一个接受连接请求的操作。
  • 如果是数据读取完成,则将客户端发送的消息原样返回,并发送一条响应消息给客户端。
  • 使用 io_uring_cqe_seen 函数通知 io_uring 环,已经处理完了这个完成事件。
  • 在程序退出之前,调用 io_uring_queue_exit 函数清理和释放与 io_uring 环相关的资源。

3.5 io_uring client测试代码

下面是一个使用io_uring的client端测试代码,编译和测试方式通io_uring server类似。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <liburing.h>
#include <sys/resource.h>
#include <time.h>

#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 12345
#define MESSAGE "Hello, Server!\r\n"
#define BUFFER_SIZE 1024
#define QUEUE_DEPTH 1
//#define NUM_CLIENTS 50
#define NUM_CLIENTS 1
#define STATS_INTERVAL 10

struct io_data {
    int fd;
    struct iovec iov;
};

void collect_stats(time_t start_time, int requests, pid_t pid) {
    struct rusage usage;
    double cpu_usage;
    int ret;
    FILE *fp;
    unsigned long mem_usage;
    time_t end_time;
    double elapsed_time;
    char line[128];
    double rps;

    printf("----------------------- stat pid:%d start ---------------------------\n", pid);
    // 获取CPU利用率
    ret = getrusage(RUSAGE_SELF, &usage);
    if (ret == 0) {
        cpu_usage = ((double)usage.ru_utime.tv_sec + (double)usage.ru_utime.tv_usec / 1000000.0 +
                     (double)usage.ru_stime.tv_sec + (double)usage.ru_stime.tv_usec / 1000000.0) * 100.0;
        printf("CPU Usage (PID %d): %.2f%%\n", pid, cpu_usage);
    }

    // 获取内存使用情况
    fp = fopen("/proc/self/statm", "r");
    if (fp != NULL) {
        if (fgets(line, sizeof(line), fp) != NULL) {
            sscanf(line, "%*s %lu", &mem_usage);
            printf("Memory Usage (PID %d): %lu pages\n", pid, mem_usage);
        }
        fclose(fp);
    }

    // 计算RPS
    end_time = time(NULL);
    elapsed_time = difftime(end_time, start_time);
    rps = (double)requests / elapsed_time;
    printf("Requests Per Second (RPS) (PID %d): %.2f\n", pid, rps);

    // 打印统计信息
    printf("----------------------- stat pid:%d end ---------------------------\n", pid);
}

static void send_message(struct io_uring *ring, int sockfd, const char *msg) {
    struct io_uring_sqe *sqe;
    struct io_data *data;

    data = malloc(sizeof(struct io_data));

    data->iov.iov_base = (void *)msg;
    data->iov.iov_len = strlen(msg);

    sqe = io_uring_get_sqe(ring);
    if (!sqe) {
        fprintf(stderr, "Could not get sqe.\n");
        exit(EXIT_FAILURE);
    }

    // io_uring_prep_writev(sqe, sockfd, &data->iov, 1, 0);
    io_uring_prep_send(sqe, sockfd, msg, strlen(msg), 0);
    io_uring_sqe_set_data(sqe, data);

    if (io_uring_submit(ring) < 0) {
        perror("io_uring_submit");
        exit(EXIT_FAILURE);
    }
}

void client_func() {
    struct sockaddr_in server_addr;
    int sock_fd, ret;
    struct io_uring ring;
    struct io_uring_sqe *sqe;
    struct io_uring_cqe *cqe;
    char buffer[BUFFER_SIZE];
    int requests = 0;
    time_t start_time = time(NULL);
    pid_t pid = getpid();

    printf("The cur task id is: %d\r\n", pid);

    // 创建socket
    sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (sock_fd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    // 初始化服务器地址结构
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
    server_addr.sin_port = htons(SERVER_PORT);

    // 连接到服务器
    if (connect(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror("connect");
        close(sock_fd);
        exit(EXIT_FAILURE);
    }

    // 初始化io_uring
    if (io_uring_queue_init(QUEUE_DEPTH, &ring, 0) < 0) {
        perror("io_uring_queue_init");
        close(sock_fd);
        exit(EXIT_FAILURE);
    }

    while (1) {
        // 准备发送消息的操作
        sqe = io_uring_get_sqe(&ring);
        if (!sqe) {
            perror("io_uring_get_sqe");
            close(sock_fd);
            io_uring_queue_exit(&ring);
            exit(EXIT_FAILURE);
        }
        io_uring_prep_send(sqe, sock_fd, MESSAGE, strlen(MESSAGE), 0);
        io_uring_sqe_set_data(sqe, NULL);

        // 提交发送消息的操作
        if (io_uring_submit(&ring) != QUEUE_DEPTH) {
            perror("io_uring_submit");
            close(sock_fd);
            io_uring_queue_exit(&ring);
            exit(EXIT_FAILURE);
        }

        // 等待发送完成
        ret = io_uring_wait_cqe(&ring, &cqe);
        if (ret < 0) {
            perror("io_uring_wait_cqe");
            close(sock_fd);
            io_uring_queue_exit(&ring);
            exit(EXIT_FAILURE);
        }

        // 接收服务器的响应
        ret = recv(sock_fd, buffer, BUFFER_SIZE, 0);
        if (ret == -1) {
            perror("recv");
            printf("recv error.\n");
            close(sock_fd);
            io_uring_cqe_seen(&ring, cqe);
            io_uring_queue_exit(&ring);
            exit(EXIT_FAILURE);
        } else if (ret == 0) {
            io_uring_cqe_seen(&ring, cqe);
            printf("Connection closed by server\n");
            break;
        } else {
            io_uring_cqe_seen(&ring, cqe);
            printf("Received message from server: %.*s\r\n", ret, buffer);

            // 统计请求数
            requests++;

            printf(" ============================= pid:%d requests:%d sleep =============================\r\n", pid, requests);
            // 每隔1秒钟进行一次通信
            sleep(1);
            printf(" ============================= pid:%d requests:%d weekup =============================\r\n", pid, requests);

            // 每隔一定时间打印一次统计信息
            if (requests % STATS_INTERVAL == 0) {
                printf(" ############################################ 1 ##################################################\r\n");
                collect_stats(start_time, requests, pid);
                printf(" ############################################ 2 ##################################################\r\n");
            }

            send_message(&ring, sock_fd, "Reply from client\r\n");
        }
    }

    // 关闭socket
    close(sock_fd);
    io_uring_queue_exit(&ring);
}


int main() {
    int i;

    for (i = 0; i < NUM_CLIENTS; i++) {
        pid_t pid = fork();
        if (pid == -1) {
            perror("fork");
            exit(EXIT_FAILURE);
        } else if (pid == 0) {
            client_func();
            exit(EXIT_SUCCESS);
        }
    }

    // 等待所有子进程结束
    while (wait(NULL) > 0);

    return 0;
}


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1668453.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何快速展示专业:掌握类的基本概念-类/方法/关键字/变量/数据类型/注释

在李笑来的《财富自由之路》中提到一种初学者快速入门的学习方法&#xff1a;快速掌握最小必要知识。 关于Java的类&#xff0c;最少必要知识就是本文提到的基本概念&#xff0c;掌握了这些基本概念&#xff0c;就对类有了基本的了解&#xff0c;为后续的深入学习和沟通奠定了基…

MFC桌面应用中窗口的客户区与非客户区的

在MFC&#xff08;Microsoft Foundation Class&#xff09;中&#xff0c;窗口被分为客户区和非客户区。理解这两个概念对于设计和开发Windows应用程序至关重要。 客户区&#xff08;Client Area&#xff09;&#xff1a; 客户区是窗口中用于显示应用程序内容的区域。它是窗口…

单链表经典算法OJ题---力扣206,876(带图详解

1.链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09;【点击即可跳转】 思路&#xff1a;创建三个指针&#xff0c;看下图 注意&#xff1a;n3如果为空&#xff0c;则不能继续指向下一节点&#xff0c;需要进行判断 代码实现&#xff1a; struct ListNode* reverseLi…

VS中scanf使用的常见问题

本篇文章给大家讲一讲如何解决scanf使用的常见问题 那么先给大家看一下scanf在vs编译器中出现的问题 从图中大家可以看到这串代码报错了&#xff0c;那么我们来看看报错信息 从图中我们可以看到&#xff0c;vs说scanf不安全&#xff0c;并且也给了我们两种解决方法 第一种&…

路由器、交换机和网卡

大家使用VMware安装镜像之后&#xff0c;是不是都会考虑虚拟机的镜像系统怎么连上网的&#xff0c;它的连接方式是什么&#xff0c;它ip是什么&#xff1f; 路由器、交换机和网卡 1.路由器 一般有几个功能&#xff0c;第一个是网关、第二个是扩展有线网络端口、第三个是WiFi功…

数据恢复软件 –最好的Android数据恢复软件分享

在安卓数据恢复方面&#xff0c;奇客数据恢复安卓版是最好的 Android 数据恢复公司&#xff0c;因为它的成功率为 100%。随着无数企业和个人使用智能手机和平板电脑&#xff0c;总是有很多数据丢失或损坏的机会&#xff0c;这就是它们如此受欢迎的原因。在恢复数据时&#xff0…

vue+springboot项目服务器部署

①创建一台opencloud8的腾讯云服务器 ②用xshell连接服务器 ③vue中新建.env.development配置文件 .env.development: VUE_APP_BASEURLhttp://localhost:9090 .env.production: VUE_APP_BASEURLhttp://服务器ip:9090 ④修改main.js import Vue from vue import App from ./A…

IB 公式解析

公式 3.2. Influence Function 影响函数允许我们在移除样本时估计模型参数的变化&#xff0c;而无需实际移除数据并重新训练模型。 3.3 影响平衡加权因子 3.4 影响平衡损失 3.5 类内重加权 m代表一个批次&#xff08;batch&#xff09;的大小&#xff0c;这意味着公式对一个批…

【qt】最快的开发界面效率——混合编程

混合编程 一.准备工作1.创建项目2.添加项目资源 二.ui界面设计1.menuBar菜单栏2.action ▲3.toolBar工具栏4.中心组件 三.代码界面设计1.toolBar添加组件2.statusBar状态栏添加组件 四.完成界面的功能1.对action配置信号槽2.对action转到信号槽3.代码添加的组件手动关联槽函数 …

鸿蒙内核源码分析(Shell编辑篇) | 两个任务,三个阶段

系列篇从内核视角用一句话概括shell的底层实现为&#xff1a;两个任务&#xff0c;三个阶段。其本质是独立进程&#xff0c;因而划到进程管理模块。每次创建shell进程都会再创建两个任务。 客户端任务(ShellEntry)&#xff1a; 负责接受来自终端(控制台)敲入的一个个字符&…

第五步->手撕spring源码之资源加载器解析到注册

本步骤目标 在完成 Spring 的框架雏形后&#xff0c;现在我们可以通过单元测试进行手动操作 Bean 对象的定义、注册和属性填充&#xff0c;以及最终获取对象调用方法。但这里会有一个问题&#xff0c;就是如果实际使用这个 Spring 框架&#xff0c;是不太可能让用户通过手动方式…

PD-L1表达与免疫逃逸和免疫响应

免疫检查点信号转导和癌症免疫治疗&#xff08;文献&#xff09;-CSDN博客https://blog.csdn.net/hx2024/article/details/137470621?ops_request_misc%257B%2522request%255Fid%2522%253A%2522171551954416800184136566%2522%252C%2522scm%2522%253A%252220140713.130102334.…

webrtc windows 编译,以及peerconnection_client

webrtc windows环境编译&#xff0c;主要参考webrtc官方文档&#xff0c;自备梯子 depot tools 安装 Install depot_tools 因为我用的是windows&#xff0c;这里下载bundle 的安装包&#xff0c;然后直接解压&#xff0c;最后设置到环境变量PATH。 执行gn等命令不报错&…

A计算机上的程序与B计算机上部署的vmware上的虚拟机的程序通讯 如何配置?

环境&#xff1a; 在A计算机上运行着Debian11.3 Linux操作系统&#xff1b;在B计算机上运行着Windows10操作系统&#xff0c;并且安装了VMware软件&#xff0c;然后在VMware上创建了虚拟机C并安装了CentOS 6操作系统 需求&#xff1a; 现在A计算机上的程序需要同虚拟机C上的软…

【递归、回溯和剪枝】全排列 子集

0.回溯算法介绍 什么是回溯算法 回溯算法是⼀种经典的递归算法&#xff0c;通常⽤于解决组合问题、排列问题和搜索问题等。 回溯算法的基本思想&#xff1a;从⼀个初始状态开始&#xff0c;按照⼀定的规则向前搜索&#xff0c;当搜索到某个状态⽆法前进时&#xff0c;回退到前…

docker容器实现https访问

前言&#xff1a; 【云原生】docker容器实现https访问_docker ssl访问-CSDN博客 一术语介绍 ①key 私钥 明文--自己生成&#xff08;genrsa &#xff09; ②csr 公钥 由私钥生成 ③crt 证书 公钥 签名&#xff08;自签名或者由CA签名&#xff09; ④证书&#xf…

Eclipse下载安装教程(包含JDK安装)【保姆级教学】【2024.4已更新】

目录 文章最后附下载链接 第一步&#xff1a;下载Eclipse&#xff0c;并安装 第二步&#xff1a;下载JDK&#xff0c;并安装 第三步&#xff1a;Java运行环境配置 安装Eclipse必须同时安装JDK &#xff01;&#xff01;&#xff01; 文章最后附下载链接 第一步&#xf…

Go编程语言的调试器Delve | Goland远程连接Linux开发调试(go远程开发)

文章目录 Go编程语言的调试器一、什么是Delve二、delve 安装安装报错cgo: C compiler "gcc" not found: exec: "gcc": executable file not found in $PATH解决 三、delve命令行使用delve 常见的调试模式常用调试方法todo调试程序代码与动态库加载程序运行…

Unity编辑器如何多开同一个项目?

在联网游戏的开发过程中&#xff0c;多开客户端进行联调是再常见不过的需求。但是Unity并不支持编辑器多开同一个项目&#xff0c;每次都得项目打个包(耗时2分钟以上)&#xff0c;然后编辑器开一个进程&#xff0c;exe 再开一个&#xff0c;真的有够XX的。o(╥﹏╥)o没错&#…

Windows下安装 Emscripten 详细过程

背景 最近研究AV1编码标准的aom编码器&#xff0c;编译的过程中发现需要依赖EMSDK&#xff0c;看解释EMSDK就是Emscripten 的相应SDK&#xff0c;所以此博客记录下EMSDK的安装过程&#xff1b;因为之前完全没接触过Emscripten 。 Emscripten Emscripten 是一个用于将 C 和 …