io_uring中recv_multishot结合缓冲区环调用返回-EINVAL的问题求助及实现咨询
io_uring中recv_multishot结合缓冲区环调用返回-EINVAL的问题求助及实现咨询
我最近在尝试用io_uring的recv_multishot结合缓冲区环(buffer ring)实现UDP套接字的批量接收,遇到了一个棘手的问题:单shot版本的recv能正常工作,但multishot版本始终返回-EINVAL错误。我怀疑是缓冲区环的配置出了问题,但对比了不少类似代码片段后还是没找到问题所在。
环境信息
- Linux内核版本:6.8
- liburing版本:2.5
- 系统:Ubuntu 24.04
测试代码
#include <arpa/inet.h> #include <liburing.h> #include <stdio.h> #include <sys/socket.h> #include <sys/types.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #define BUF_BGID 0 #define N_BUFS 128 #define PAGESIZE 4096 #define BUFSIZE 8192 #define PORT 49000 int main(int argc, char *argv[]) { struct sockaddr_in addr; int sock_fd; char* bufs; // All bufs go here int i; struct io_uring ring; struct io_uring_buf_ring* br = NULL; struct io_uring_sqe *sqe; struct io_uring_cqe* cqe; /* Set up socket */ addr.sin_family = AF_INET; addr.sin_port = htons(PORT); inet_aton("0.0.0.0", &addr.sin_addr); /* Create UDP socket */ sock_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (sock_fd < 0) { fprintf(stderr, "Could not create socket.\n"); return 1; } if (bind(sock_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { fprintf(stderr, "Could not bind socket.\n"); return 1; } /* Create uring */ if (io_uring_queue_init(N_BUFS, &ring, 0) < 0) { fprintf(stderr, "Could not init ring.\n"); return 1; } /* Create buffers and buffer ring */ if (posix_memalign((void**)&bufs, PAGESIZE, BUFSIZE * N_BUFS)) { fprintf(stderr, "Could not allocate memory.\n"); return 1; } memset((void*)bufs, 0, BUFSIZE * N_BUFS); int err = 0; br = io_uring_setup_buf_ring(&ring, N_BUFS, BUF_BGID, 0, &err); if (!br) { fprintf(stderr, "Could not create buffer ring. Error code: %d\n", err); return 1; } io_uring_buf_ring_init(br); for (i = 0; i < N_BUFS; i++) { io_uring_buf_ring_add(br, bufs + i * BUFSIZE, BUFSIZE, i, io_uring_buf_ring_mask(N_BUFS), i); } io_uring_buf_ring_advance(br, N_BUFS); /* Set up recv SQE */ sqe = io_uring_get_sqe(&ring); io_uring_sqe_set_flags(sqe, IOSQE_BUFFER_SELECT); sqe->buf_group = BUF_BGID; io_uring_prep_recv_multishot(sqe, sock_fd, NULL, 0, 0); /* This doesn't work! */ /*io_uring_prep_recv(sqe, sock_fd, bufs, BUFSIZE, 0);*/ /* This works! */ if (io_uring_submit(&ring) < 0) { fprintf(stderr, "Failed to submit SQE.\n"); return 1; } if (io_uring_wait_cqe(&ring, &cqe) < 0) { fprintf(stderr, "Could not get CQE.\n"); return 1; } if (cqe->res < 0) { fprintf(stderr, "Bad CQE result: %s\n", strerror(-cqe->res)); return 1; } fprintf(stderr, "Buffer index: %d\n", cqe->flags >> IORING_CQE_BUFFER_SHIFT); /* Cleanup */ free(bufs); io_uring_queue_exit(&ring); close(sock_fd); return 0; }
问题核心
- 单shot的
io_uring_prep_recv可以正常接收UDP数据包 - 切换到
io_uring_prep_recv_multishot后,CQE返回-EINVAL错误 - 已确认socket绑定正常,缓冲区环的内存分配、初始化流程看起来和示例代码一致
问题分析与解决方案
经过查核io_uring内核文档和opcode定义,发现了问题的根源:
IORING_OP_RECV_MULTISHOT仅支持流式套接字(如TCP),不支持数据报套接字(如UDP)。虽然普通的recv系统调用可以兼容UDP套接字,但multishot版本的RECV opcode被内核限制为仅流式使用,因此传入UDP套接字时会返回-EINVAL。
对于UDP这类数据报套接字,应该使用**IORING_OP_RECVFROM_MULTISHOT**对应的io_uring_prep_recvfrom_multishot函数。即使你不需要获取源地址信息,也需要使用这个opcode,同时可以传入NULL地址参数来忽略源地址。
修改后的SQE设置代码如下:
/* Set up recvfrom_multishot SQE */ sqe = io_uring_get_sqe(&ring); io_uring_sqe_set_flags(sqe, IOSQE_BUFFER_SELECT); sqe->buf_group = BUF_BGID; // 使用recvfrom_multishot,传入NULL忽略源地址 io_uring_prep_recvfrom_multishot(sqe, sock_fd, NULL, 0, 0, NULL, NULL);
另外补充两个小建议:
- 所有系统调用(socket、bind、io_uring_setup_buf_ring等)都应该添加错误检查,比如
io_uring_setup_buf_ring返回NULL时,打印err变量的值可以帮助快速定位问题 - 程序退出时记得释放内存、关闭文件描述符和销毁io_uring环(已在修改后的代码中补充)
修改后重新编译运行,multishot接收应该就能正常工作了。
内容来源于stack exchange
相关产品推荐
相关产品推荐

