传统的 Linux 网络编程依赖 epoll,每次 I/O 事件触发后还需要调用 read/write 等系统调用去真正读写数据。每次系统调用都要在用户态和内核态之间切换,连接数一高,这部分开销就不可忽视。
io_uring 是 Linux 5.1 引入的异步 I/O 框架,核心思路是:把要做的事提交进去,等内核做完再来取结果,整个过程通过共享内存的环形队列通信,能大幅减少系统调用次数。
一.核心数据结构
io_uring 有两个环形队列:
SQ(Submission Queue)提交队列:用户把要做的操作(accept/recv/send)写进这里。
CQ(Completion Queue)完成队列:内核做完操作后把结果写进这里,用户来取。
每个 SQE(提交队列条目)有一个 user_data 字段,可以存任意用户数据,内核完成后原封不动地带回 CQE 里,这是事件分发的关键。
二.整体框架
代码整体分为四个部分:
1.结构体 — conn_info
//#defineEVENT_ACCEPT0#defineEVENT_READ1#defineEVENT_WRITE2structconn_info{intfd;//哪个连接intevent;//什么操作};它是贯穿整个程序的数据载体,通过 SQE 的 user_data 在提交和完成之间传递上下文。
2.初始化
包含两部分:网络层的 init_server 做 socket/bind/listen,以及 io_uring 的队列初始化和首次注册 accept:
intinit_server(unsignedshortport){if(port==0)return-1;intsockfd=socket(AF_INET,SOCK_STREAM,0);structsockaddr_insockaddr;sockaddr.sin_family=AF_INET;sockaddr.sin_addr.s_addr=htonl(INADDR_ANY);sockaddr.sin_port=htons(port);intret=bind(sockfd,(structsockaddr*)&sockaddr,sizeof(structsockaddr_in));if(ret==-1)return-2;ret=listen(sockfd,10);if(ret==-1)return-2;returnsockfd;}structio_uringring;structio_uring_paramsparams;memset(¶ms,0,sizeof(params));io_uring_queue_init_params(ENTRIES_LENGTH,&ring,¶ms);// 首次挂上 accept,等待第一个连接uring_accept(&ring,sockfd,(structsockaddr*)&clientaddr,&len,0);3.执行函数 — 三个操作封装
每个函数从 SQ 队列取一个槽位,填入操作参数,把 conn_info 写进 user_data:
uring_accept(ring,sockfd,addr,addrlen,flags);uring_recv(ring,fd,buf,len,flags);uring_send(ring,fd,buf,len,flags);4.主循环
while 循环:
submit → wait_cqe(阻塞等至少1个) → peek_batch_cqe(批量取) → for 遍历 → 按 EVENT 分发调用执行函数 → cq_advance
这里有2个关键点:
1.为什么 accept 用 res 取 fd,而 recv/send 用 result.fd呢?因为accept 产生新 fd,只能从返回值拿,而之后的recv/send 是用的已有 fd,其早就存进 user_data 了。
2.先写wait_cqe 阻塞,等至少一个完成,保证后面 peek_batch_cqe 进去的时候 CQ 里一定有东西,不会空转。
三.详细代码
#include<liburing.h>#include<stdio.h>#include<sys/socket.h>#include<arpa/inet.h>#include<string.h>#include<unistd.h>#include<netinet/in.h>// @return -1 --> init error// -2 --> return error#defineEVENT_ACCEPT0#defineEVENT_READ1#defineEVENT_WRITE2#defineENTRIES_LENGTH1024#defineDATA_LENGTH1024structconn_info{intfd;intevent;};intinit_server(unsignedshortport){if(port==0)return-1;intsockfd=socket(AF_INET,SOCK_STREAM,0);structsockaddr_insockaddr;sockaddr.sin_family=AF_INET;sockaddr.sin_addr.s_addr=htonl(INADDR_ANY);sockaddr.sin_port=htons(port);intret=bind(sockfd,(structsockaddr*)&sockaddr,sizeof(structsockaddr_in));if(ret==-1)return-2;ret=listen(sockfd,10);if(ret==-1)return-2;returnsockfd;}inturing_accept(structio_uring*ring,intsockfd,structsockaddr*addr,socklen_t*addrlen,intflags){structio_uring_sqe*sqe=io_uring_get_sqe(ring);if(sqe==NULL)return-2;io_uring_prep_accept(sqe,sockfd,(structsockaddr*)addr,addrlen,flags);structconn_infoaccept_info={.fd=sockfd,.event=EVENT_ACCEPT,};memcpy(&sqe->user_data,&accept_info,sizeof(structconn_info));return0;}inturing_recv(structio_uring*ring,intfd,void*buf,size_tlen,intflags){structio_uring_sqe*sqe=io_uring_get_sqe(ring);if(sqe==NULL)return-2;io_uring_prep_recv(sqe,fd,buf,len,flags);structconn_inforecv_info={.fd=fd,.event=EVENT_READ,};memcpy(&sqe->user_data,&recv_info,sizeof(structconn_info));return0;}inturing_send(structio_uring*ring,intfd,constvoid*buf,size_tlen,intflags){structio_uring_sqe*sqe=io_uring_get_sqe(ring);if(sqe==NULL)return-2;io_uring_prep_send(sqe,fd,buf,len,flags);structconn_infosend_info={.fd=fd,.event=EVENT_WRITE,};memcpy(&sqe->user_data,&send_info,sizeof(structconn_info));return0;}intmain(intargc,charconst*argv[]){unsignedshortport=2000;intsockfd=init_server(port);structio_uringring;structio_uring_paramsparams;memset(¶ms,0,sizeof(structio_uring_params));io_uring_queue_init_params(ENTRIES_LENGTH,&ring,¶ms);structsockaddr_inclientaddr;socklen_tlen=sizeof(clientaddr);uring_accept(&ring,sockfd,(structsockaddr*)&clientaddr,&len,0);charbuf[DATA_LENGTH]={0};while(1){io_uring_submit(&ring);//触发系统调用,内核开始异步 IOstructio_uring_cqe*cqe;io_uring_wait_cqe(&ring,&cqe);structio_uring_cqe*cqes[512];intnready=io_uring_peek_batch_cqe(&ring,cqes,128);inti=0;for(i=0;i<nready;i++){structio_uring_cqe*cqe=cqes[i];structconn_inforesult;memcpy(&result,&cqe->user_data,sizeof(structconn_info));if(result.event==EVENT_ACCEPT){uring_accept(&ring,sockfd,(structsockaddr*)&clientaddr,&len,0);intconnfd=cqe->res;uring_recv(&ring,connfd,buf,DATA_LENGTH,0);}elseif(result.event==EVENT_READ){intret=cqe->res;if(ret<0){close(result.fd);continue;}elseif(ret==0){close(result.fd);}else{uring_send(&ring,result.fd,buf,DATA_LENGTH,0);printf("send: %d, %s\n",ret,buf);}}elseif(result.event==EVENT_WRITE){uring_recv(&ring,result.fd,buf,DATA_LENGTH,0);}}io_uring_cq_advance(&ring,nready);}return0;}四.流程图
零声社区资源链接:https:github.com/0voice