2025-11-30 · linux / io_uring
【io_uring 系列】liburing 基础 API 详解:从 Hello World 到文件 I/O
手把手教你使用 liburing 编写第一个 io_uring 程序。详解 io_uring_queue_init, io_uring_submit 等核心 API 的使用流程。
在上一篇中,我们学会了如何用 io_uring
读取文件。今天,我们将进入更复杂的领域:网络编程。我们将实现一个经典的
TCP Echo
Server:它接收客户端连接,读取发送的数据,然后原样返回。
与同步阻塞模型(One Thread Per Connection)或
epoll Reactor 模型不同,io_uring
的网络编程更像是一个状态机。
我们需要维护每个连接的状态,并通过 user_data
在提交(Submission)和完成(Completion)之间传递上下文。
在 epoll 中,我们通常用
epoll_data.ptr 指向一个结构体。在
io_uring 中,我们使用
io_uring_sqe_set_data 和
io_uring_cqe_get_data。
我们需要定义一个结构体来区分当前完成的事件类型:
enum {
EVENT_ACCEPT,
EVENT_READ,
EVENT_WRITE
};
struct conn_info {
int fd; // 文件描述符
int type; // 事件类型
char buf[1024]; // 数据缓冲区
struct iovec iov; // io_uring 需要的 iovec 结构
};异步编程的核心在于在回调中发起下一个异步操作。
client_fd。io_uring_prep_read(client_fd)。io_uring_prep_accept(server_fd),否则服务器将无法接受新连接。res > 0:发起
io_uring_prep_write(client_fd)
将数据回写。res <= 0:关闭连接
close(client_fd)。io_uring_prep_read(client_fd)
等待下一条消息。下面是 02-echo-server.c 的核心逻辑。
/* examples/io_uring/02-echo-server.c */
// ... (头文件与结构体定义) ...
int main() {
// ... (Socket 初始化与 bind/listen) ...
// 初始化 io_uring
struct io_uring ring;
io_uring_queue_init(4096, &ring, 0);
// 提交第一个 Accept 请求
add_accept_request(&ring, server_fd, ...);
io_uring_submit(&ring);
while (1) {
struct io_uring_cqe *cqe;
io_uring_wait_cqe(&ring, &cqe);
struct conn_info *user_data = (struct conn_info *)io_uring_cqe_get_data(cqe);
if (user_data->type == EVENT_ACCEPT) {
int client_fd = cqe->res;
// 1. 为新连接准备 Read
add_read_request(&ring, client_fd);
// 2. 重新 Arm Accept
add_accept_request(&ring, server_fd, ...);
}
else if (user_data->type == EVENT_READ) {
int bytes = cqe->res;
if (bytes <= 0) {
close(user_data->fd); // 断开
} else {
// 收到数据,准备 Write (Echo)
add_write_request(&ring, user_data->fd, user_data->buf, bytes);
}
}
else if (user_data->type == EVENT_WRITE) {
// 发送完毕,继续 Read
add_read_request(&ring, user_data->fd);
}
// 释放旧的 user_data (注意内存管理策略)
free(user_data);
io_uring_cqe_seen(&ring, cqe);
io_uring_submit(&ring);
}
}完整代码: 02-echo-server.c
在示例代码中,为了简化逻辑,我们在每次请求时都
malloc 一个新的
conn_info,并在处理完 CQE 后 free
掉。
在高并发生产环境中,频繁的 malloc/free
是不可接受的。通常的优化策略包括: 1. 内存池 (Memory
Pool):预分配大量的 conn_info。 2.
嵌入式结构:将 conn_info
嵌入到连接对象中,整个生命周期复用。 3. Provided
Buffers:使用 io_uring 的高级特性
IOSQE_BUFFER_SELECT,让内核自动选择缓冲区,避免为每个连接预分配读缓冲。(我们将在下一篇详细介绍)。
通过这个 Echo Server,我们掌握了 io_uring
处理网络并发的基本模式。相比
epoll,我们不再需要手动调用
read/write,而是将这些操作全部交给内核。
下一篇,我们将探讨 io_uring
的高级特性,看看如何通过
SQPOLL 和 Fixed Buffers
进一步榨干硬件性能。
如果你想看这个 C echo server 被翻成 Rust 时编译器拦下了哪些问题,可以读用 Rust 重写一个 C 网络服务器,编译器拦了我五次。
上一篇: 03-liburing-api.md - liburing 基础 API 下一篇: 05-advanced-features.md - 高级特性
把当前热点继续串成多页阅读,而不是停在单篇消费。
2025-11-30 · linux / io_uring
手把手教你使用 liburing 编写第一个 io_uring 程序。详解 io_uring_queue_init, io_uring_submit 等核心 API 的使用流程。
2025-11-30 · linux / io_uring
深入解析 Linux io_uring 的核心机制:提交队列 (SQ)、完成队列 (CQ) 与零拷贝技术,理解它如何重塑高性能网络编程。
2025-11-30 · linux / io_uring
全方位对比 io_uring 与 epoll:从系统调用开销、内存管理到编程模型,分析 io_uring 在高性能 I/O 场景中的优势与局限。
2025-11-30 · linux / io_uring
深入探讨 io_uring 的进阶功能:SQPOLL (零系统调用)、Fixed Files (减少引用计数开销) 和 Provided Buffers (自动缓冲区管理)。
在上一篇中,我们学会了如何用 io_uring
读取文件。今天,我们将进入更复杂的领域:网络编程。我们将实现一个经典的
TCP Echo
Server:它接收客户端连接,读取发送的数据,然后原样返回。
与同步阻塞模型(One Thread Per Connection)或
epoll Reactor 模型不同,io_uring
的网络编程更像是一个状态机。
我们需要维护每个连接的状态,并通过 user_data
在提交(Submission)和完成(Completion)之间传递上下文。
在 epoll 中,我们通常用
epoll_data.ptr 指向一个结构体。在
io_uring 中,我们使用
io_uring_sqe_set_data 和
io_uring_cqe_get_data。
我们需要定义一个结构体来区分当前完成的事件类型:
enum {
EVENT_ACCEPT,
EVENT_READ,
EVENT_WRITE
};
struct conn_info {
int fd; // 文件描述符
int type; // 事件类型
char buf[1024]; // 数据缓冲区
struct iovec iov; // io_uring 需要的 iovec 结构
};异步编程的核心在于在回调中发起下一个异步操作。
client_fd。io_uring_prep_read(client_fd)。io_uring_prep_accept(server_fd),否则服务器将无法接受新连接。res > 0:发起
io_uring_prep_write(client_fd)
将数据回写。res <= 0:关闭连接
close(client_fd)。io_uring_prep_read(client_fd)
等待下一条消息。下面是 02-echo-server.c 的核心逻辑。
/* examples/io_uring/02-echo-server.c */
// ... (头文件与结构体定义) ...
int main() {
// ... (Socket 初始化与 bind/listen) ...
// 初始化 io_uring
struct io_uring ring;
io_uring_queue_init(4096, &ring, 0);
// 提交第一个 Accept 请求
add_accept_request(&ring, server_fd, ...);
io_uring_submit(&ring);
while (1) {
struct io_uring_cqe *cqe;
io_uring_wait_cqe(&ring, &cqe);
struct conn_info *user_data = (struct conn_info *)io_uring_cqe_get_data(cqe);
if (user_data->type == EVENT_ACCEPT) {
int client_fd = cqe->res;
// 1. 为新连接准备 Read
add_read_request(&ring, client_fd);
// 2. 重新 Arm Accept
add_accept_request(&ring, server_fd, ...);
}
else if (user_data->type == EVENT_READ) {
int bytes = cqe->res;
if (bytes <= 0) {
close(user_data->fd); // 断开
} else {
// 收到数据,准备 Write (Echo)
add_write_request(&ring, user_data->fd, user_data->buf, bytes);
}
}
else if (user_data->type == EVENT_WRITE) {
// 发送完毕,继续 Read
add_read_request(&ring, user_data->fd);
}
// 释放旧的 user_data (注意内存管理策略)
free(user_data);
io_uring_cqe_seen(&ring, cqe);
io_uring_submit(&ring);
}
}完整代码: 02-echo-server.c
在示例代码中,为了简化逻辑,我们在每次请求时都
malloc 一个新的
conn_info,并在处理完 CQE 后 free
掉。
在高并发生产环境中,频繁的 malloc/free
是不可接受的。通常的优化策略包括: 1. 内存池 (Memory
Pool):预分配大量的 conn_info。 2.
嵌入式结构:将 conn_info
嵌入到连接对象中,整个生命周期复用。 3. Provided
Buffers:使用 io_uring 的高级特性
IOSQE_BUFFER_SELECT,让内核自动选择缓冲区,避免为每个连接预分配读缓冲。(我们将在下一篇详细介绍)。
通过这个 Echo Server,我们掌握了 io_uring
处理网络并发的基本模式。相比
epoll,我们不再需要手动调用
read/write,而是将这些操作全部交给内核。
下一篇,我们将探讨 io_uring
的高级特性,看看如何通过
SQPOLL 和 Fixed Buffers
进一步榨干硬件性能。
如果你想看这个 C echo server 被翻成 Rust 时编译器拦下了哪些问题,可以读用 Rust 重写一个 C 网络服务器,编译器拦了我五次。
上一篇: 03-liburing-api.md - liburing 基础 API 下一篇: 05-advanced-features.md - 高级特性
把当前热点继续串成多页阅读,而不是停在单篇消费。
2025-11-30 · linux / io_uring
手把手教你使用 liburing 编写第一个 io_uring 程序。详解 io_uring_queue_init, io_uring_submit 等核心 API 的使用流程。
2025-11-30 · linux / io_uring
深入解析 Linux io_uring 的核心机制:提交队列 (SQ)、完成队列 (CQ) 与零拷贝技术,理解它如何重塑高性能网络编程。
2025-11-30 · linux / io_uring
全方位对比 io_uring 与 epoll:从系统调用开销、内存管理到编程模型,分析 io_uring 在高性能 I/O 场景中的优势与局限。
2025-11-30 · linux / io_uring
深入探讨 io_uring 的进阶功能:SQPOLL (零系统调用)、Fixed Files (减少引用计数开销) 和 Provided Buffers (自动缓冲区管理)。