土法炼钢兴趣小组的算法知识备份

【io_uring 系列】实战:基于 io_uring 的 TCP Echo Server

源码下载

本文相关源码已整理,共 1 个文件。

打开下载目录 →

目录

在上一篇中,我们学会了如何用 io_uring 读取文件。今天,我们将进入更复杂的领域:网络编程。我们将实现一个经典的 TCP Echo Server:它接收客户端连接,读取发送的数据,然后原样返回。

1. 异步网络编程模型

与同步阻塞模型(One Thread Per Connection)或 epoll Reactor 模型不同,io_uring 的网络编程更像是一个状态机

我们需要维护每个连接的状态,并通过 user_data 在提交(Submission)和完成(Completion)之间传递上下文。

1.1 状态流转图

Echo Server State Diagram

2. 核心设计

2.1 上下文管理 (Context)

epoll 中,我们通常用 epoll_data.ptr 指向一个结构体。在 io_uring 中,我们使用 io_uring_sqe_set_dataio_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 结构
};

2.2 链式处理 (Chaining)

异步编程的核心在于在回调中发起下一个异步操作

  1. Accept 完成时
  1. Read 完成时
  1. Write 完成时

3. 代码实现

下面是 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

4. 内存管理注意事项

在示例代码中,为了简化逻辑,我们在每次请求时都 malloc 一个新的 conn_info,并在处理完 CQE 后 free 掉。

在高并发生产环境中,频繁的 malloc/free 是不可接受的。通常的优化策略包括: 1. 内存池 (Memory Pool):预分配大量的 conn_info。 2. 嵌入式结构:将 conn_info 嵌入到连接对象中,整个生命周期复用。 3. Provided Buffers:使用 io_uring 的高级特性 IOSQE_BUFFER_SELECT,让内核自动选择缓冲区,避免为每个连接预分配读缓冲。(我们将在下一篇详细介绍)。

5. 总结

通过这个 Echo Server,我们掌握了 io_uring 处理网络并发的基本模式。相比 epoll,我们不再需要手动调用 read/write,而是将这些操作全部交给内核。

下一篇,我们将探讨 io_uring高级特性,看看如何通过 SQPOLLFixed Buffers 进一步榨干硬件性能。

如果你想看这个 C echo server 被翻成 Rust 时编译器拦下了哪些问题,可以读用 Rust 重写一个 C 网络服务器,编译器拦了我五次


上一篇: 03-liburing-api.md - liburing 基础 API 下一篇: 05-advanced-features.md - 高级特性

返回 io_uring 系列索引


By .