互联网程序设计 姓名:黄健锋 学号:202221080930 基于 io_uring 实现事件处理引擎 1. 总体要求 模仿 muduo 中 epoll 的事件处理方案,采用 io_uring 实现一个事件循环 安装 liburing,阅读 io_uring_setup,io_uring_enter 手册 采用 io_uring 实现一个事件分发器,角色相当于 epoll 实现两个 channel,FileChannel、TcpChannel 做功能测试、性能测试 2. io_uring 介绍: (1) io_uring 是 linux 平台提供的异步 io 机制,io_uring 的设计目标是提供一个统一、易 用、可扩展、功能丰富、高效的网络和磁盘系统接口。 (2) Io_uring 提供了 3 个系统调用 API 1 io_uring_setup int io_uring_setup(unsigned entries, struct io_uring_params *params); entries: queue depth,表示队列深度。 io_uring_params: 初始化时候的参数。 2 3 Io_uring_enter int io_uring_enter(unsigned int fd, u32 to_submit, u32 min_complete, u32 flags); io_uring_enter 用于提交 io 请求,收割完成好的 io 操作。 io_uring_register int io_uring_register(unsigned int fd, unsigned int opcode, void * arg, unsigned int nr_args) 此接口用于设置 io_uring 的一些行为。 3. liburing 库 想使用 io_uring 框架比较繁琐,为了简化 io_uring 的使用,io_uring 的作者提供了一个 io_uring 的库 liburing,通过使用 liburing,大大减轻了繁琐的配置 liburing 的接口: int io_uring_queue_init(unsigned entries, struct io_uring *ring, unsigned flags); 用于初始化一个 io_uring 队列 void io_uring_queue_exit(struct io_uring *ring); 清理 io_uring struct io_uring_sqe *io_uring_get_sqe(struct io_uring *ring); 得到一个 sqe,用于提交 io 请求 void io_uring_prep_writev(struct io_uring_sqe *sqe, int fd,const struct iovec *iovecs, unsigned nr_vecs, off_t offset) 准备一个写请求 void io_uring_prep_readv(struct io_uring_sqe *sqe, int fd, const struct iovec *iovecs, unsigned nr_vecs, off_t offset) 准备一个读请求 int io_uring_submit(struct io_uring *ring); 提交请求 int io_uring_submit_and_wait(struct io_uring *ring, unsigned wait_nr); 获取 io 结果 4. eventloop 设计 eventloop 负责 channel 的管理,每个 channel 负责一个文件描述符 fd,提高对该 fd 的 读写,channel 允许用户设置回调函数,在完成读写等 io 操作后,eventloop 自动调用用户绑 定到 channel 上的回调函数。 Eventloop 通过 io_uring_submit_and_wait 函数实现事件分发功能 Eventloop 实现事件分发的关键代码,eventloop 通过 io_uring_wait_cqe 获取已经完成的 io 事件,然后调用对应的事件回调,即 chan->handleEvent() 5. Channel Channel 是对文件描述符的抽象,通过 channel 可以更好的管理 io 操作,eventloop 通 过 channel 完成事件分发。 Channel 即可以读也可以写,channel 的数据成员定义: 可以看到 channel 有三个回调函数 readCallback,writeCallback,listenCallback。用户设 置好回调之后,eventloop 会在对应的事件发生后调用对应的回调。 6. 测试: 测试异步文件读取 测试代码如下: void readcb(Channel* chan) { fprintf(stdout,"i am readcb\n"); char* buf = (char*)(chan->readbuf); int size = chan->res; buf[size] = '\0'; // fprintf(stdout,"size=%d\n",size); fprintf(stdout,"%s\n",buf); fflush(stdout); } int main(int argc, char* argv[]) { Eventloop loop; int fd = open("./test/data.txt",O_RDONLY); Channel chan(fd,&loop); char* buf = new char[1024]; chan.read(100,buf,readcb); loop.loop(); delete buf; } Main 函数中读取一个文件 data.txt 的内容,并打印到 console 上。 运行结果 Data.txt 的内容: 附录: #ifndef EVENTLOOP #define EVENTLOOP #include <liburing.h> #include "Channel.hpp" #include "ServerChannel.h" #include <vector> #include <memory> #define QUEUE_SIZE 1024 class Channel; class ServerChannel; class Eventloop{ public: Eventloop(); void submitRead(Channel* channel); void submitWrite(Channel* channel); void submitServerRead(Channel* channel); void addChannel(int fd); void loop(); ~Eventloop(); // data member std::vector<std::shared_ptr<Channel>> channel_list_; struct io_uring ring_; }; #endif #include #include #include #include #include "Eventloop.h" <errno.h> <stdio.h> <liburing.h> <string.h> Eventloop::Eventloop() { io_uring_queue_init(QUEUE_SIZE,&ring_,0); } void Eventloop::submitRead(Channel* chan) { io_uring_sqe* sqe = io_uring_get_sqe(&ring_); io_uring_sqe_set_data(sqe,chan); io_uring_prep_read(sqe,chan->fd_,chan->readbuf,chan->readsize,0); io_uring_submit(&ring_); } void Eventloop::submitWrite(Channel* chan) { io_uring_sqe* sqe = io_uring_get_sqe(&ring_); io_uring_sqe_set_data(sqe,chan); io_uring_prep_write(sqe,chan->fd_,chan->writebuf,chan->writesize,-1) ; io_uring_submit(&ring_); } Eventloop::~Eventloop() { io_uring_queue_exit(&ring_); } void Eventloop::loop() { struct io_uring_cqe* cqe; int ret=0; while(true) { if((ret=io_uring_wait_cqe(&ring_, &cqe)!=0)) { fprintf(stderr,"io_uring_wait_cqe failed:%s", strerror(ret)); } Channel* chan = static_cast<Channel*>(io_uring_cqe_get_data(cqe)); // fprintf(stdout,"cqe->res=%d\n",cqe->res); chan->handleEvent(cqe->res); io_uring_cqe_seen(&ring_,cqe); // io_uring_submit(&ring_); } } void Eventloop::submitServerRead(Channel* chan) { io_uring_sqe* sqe = io_uring_get_sqe(&ring_); io_uring_sqe_set_data(sqe,chan); io_uring_prep_accept(sqe,chan->fd_,nullptr,0,0); io_uring_submit(&ring_); } void Eventloop::addChannel(int fd) { std::shared_ptr<Channel> ptr = std::make_shared<Channel>(fd,this); this->channel_list_.push_back(ptr); } #ifndef CHANNEL #define CHANNEL #include <liburing.h> #include <vector> #include <functional> #include "Eventloop.h" class Eventloop; class Channel { public: Channel(int fd, Eventloop* loop); void setReadCallback(std::function<void(Channel*)> callback) { this->readCallback = callback; } void setWriteCallback(std::function<void(Channel*)> callback) { this->writeCallback = callback; } void setListenCallback(std::function<void(Channel*)> callback) { this->listenCallback = callback; } void handleEvent(int res); void setReadState(); void setWriteState(); void setListenState() { this->state = LISTEN; } void write(void* buf,int size,std::function<void(Channel*)> cb); void read(int size,void* buf,std::function<void(Channel*)> cb); void accept(); // data member enum State{READ,WRITE,LISTEN}; State state; int fd_; Eventloop* loop_; void* readbuf; int readsize; void* writebuf; int writesize; int res; std::function<void(Channel*)> readCallback; std::function<void(Channel*)> writeCallback; std::function<void(Channel*)> listenCallback; }; #endif #include "Channel.hpp" #include "Eventloop.h" #include <stdio.h> #include <stdlib.h> Channel::Channel(int fd, Eventloop* loop) { fd_ = fd; loop_ = loop; } void Channel::setReadState() { state = READ; } void Channel::setWriteState() { state = WRITE; } void Channel::handleEvent(int res) { // fprintf(stdout,"channel::handleEvent:res = %d\n",res); if (res < 0) { fprintf(stderr, "Async readq failed.\n"); exit(1); } this->res = res; switch(state) { case READ: readCallback(this); break; case WRITE: writeCallback(this); break; case LISTEN: listenCallback(this); default: ; } } void Channel::write(void* buf, int size,std::function<void(Channel*)> cb) { writebuf = buf; writesize = size; loop_->submitWrite(this); writeCallback = cb; setWriteState(); } void Channel::read(int size,void* buf,std::function<void(Channel*)> cb) { this->readbuf = buf; this->readsize = size; this->readCallback = cb; this->loop_->submitRead(this); setReadState(); } void Channel::accept() { loop_->submitServerRead(this); setListenState(); } #include #include #include #include <iostream> "Eventloop.h" <unistd.h> <stdio.h> void readcb(Channel* chan) { fprintf(stdout,"i am readcb\n"); char* buf = (char*)(chan->readbuf); int size = chan->res; buf[size] = '\0'; // fprintf(stdout,"size=%d\n",size); fprintf(stdout,"%s\n",buf); fflush(stdout); } int main(int argc, char* argv[]) { Eventloop loop; int fd = open("./test/data.txt",O_RDONLY); Channel chan(fd,&loop); char* buf = new char[1024]; chan.read(100,buf,readcb); loop.loop(); delete buf; }