Linux学习-TCP并发服务器构建(epoll)

发布于:2025-08-29 ⋅ 阅读:(16) ⋅ 点赞:(0)

一、核心特点(对比 select、poll 优势)

  1. 存储结构:用红黑树存文件描述符集合,无数量上限,查找高效
  2. 数据拷贝:集合创建在内核层,避免应用层 - 内核层反复拷贝,降低开销
  3. 事件处理:返回到达事件,无需遍历,直接处理,效率高
  4. 触发模式:支持水平触发(低速)和边沿触发(高速),适配不同场景

二、操作流程与关键函数

  1. 创建集合int epoll_create(int size)
    • 功能:通知内核创建文件描述符集合
    • 参数:size 填监测的文件描述符预期数量(实际无严格限制,传正整数即可 )
    • 返回:成功返回集合的文件描述符(用于后续操作),失败返回 -1
  2. 管理集合(增删改)int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
    • 功能:操作 epoll 集合(添加、修改、删除文件描述符及事件)
    • 参数:
      • epfdepoll_create 返回的集合描述符
      • op:操作类型,如 EPOLL_CTL_ADD(添加)、EPOLL_CTL_MOD(修改)、EPOLL_CTL_DEL(删除)
      • fd:要操作的文件描述符
      • event:事件结构体,含 events(事件类型,如 EPOLLIN 读、EPOLLOUT 写 )和 data(关联数据,常用 data.fd 存关注的文件描述符 )
    • 返回:成功返回 0,失败返回 -1
  3. 监测事件int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
    • 功能:通知内核开始监测事件,阻塞等待事件触发
    • 参数:
      • epfd:要监测的集合描述符
      • events:数组,用于存返回的到达事件结果
      • maxevents:最多处理的事件个数(需 ≤ events 数组大小 )
      • timeout:超时时间,-1 表示一直阻塞,0 立即返回,正数为毫秒级超时
    • 返回:成功返回触发的事件数量,失败返回 -1
  4. 事件类型与数据结构
    • epoll_data_t 联合结构体:可存指针、文件描述符等,常用 fd 关联关注的描述符
    • struct epoll_event:含 events(事件类型标记,如 EPOLLIN/EPOLLOUT )和 dataepoll_data_t 类型数据 )

三、核心原理与数据结构

epoll 在内核中维护两个关键数据结构,共同实现高效事件管理:

  1. 红黑树(RB-Tree)

    • 用途:存储所有被监控的 FD 及其关联的事件(如 EPOLLINEPOLLOUT)。
    • 优势:插入、删除、查找 FD 的时间复杂度为 O(log n),支持动态增删大量 FD,无数量上限(仅受系统内存限制)。
  2. 就绪链表(Ready List)

    • 用途:缓存所有触发了事件的 FD(就绪 FD)。
    • 优势:epoll_wait 直接从链表中获取就绪 FD,无需遍历全部 FD,效率极高。

四、关键函数详解

1. 创建 epoll 实例:epoll_create
#include <sys/epoll.h>
int epoll_create(int size);
  • 功能:向内核申请创建一个 epoll 实例(管理 FD 集合的句柄)。

  • 参数size 是历史遗留参数,早期用于提示内核预分配空间,现代 Linux 中已忽略(只需传一个正整数即可)。

  • 返回值:成功返回 epoll 实例的文件描述符(epfd),失败返回 -1(需检查 errno)。

  • 注意epfd 本身也是一个 FD,使用完毕后需用 close(epfd) 释放资源。

2. 管理监控的 FD:epoll_ctl
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
  • 功能:向 epoll 实例添加、修改或删除被监控的 FD 及事件。

  • 参数

    • epfdepoll_create 返回的实例句柄。
    • op:操作类型:
      • EPOLL_CTL_ADD:添加 FD 到监控集合。
      • EPOLL_CTL_MOD:修改已监控 FD 的事件类型。
      • EPOLL_CTL_DEL:从监控集合中删除 FD(此时 event 可设为 NULL)。
    • fd:需要监控的文件描述符(如 socket FD)。
    • event:事件结构体,定义如下:
      struct epoll_event {
          uint32_t events;  // 事件类型(位掩码)
          epoll_data_t data;  // 关联数据(用户自定义)
      };
      
      typedef union epoll_data {
          void    *ptr;  // 指针(可关联自定义数据结构)
          int      fd;   // 常用:关联被监控的 FD 本身
          uint32_t u32;
          uint64_t u64;
      } epoll_data_t;
      
    • events 常用类型:
      • EPOLLIN:FD 可读(如 socket 收到数据)。
      • EPOLLOUT:FD 可写(如 socket 发送缓冲区空闲)。
      • EPOLLERR:FD 发生错误(无需主动设置,内核自动触发)。
      • EPOLLET:启用边沿触发模式(默认是水平触发)。
      • EPOLLONESHOT:事件触发一次后自动取消监控(需重新添加才能再次监控)。
  • 返回值:成功返回 0,失败返回 -1(如 FD 已被监控、权限不足等)。

3. 等待就绪事件:epoll_wait
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
  • 功能:阻塞等待监控集合中就绪的事件,将结果存入 events 数组。

  • 参数

    • epfdepoll 实例句柄。
    • events:用户分配的数组,用于接收就绪事件(输出参数)。
    • maxeventsevents 数组的最大长度(必须 ≥ 1)。
    • timeout:超时时间(毫秒):
      • -1:永久阻塞,直到有事件就绪。
      • 0:立即返回(非阻塞模式)。
      • >0:最多阻塞 timeout 毫秒,超时后返回 0
  • 返回值

    • 成功:返回就绪事件的数量(0 表示超时,无就绪事件)。
    • 失败:返回 -1(如被信号中断,需检查 errno)。

五、与 select/poll 的对比

特性 select poll epoll
监控 FD 数量上限 有(通常 1024) 无(受系统内存限制) 无(受系统内存限制)
事件存储 位图(固定大小) 数组(动态分配) 红黑树 + 就绪链表
事件检测方式 遍历所有 FD 遍历所有 FD 直接取就绪链表(无需遍历)
数据拷贝 每次调用拷贝全部 FD 每次调用拷贝全部 FD 仅初始化时拷贝一次
触发模式 仅水平触发 仅水平触发 水平触发 + 边沿触发
时间复杂度 O(n) O(n) O(1)(获取就绪事件)
高并发表现 差(FD 越多越慢) 较差(遍历开销大) 优秀(无遍历开销)

六、典型使用流程

  1. 创建 socket 并绑定端口,设为监听状态(listen)。
  2. 调用 epoll_create 创建 epoll 实例(epfd)。
  3. 调用 epoll_ctl 添加监听 socket 到 epoll 集合(关注 EPOLLIN 事件)。
  4. 循环调用 epoll_wait 等待就绪事件:
    • 若监听 socket 就绪(新连接),调用 accept 获取客户端 socket,添加到 epoll 集合。
    • 若客户端 socket 就绪(可读/可写),处理数据(读/写操作)。
  5. 断开连接时,调用 epoll_ctl 从集合中删除客户端 socket。

网站公告

今日签到

点亮在社区的每一天
去签到