io_uring的使用示例及其解释

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    1024struct 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 10struct 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);}// 计算RPSend_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);// 创建socketsock_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_uringif (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");}}// 关闭socketclose(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.mzph.cn/news/836542.shtml

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

相关文章

基础ArkTS组件:导航栏组件(HarmonyOS学习第三课【3.8】)

Navigation 官方文献 Navigation 组件一般作为页面布局的根容器&#xff0c;它提供了一系列属性方法来设置页面的标题栏、工具栏以及菜单栏的各种展示样式。 Navigation 除了提供了默认的展示样式属性外&#xff0c;它还提供了 CustomBuilder 模式来自定义展示样式 说明 该…

OCR技术在历史文献数字化中的革命性作用

随着数字化技术的不断发展&#xff0c;历史文献的数字化已成为保存和传播文化遗产的重要途径。其中&#xff0c;光学字符识别&#xff08;OCR&#xff09;技术在历史文献数字化中发挥了革命性的作用&#xff0c;为研究者提供了更广阔的研究空间&#xff0c;推动了历史学研究的发…

kafka安装及收发消息

kafka需要与zookeeper配合使用&#xff0c;但是从2.8版本kafka引入kraft&#xff0c;也就是说在2.8后&#xff0c;zookeeper和kraft都可以管理kafka集群&#xff0c;这里我们依然采用zookeeper来配合kafka。 1、首先我们下载zookeeper 下载地址为 https://zookeeper.apache.org…

三. TensorRT基础入门-剖析ONNX架构并理解ProtoBuf

目录 前言0. 简述1. 执行一下我们的python程序2. ONNX是什么&#xff1f;3. onnx中的各类Proto3.1 理解onnx中的ValueInfoProto3.2 理解onnx中的TensorProto3.3 理解onnx中的NodeProto3.4 理解onnx中的AttributeProto3.5 理解onnx中的GraphProto3.6 理解onnx中的ModelProto 4. …

算法提高之单词接龙

算法提高之单词接龙 核心思想&#xff1a;dfs 预处理每两个字符串之间最短的公共部分长度 求最短公共 最终字符串是最长 dfs所有开头字符串 #include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N 25;int g[N][N…

Feign 和 OpenFeign 的区别

Feign 和 OpenFeign 都是用来进行服务间调用的客户端库&#xff0c;它们旨在简化HTTP API客户端的编写过程&#xff0c;使得编写对外部服务的接口就像调用本地方法一样简单。尽管它们有相似之处&#xff0c;但也存在一些关键差异&#xff1a; 归属和演进&#xff1a; Feign 最初…

大规模 RGB LED灯控系统 Lumos:创新与智能化的融合

灯控系统&#xff1a;创新与智能化的融合 在现代照明技术不断进步的背景下&#xff0c;灯控系统的应用已经从简单的开关控制&#xff0c;发展到能够进行复杂程控操作的智能化管理。我们推出的新一代灯控解决方案&#xff0c;凭借其高度的可配置性和跨平台兼容性&#xff0c;已…

虚拟化数据恢复—误还原虚拟机快照怎么办?怎么恢复最新虚拟机数据?

虚拟化技术原理是将硬件虚拟化给不同的虚拟机使用&#xff0c;利用虚拟化技术可以在一台物理机上安装多台虚拟机。误操作或者物理机器出现故障都会导致虚拟机不可用&#xff0c;虚拟机中的数据丢失。 虚拟化数据恢复环境&#xff1a; 有一台虚拟机是由物理机迁移到ESXI上面的&a…

pikachu靶场(xss通关教程)

&#xff08;注&#xff1a;若复制注入代码攻击无效&#xff0c;请手动输入注入语句&#xff0c;在英文输入法下&#xff09; 反射型xss(get型) 1.打开网站 发现有个框&#xff0c;然后我们在框中输入一个“1”进行测试&#xff0c; 可以看到提交的数据在url处有显示&#xf…

Debian Linux 下给Nginx 1.26.0 编译增加Brotli算法支持

明月发现参考【给Nginx添加谷歌Brotli压缩算法支持】一文给出的方法&#xff0c;在Debian Linux 12.5下就一直编译失败&#xff0c;主要的错误是因为文件缺失&#xff0c;在专门又安装了apt-get install libbrotli-dev的依赖库后依然会因为文件缺失无法编译完成&#xff0c;就这…

ERP与MES与WMS集成

WMS储位管理 WMS与MES集成 (一) 打通追溯链 在拣货时&#xff0c;将配料标签与供应商的物料标签进行关联。通过配料标签达到精确追溯及防错目的。针对模糊查询&#xff0c;将工单与物料的供应商信息、仓库流转信息进行关联。 (二) WMS入库 成品(半成品)下线后&#xff0c;M…

draw.text((left, top - 15), text,font=font, fill=“green”)

这是一个Python PIL库中的方法&#xff0c;用于在图片上绘制文本。具体来说&#xff0c;它可以在指定的位置绘制指定的文本&#xff0c;并使用指定的字体、颜色等参数进行渲染。其中&#xff0c;left和top是文本绘制的左上角坐标&#xff0c;text是要绘制的文本内容&#xff0c…

齿轮滚刀刃口钝化技术简介

介绍 在滚刀的使用中发现&#xff0c;进口滚刀和国产滚刀在加工质量和寿命方面存在显著差异。经过多次比较得知&#xff0c;滚刀的使用寿命可以达到国产滚刀的两倍以上&#xff0c;而进口滚刀返回原厂磨削后的使用寿命约为新刀具的90% &#xff0c;但同样经过国内厂家磨削后&a…

【C语言项目】贪吃蛇(下)

个人主页~ 源码在Gitee仓库~ 上一篇贪吃蛇&#xff08;上&#xff09;~ 贪吃蛇 四、核心的实现游戏测试1、GameStart&#xff08;1&#xff09;控制台窗口大小和名字设置&#xff08;2&#xff09;光标隐藏&#xff08;3&#xff09;打印欢迎界面&#xff08;4&#xff09;创建…

azkaban-tools 项目介绍

本文背景 应一个用户的好心和好奇心&#xff0c;在最近水深火热的百忙之中抽时间写完了一个简短的项目介绍&#xff0c;其实就是几个azkaban的批量操作脚本&#xff0c;但在大数据集群的“运维生涯”中&#xff0c;还是帮了自己不少忙&#xff0c;也算是为了它做一个简单的回顾…

Java | Leetcode Java题解之第85题最大矩形

题目&#xff1a; 题解&#xff1a; class Solution {public int maximalRectangle(char[][] matrix) {int m matrix.length;if (m 0) {return 0;}int n matrix[0].length;int[][] left new int[m][n];for (int i 0; i < m; i) {for (int j 0; j < n; j) {if (mat…

Python3 + Appium + 安卓模拟器实现APP自动化测试并生成测试报告

这篇文章主要介绍了Python3 Appium 安卓模拟器实现APP自动化测试并生成测试报告,本文给大家介绍的非常详细&#xff0c;对大家的学习或工作具有一定的参考借鉴价值&#xff0c;需要的朋友可以参考下 本文主要分为以下几个部分 安装Python3 安装Python3的Appium库 安装Andr…

Mp3tag for Mac:音乐标签,轻松管理

还在为杂乱无章的音乐文件而烦恼吗&#xff1f;Mp3tag for Mac&#xff0c;让您的音乐库焕然一新&#xff01;它支持多种音频格式&#xff0c;批量编辑标签&#xff0c;让音乐管理变得简单高效。同时&#xff0c;自动获取在线数据库的音乐元数据&#xff0c;确保您的音乐库始终…

kafka安装配置及集成springboot

1. 安装 单机安装kafka Kafka对于zookeeper是强依赖&#xff0c;保存kafka相关的节点数据&#xff0c;所以安装Kafka之前必须先安装zookeeper dockerhub网址: https://hub.docker.com Docker安装zookeeper 下载镜像&#xff1a; docker pull zookeeper:3.4.14创建容器 doc…

docker(五):DockerFile

文章目录 DockerFile1、Dockerfile构建过程解析2、DockerFile常用保留字命令FROMMAINTAINERRUNEXPOSEWORKDIRUSERENVADDCOPYVOLUMECMDENTRYPOINT总结 3、案例 DockerFile 1、Dockerfile构建过程解析 官网文档&#xff1a;https://docs.docker.com/reference/dockerfile/ Dock…