了解一下与epoll媲美的io_uring

简介: 了解一下与epoll媲美的io_uring

1 io_uring是Linux内核的一个新型I/O事件通知机制,具有以下特点:

高性能:相比传统的select/poll/epoll等I/O多路复用机制,io_uring采用了更高效的ring buffer实现方式,可以在处理大量并发I/O请求时提供更高的吞吐量和低延迟。

异步:io_uring支持异步I/O操作,并且可以通过用户空间和内核空间之间的共享内存映射来避免数据拷贝,从而减少了CPU的开销。

事件批处理:io_uring可以将多个I/O操作合并成一个请求进行处理,从而降低了系统调用的次数和上下文切换的开销。

灵活性:io_uring提供了非常灵活的接口和配置选项,可以根据应用程序的需要进行优化和调整。同时,它还支持多线程操作和信号驱动I/O等功能。

2 安装

确认系统内核是5.10以后的版本,还需安装liburing(依赖于内核的三个新的系统调用
io_uring_setup io_uring_register io_uring_enter )
安装liburing库,它对内核三个新的系统进行了封装
git clone https://github.com/axboe/liburing.git
cd liburing/
./configure
make && make install

3 原理图

sq 与 cq 都是环型的队列(这里一看到队列,就要想到它起到异步解耦的作用), 读写都是异步的
在这里插入图片描述
submit queue 任务提交队列
complete queue 任务处理完成的队列
sq 与 cq 是共用相同的节点,sq到cq没有拷贝操作,而且节点所占用的内存是内核空间与用户空间共享的
(看源码可得:内存是在内核空间开辟的,然后映射到用户空间)

4 源码

#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>

#include <liburing.h>

#define ENTRIES_LENGTH        1024

enum {
   
   
    EVENT_ACCEPT = 0,
    EVENT_READ,
    EVENT_WRITE
};

typedef struct _conninfo {
   
   
    int connfd;
    int event;
} conninfo;

// sizeof(conninfo)  = 8

// 0, 1, 2
// 3, 4, 5

void set_send_event(struct io_uring *ring, int sockfd, void *buf, size_t len, int flags) {
   
   

    struct io_uring_sqe *sqe = io_uring_get_sqe(ring);

    io_uring_prep_send(sqe, sockfd, buf, len, flags);
    conninfo info_send = {
   
   
        .connfd = sockfd,
        .event = EVENT_WRITE,
    };
    memcpy(&sqe->user_data, &info_send, sizeof(info_send));

}


void set_recv_event(struct io_uring *ring, int sockfd, void *buf, size_t len, int flags) {
   
   

    struct io_uring_sqe *sqe = io_uring_get_sqe(ring);

    io_uring_prep_recv(sqe, sockfd, buf, len, flags);
    conninfo info_recv = {
   
   
        .connfd = sockfd,
        .event = EVENT_READ,
    };
    memcpy(&sqe->user_data, &info_recv, sizeof(info_recv));

}

void set_accept_event(struct io_uring *ring, int sockfd, struct sockaddr *addr,
                   socklen_t *addrlen, int flags) {
   
   

    struct io_uring_sqe *sqe = io_uring_get_sqe(ring);

    io_uring_prep_accept(sqe, sockfd, addr, addrlen, flags);
    conninfo info_accept = {
   
   
        .connfd = sockfd,
        .event = EVENT_ACCEPT,
    };
    memcpy(&sqe->user_data, &info_accept, sizeof(info_accept));

}


int main() {
   
   

    int sockfd = socket(AF_INET, SOCK_STREAM, 0); // io

    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(struct sockaddr_in)); // 192.168.2.123
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0
    servaddr.sin_port = htons(9999);

    if (-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))) {
   
   
        printf("bind failed: %s", strerror(errno));
        return -1;
    }

    listen(sockfd, 10); 
//liburing

    struct io_uring_params params;
    memset(&params, 0, sizeof(params));

    struct io_uring ring;
    io_uring_queue_init_params(ENTRIES_LENGTH, &ring, &params);

    struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);

    struct sockaddr_in clientaddr;
    socklen_t clilen = sizeof(struct sockaddr);

    set_accept_event(&ring, sockfd, (struct sockaddr*)&clientaddr, &clilen, 0);

    char buffer[1024] = {
   
   0};

    while (1) {
   
   

        io_uring_submit(&ring);

        struct io_uring_cqe *cqe;
        io_uring_wait_cqe(&ring, &cqe);

        struct io_uring_cqe *cqes[10];
        int cqecount = io_uring_peek_batch_cqe(&ring, cqes, 10);

        //printf("cqecount --> %d\n", cqecount);
        int i = 0;
        for (i = 0;i < cqecount;i ++) {
   
   

            cqe = cqes[i];

            conninfo ci;
            memcpy(&ci, &cqe->user_data, sizeof(ci));

            if (ci.event == EVENT_ACCEPT) {
   
     // recv/send


                if (cqe->res < 0) continue;

                int connfd = cqe->res;
                //printf("accept --> %d\n", connfd);
                set_accept_event(&ring, ci.connfd, (struct sockaddr*)&clientaddr, &clilen, 0);

                set_recv_event(&ring, connfd, buffer, 1024, 0);

            } else if (ci.event == EVENT_READ) {
   
   

                if (cqe->res < 0) continue;
                if (cqe->res == 0) {
   
   

                    close(ci.connfd);

                } else {
   
   

                    printf("recv --> %s, %d\n", buffer, cqe->res);

                    //set_recv_event(&ring, ci.connfd, buffer, 1024, 0);
                    set_send_event(&ring, ci.connfd, buffer, cqe->res, 0);
                }

            } else if (ci.event == EVENT_WRITE) {
   
    //


                //printf("write complete\n");
                set_recv_event(&ring, ci.connfd, buffer, 1024, 0);

            }    
        }
        io_uring_cq_advance(&ring, cqecount);
    }
    getchar();

}

5 结果演示

编译 gcc -o uring_server uring_server.c -luring -static
运行 ./uring_server, 借助网络调试助手测试
在这里插入图片描述
文章参考与<零声教育>的C/C++linux服务期高级架构系统教程学习:链接

相关文章
|
网络协议 安全 测试技术
手撕测试tcp服务器效率工具——以epoll和io_uring对比为例
手撕测试tcp服务器效率工具——以epoll和io_uring对比为例
349 2
|
存储 Linux 调度
io复用之epoll核心源码剖析
epoll底层实现中有两个关键的数据结构,一个是eventpoll另一个是epitem,其中eventpoll中有两个成员变量分别是rbr和rdlist,前者指向一颗红黑树的根,后者指向双向链表的头。而epitem则是红黑树节点和双向链表节点的综合体,也就是说epitem即可作为树的节点,又可以作为链表的节点,并且epitem中包含着用户注册的事件。当用户调用epoll_create()时,会创建eventpoll对象(包含一个红黑树和一个双链表);
265 0
io复用之epoll核心源码剖析
|
存储 网络协议
TCP服务器 IO多路复用的实现:select、poll、epoll
TCP服务器 IO多路复用的实现:select、poll、epoll
257 0
|
网络协议 Linux C++
Linux C/C++ 开发(学习笔记十二 ):TCP服务器(并发网络编程io多路复用epoll)
Linux C/C++ 开发(学习笔记十二 ):TCP服务器(并发网络编程io多路复用epoll)
328 0
|
存储 Linux
图解IO多路复用模型之select、poll、epoll
图解IO多路复用模型之select、poll、epoll
294 0
|
存储 网络协议
与epoll媲美的io_uring
与epoll媲美的io_uring
147 0
|
9月前
|
网络协议 Linux Go
用 Go 基于 epoll 实现一个最小化的IO库
Go 语言社区中存在多个异步网络框架,如 evio、nbio、gnet 和 netpoll 等。这些框架旨在解决标准库 netpoll 的低效问题,如一个连接占用一个 goroutine 导致的资源浪费。easyio 是一个最小化的 IO 框架,核心代码不超过 500 行,仅实现 Linux 下的 epoll 和 TCP 协议。它通过 Worker Pool、Buffer 等优化提高了性能,并提供了简单的事件处理机制。
137 0
|
Linux C++
Linux C/C++之IO多路复用(poll,epoll)
这篇文章详细介绍了Linux下C/C++编程中IO多路复用的两种机制:poll和epoll,包括它们的比较、编程模型、函数原型以及如何使用这些机制实现服务器端和客户端之间的多个连接。
431 0
Linux C/C++之IO多路复用(poll,epoll)
|
安全 Java Linux
(七)Java网络编程-IO模型篇之从BIO、NIO、AIO到内核select、epoll剖析!
IO(Input/Output)方面的基本知识,相信大家都不陌生,毕竟这也是在学习编程基础时就已经接触过的内容,但最初的IO教学大多数是停留在最基本的BIO,而并未对于NIO、AIO、多路复用等的高级内容进行详细讲述,但这些却是大部分高性能技术的底层核心,因此本文则准备围绕着IO知识进行展开。
507 1
|
存储 Java Unix
(八)Java网络编程之IO模型篇-内核Select、Poll、Epoll多路复用函数源码深度历险!
select/poll、epoll这些词汇相信诸位都不陌生,因为在Redis/Nginx/Netty等一些高性能技术栈的底层原理中,大家应该都见过它们的身影,接下来重点讲解这块内容。
358 0