io_uring是一个Linux内核的异步I/O框架,它提供了高性能的异步I/O操作,io_uring的目标是通过减少系统调用和上下文切换的开销来提高I/O操作的性能。
在网络编程中,我们通常使用epoll IO多路复用来处理网络IO,然而epoll也并不是异步网络IO,仅仅是内核提供了IO复用机制,epoll回调通知的是数据可以读取或者写入了,具体的读写操作仍然需要用户去做,而不是内核代替完成。
io_uring原理介绍:
kernel_new_features/io_uring/文章/浅析开源项目之io_uring.md at main · 0voice/kernel_new_features · GitHubi
git clone https://github.com/axboe/liburing.git
./configure
make
make install
#include
#include
#include
#include
#include
#include
#define EVENT_ACCEPT 0
#define EVENT_READ 1
#define EVENT_WRITE 2
#define ENTRIES_LENGTH 1024
#define BUFFER_LENGTH 1024
struct conn_info {
int fd;
int event;
char buf[BUFFER_LENGTH];
};
int init_server(unsigned short port) {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(struct sockaddr_in));
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
serveraddr.sin_port = htons(port);
if (-1 == bind(sockfd, (struct sockaddr*)&serveraddr, sizeof(struct sockaddr))) {
perror("bind");
return -1;
}
listen(sockfd, 10);
return sockfd;
}
int set_event_recv(struct io_uring *ring,
struct conn_info *info, int flags) {
struct io_uring_sqe *sqe = io_uring_get_sqe(ring);
info->event = EVENT_READ;
io_uring_prep_recv(sqe, info->fd, info->buf, BUFFER_LENGTH, flags);
io_uring_sqe_set_data(sqe, info);
}
int set_event_send(struct io_uring *ring,
struct conn_info *info, size_t len, int flags) {
struct io_uring_sqe *sqe = io_uring_get_sqe(ring);
info->event = EVENT_WRITE;
io_uring_prep_send(sqe, info->fd, info->buf, len, flags);
io_uring_sqe_set_data(sqe, info);
}
int set_event_accept(struct io_uring *ring, struct conn_info *info,
struct sockaddr *addr, socklen_t *addrlen, int flags) {
struct io_uring_sqe *sqe = io_uring_get_sqe(ring);
info->event = EVENT_ACCEPT;
io_uring_prep_accept(sqe, info->fd, (struct sockaddr*)addr, addrlen, flags);
io_uring_sqe_set_data(sqe, info);
}
int main(int argc, char *argv[]) {
unsigned short port = 9999;
int sockfd = init_server(port);
struct io_uring_params params;
memset(¶ms, 0, sizeof(params));
struct io_uring ring;
io_uring_queue_init_params(ENTRIES_LENGTH, &ring, ¶ms);
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
struct conn_info *accept_info = malloc(sizeof(struct conn_info));
memset(accept_info, 0, sizeof(struct conn_info));
accept_info->fd = sockfd;
accept_info->event = EVENT_ACCEPT;
set_event_accept(&ring, accept_info, (struct sockaddr*)&clientaddr, &len, 0);
while (1) {
io_uring_submit(&ring);
struct io_uring_cqe *cqe;
io_uring_wait_cqe(&ring, &cqe);
struct io_uring_cqe *cqes[128];
int nready = io_uring_peek_batch_cqe(&ring, cqes, 128); // epoll_wait
int i = 0;
for (i = 0;i < nready;i ++) {
struct io_uring_cqe *entries = cqes[i];
struct conn_info *result = io_uring_cqe_get_data(entries);
if (result->event == EVENT_ACCEPT) {
set_event_accept(&ring, result, (struct sockaddr*)&clientaddr, &len, 0);
//printf("set_event_accept\n"); //
int connfd = entries->res;
struct conn_info *info = malloc(sizeof(struct conn_info));
memset(info, 0, sizeof(struct conn_info));
info->fd = connfd;
set_event_recv(&ring, info, 0);
} else if (result->event == EVENT_READ) { //
int ret = entries->res;
//printf("set_event_recv ret: %d, %s\n", ret, result->buf); //
if (ret == 0) {
close(result->fd);
} else if (ret > 0) {
set_event_send(&ring, result, ret, 0);
}
} else if (result->event == EVENT_WRITE) {
//
int ret = entries->res;
//printf("set_event_send ret: %d, %s\n", ret, result->buf);
set_event_recv(&ring, result, 0);
}
}
io_uring_cq_advance(&ring, nready);
}
}
运行环境内核版本:Linux version 5.15.0-119-generic,尽量高点,不然容易报错
测试:客户端建立50个连接,发送1百万个请求,测试QPS
收发包框架\数据包大小 | 64 | 128 | 256 | 512 | 1024 |
io_uring | 84012 | 82345 | 80308 | 82624 | 81752 |
epoll | 74145 | 73561 | 73866 | 74940 | 72358 |
io_uring qps比epoll块10%左右