首页 短视频

高性能 TCP 服务器:io_uring 异步 I/O 的实战解析与应用

分类:短视频
字数: (1369)
阅读: (3352)
内容摘要:高性能 TCP 服务器:io_uring 异步 I/O 的实战解析与应用,

在构建高性能 TCP 服务器时,传统的阻塞 I/O 模型往往会成为性能瓶颈。每个连接都需要一个线程处理,导致资源消耗巨大,在高并发场景下,服务器很容易达到性能瓶颈。本文将深入探讨如何使用 Linux 内核提供的 io_uring 技术,实现高效的异步 I/O 处理,构建一个高性能的 io_uring_tcp_server,并分享实战中的一些经验教训。

io_uring 底层原理深度剖析

io_uring 是 Linux 内核提供的一种新型异步 I/O 接口,它通过共享的完成队列 (Completion Queue, CQ) 和提交队列 (Submission Queue, SQ) 实现用户空间和内核空间的数据交换,从而避免了传统 I/O 模型的上下文切换和数据拷贝开销。相比于 epoll,io_uring 更加灵活,可以处理更多的 I/O 操作,并且拥有更好的性能表现。

具体来说,io_uring 的工作流程如下:

高性能 TCP 服务器:io_uring 异步 I/O 的实战解析与应用
  1. 用户空间准备 I/O 请求: 用户程序将 I/O 请求(例如读、写、accept 等)封装成 io_uring 的数据结构,并将其放入提交队列 (SQ)。
  2. 内核空间处理 I/O 请求: 内核从提交队列 (SQ) 中取出 I/O 请求,并将其提交给相应的 I/O 设备驱动程序进行处理。
  3. I/O 完成: 当 I/O 操作完成后,内核将完成事件放入完成队列 (CQ)。
  4. 用户空间获取 I/O 结果: 用户程序从完成队列 (CQ) 中获取 I/O 完成事件,并处理 I/O 结果。

由于整个过程都是异步的,用户程序可以在等待 I/O 完成的同时,继续执行其他任务,从而提高了系统的并发能力。这与 Nginx 使用的事件驱动模型类似,都旨在提升服务器的并发连接数。

io_uring 的优势

  • 零拷贝 (Zero-copy): io_uring 允许直接在用户空间和内核空间之间传输数据,避免了不必要的数据拷贝,提高了 I/O 效率。
  • 异步 I/O: io_uring 采用异步 I/O 模型,允许用户程序在等待 I/O 完成的同时执行其他任务,提高了并发能力。
  • 批量操作: io_uring 支持批量提交和完成 I/O 请求,减少了系统调用的次数,提高了 I/O 效率。

为什么选择 io_uring?

在高并发场景下,传统 I/O 模型,如 select、poll、epoll 等,存在一些局限性。例如,epoll 在处理大量活跃连接时,仍然需要使用水平触发或者边缘触发,并且需要手动管理事件循环,较为复杂。而 io_uring 通过异步 I/O 和零拷贝等技术,可以更加高效地处理大量并发连接,并且提供了更加灵活的接口。

高性能 TCP 服务器:io_uring 异步 I/O 的实战解析与应用

使用 io_uring 实现 TCP 服务器:代码示例

下面是一个使用 io_uring 实现 TCP 服务器的简单示例代码,该示例演示了如何使用 io_uring 异步地接受连接和读取数据。为了简化代码,我们只处理了简单的读操作。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <linux/io_uring.h>

#define PORT 8080
#define QUEUE_DEPTH 256
#define BUFFER_SIZE 1024

// io_uring 上下文
struct io_uring ring;

// 创建 io_uring 实例
int setup_io_uring()
{
    int ret = io_uring_queue_init(QUEUE_DEPTH, &ring, 0); // 初始化 io_uring 队列
    if (ret < 0) {
        perror("io_uring_queue_init");
        return -1;
    }
    return 0;
}

// 添加一个 accept 请求到 io_uring
int add_accept_request(int sockfd, struct sockaddr_in *client_addr, socklen_t *client_addr_len)
{
    struct io_uring_sqe *sqe = io_uring_get_sqe(&ring); // 获取一个 sqe
    if (!sqe) {
        fprintf(stderr, "Failed to get sqe.\n");
        return -1;
    }

    io_uring_prep_accept(sqe, sockfd, (struct sockaddr*)client_addr, client_addr_len, 0); // 准备 accept 请求
    io_uring_sqe_set_data(sqe, NULL); // 设置用户数据,这里先设为 NULL
    return 0;
}

// 添加一个 read 请求到 io_uring
int add_read_request(int fd)
{
    struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
    if (!sqe) {
        fprintf(stderr, "Failed to get sqe.\n");
        return -1;
    }

    char *buf = malloc(BUFFER_SIZE); // 分配缓冲区
    if (!buf) {
        perror("malloc");
        return -1;
    }
    io_uring_prep_read(sqe, fd, buf, BUFFER_SIZE, 0); // 准备 read 请求
    io_uring_sqe_set_data(sqe, buf); // 设置用户数据为缓冲区地址
    return 0;
}

int main()
{
    int sockfd, client_sockfd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_addr_len = sizeof(client_addr);

    // 创建 socket
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("socket");
        return 1;
    }

    // 设置 socket 地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(PORT);

    // 绑定 socket
    if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("bind");
        return 1;
    }

    // 监听 socket
    if (listen(sockfd, 5) < 0) {
        perror("listen");
        return 1;
    }

    // 初始化 io_uring
    if (setup_io_uring() < 0) {
        return 1;
    }

    // 提交第一个 accept 请求
    if (add_accept_request(sockfd, &client_addr, &client_addr_len) < 0) {
        return 1;
    }

    while (1) {
        // 提交队列中的所有请求
        io_uring_submit(&ring);

        // 等待完成事件
        struct io_uring_cqe *cqe;
        io_uring_wait_cqe(&ring, &cqe);

        // 处理完成事件
        if (cqe->res >= 0) { // 操作成功
            if (cqe->flags == 0) { // accept 事件
                client_sockfd = cqe->res;  // 获取客户端 socket
                printf("Accepted connection from %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

                // 提交 read 请求
                if (add_read_request(client_sockfd) < 0) {
                   close(client_sockfd);
                   free(io_uring_cqe_get_data(cqe)); //释放 accept 中没有使用的数据
                }

                // 再次提交 accept 请求,用于接受下一个连接
                if (add_accept_request(sockfd, &client_addr, &client_addr_len) < 0) {
                    return 1;
                }
            } else { // read 事件
                char *buf = (char*)io_uring_cqe_get_data(cqe);  // 获取缓冲区地址
                printf("Received data: %s\n", buf);
                free(buf); // 释放缓冲区
                close(cqe->fd); // 关闭客户端 socket
            }
        } else {
            fprintf(stderr, "Async operation failed: %s\n", strerror(-cqe->res));
        }

        // 标记 cqe 已经处理完毕
        io_uring_cqe_seen(&ring, cqe);
    }

    // 关闭 io_uring
    io_uring_queue_exit(&ring);
    close(sockfd);

    return 0;
}

注意: 上述代码只是一个简单的示例,实际应用中需要考虑更多的错误处理、连接管理、数据传输等问题。同时,可以使用类似宝塔面板这样的工具进行服务器管理,但需注意其安全性配置。

高性能 TCP 服务器:io_uring 异步 I/O 的实战解析与应用

实战避坑经验总结

在使用 io_uring 构建 io_uring_tcp_server 时,需要注意以下几点:

  • 内存管理: 由于 io_uring 涉及异步 I/O,需要特别注意内存管理,避免内存泄漏和野指针。例如,在提交 read 请求时,需要分配缓冲区,并在读取完成后释放缓冲区。
  • 错误处理: io_uring 的错误处理相对复杂,需要仔细检查每个操作的返回值,并进行相应的处理。例如,如果 accept 或 read 操作失败,需要关闭 socket 并释放资源。
  • 性能调优: io_uring 的性能受到多种因素的影响,例如队列深度、I/O 大小、CPU 频率等。需要根据实际情况进行性能调优,以达到最佳性能。
  • 版本兼容性: 确保内核版本支持 io_uring,建议使用较新的内核版本。
  • 避免阻塞操作: 尽量避免在 io_uring 的回调函数中执行阻塞操作,否则会影响性能。如果必须执行阻塞操作,可以将其放入单独的线程中执行。

在实际应用中,可以使用线程池来处理 I/O 操作,避免阻塞主线程。此外,还可以使用一些现有的 io_uring 库,例如 liburing,来简化开发过程。通过合理的设计和优化,可以利用 io_uring 构建高性能、高并发的 TCP 服务器。

高性能 TCP 服务器:io_uring 异步 I/O 的实战解析与应用

在高并发场景下,服务器的并发连接数是一个重要的指标。通过使用 io_uring,可以显著提高服务器的并发连接数,并降低资源消耗。

io_uring 结合 epoll:更灵活的选择

虽然 io_uring 在某些方面优于 epoll,但这并不意味着完全抛弃 epoll。在实际项目中,可以将两者结合使用,例如使用 epoll 监听连接事件,然后使用 io_uring 进行数据读写。这种方式可以充分发挥两者的优势,实现更加灵活和高效的 I/O 处理。

总结

io_uring 作为一种新型的异步 I/O 技术,为构建高性能 TCP 服务器提供了新的选择。通过深入理解 io_uring 的原理,并结合实际应用场景进行优化,可以充分发挥其优势,构建高性能、高并发的 TCP 服务器。同时,也要注意内存管理、错误处理和性能调优等方面的问题,避免踩坑。

高性能 TCP 服务器:io_uring 异步 I/O 的实战解析与应用

转载请注明出处: 代码一只喵

本文的链接地址: http://m.acea4.store/article/25678.html

本文最后 发布于2026-04-14 00:13:18,已经过了13天没有更新,若内容或图片 失效,请留言反馈

()
您可能对以下文章感兴趣
评论
  • 咕咕咕 3 天前
    现在用得最多的还是 epoll,迁移到 io_uring 的成本有点高啊,需要评估一下。
  • 摆烂大师 4 天前
    写得太棒了!io_uring 的确是未来的方向,解决了 epoll 的一些痛点。感谢分享。
  • 秋名山车神 1 天前
    感谢大佬分享,对理解 io_uring 的底层原理很有帮助,避坑经验也很实在。
  • 非酋本酋 2 天前
    代码示例很实用,正好最近在研究 io_uring,学习了!不过错误处理部分感觉可以再详细一些。