欢迎光临
我们一直在努力

网络通信 --- poll及epoll相关函数接口/三种方式的区别 --- Linux

1、区别(select/poll/epoll)

特性selectpollepoll
文件描述符上限 监听的文件描述符集合是一个数组,是有上限的(默认 1024,由 FD_SETSIZE 定义) poll监听的事件集合在链表中,没有上限的限制(基于链表存储) 没有文件描述符上限的限制(仅受系统最大文件描述符限制)
数据拷贝开销 每次调用都需把 fd 集合从用户态拷贝到内核态;返回后需遍历所有 fd 同 select(拷贝链表,返回后遍历所有 fd) 仅在 epoll_ctl 时拷贝 fd 到内核态,epoll_wait 仅返回就绪 fd,无大量拷贝
触发模式

只能工作在水平触发模式(低速模式)

仅水平触发(LT)

仅水平触发(LT)

epoll可以工作在水平触发(默认)和边沿触发模式(EPOLLET)

默认水平触发(LT),支持边沿触发(ET)

就绪 fd 查找方式 遍历所有监听的 fd,效率低 遍历所有监听的 fd,效率低 直接返回就绪 fd 列表,无需遍历,效率高
内核数据结构 固定大小的 fd_set 数组 动态链表(struct pollfd 数组) 红黑树(存储监听 fd)+ 就绪链表(存储就绪 fd)
重复监听 每次调用需重新传入完整的 fd 集合 每次调用需重新传入 pollfd 数组 无需重复传入,fd 会一直保存在内核事件表中(除非主动删除)

2、相关函数接口

2.1 poll 函数

  • 函数原型:

#include <poll.h> // 必须包含的头文件

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

  • 功能:监听多个文件描述符上的可读、可写、异常事件。
  • 参数:
    • fds:struct pollfd 数组首地址,结构体定义:
      • 如果 fd 字段为负数,该条目被忽略;revents 会被设置为 0

  • struct pollfd {
    int fd; // 要监听的文件描述符,若为-1则忽略该元素
    short events; // 要监听的事件(输入),如POLLIN(可读)、POLLOUT(可写)、POLLERR(异常)
    short revents; // 实际发生的事件(输出),由内核填充
    };

    • nfds:数组中有效元素的个数(类型 nfds_t 是无符号整型)。
    • timeout:超时时间(毫秒),-1 永久阻塞,0 立即返回,>0 等待指定毫秒。
  • 返回值:
    • 成功:返回就绪的文件描述符个数;
    • 失败:返回 -1(并设置 errno);
    • 超时:返回 0;-1: 一直阻塞直到事件发生

2.2 epoll相关函数

2.2.1 epoll_create

  • 函数原型:

#include <sys/epoll.h> // 必须包含的头文件

// 1. 创建epoll实例(内核事件表)
int epoll_create(int size);

  • 功能:创建一个 epoll 实例,返回对应的文件描述符,内核会为该实例维护红黑树(监听 fd)和就绪链表。创建实例(空红黑树 + 空就绪链表)

* 内核会为该实例维护:
* 1. 红黑树:存放所有通过 epoll_ctl() 添加的 fd
* 2. 就绪链表:存放有事件发生的 fd(由内核自动填充)
*
* 使用完后必须 close(epfd) 释放资源

  • 参数:
    • size:Linux 2.6.8 后该参数无意义(仅需传入大于 0 的整数),早期用于提示内核事件表的初始大小。
  • 返回值:
    • 成功:返回 epoll 实例的文件描述符(非负整数);
    • 失败:返回 -1(设置 errno)。

2.2.2 epoll_ctl

  • 函数原型:

// 2. 操作epoll事件表(添加/修改/删除事件)
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

  • 功能:向 epoll 实例的事件表中添加、修改或删除目标文件描述符的监听事件。
  • 参数:
    • epfd:epoll_create 返回的实例文件描述符;
    • op:操作类型:
      • EPOLL_CTL_ADD:添加 fd 的监听事件(fd 不能已存在于事件表);
      • EPOLL_CTL_MOD:修改 fd 的监听事件(fd 必须已在事件表);
      • EPOLL_CTL_DEL:删除 fd 的监听事件(event 参数可设为 NULL);
    • fd:要监听的文件描述符;
    • event:监听的事件结构体,定义:
      • struct epoll_event {
        uint32_t events; // 监听的事件,如EPOLLIN(可读)、EPOLLOUT(可写)、EPOLLET(边沿触发)
        epoll_data_t data;// 关联的数据,通常存放fd,结构体定义:
        // typedef union epoll_data {
        // void *ptr;
        // int fd;
        // uint32_t u32;
        // uint64_t u64;
        // } epoll_data_t;
        };

  • 返回值:
    • 成功:返回 0;
    • 失败:返回 -1(设置 errno)。

2.2.3 epoll_wait

  • 函数原型:

// 3. 等待epoll事件就绪
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

  • 功能:等待 epoll 实例中监听的 fd 有事件就绪,将就绪事件填充到events数组中。
  • 参数:
    • epfd:epoll 实例的文件描述符;
    • events:用户态数组,用于接收内核返回的就绪事件(输出参数);
    • maxevents:events数组的大小(必须大于 0);
    • timeout:超时时间(毫秒),-1 永久阻塞,0 立即返回,>0 等待指定毫秒。
  • 返回值:
    • 成功:返回就绪的文件描述符个数;
    • 失败:返回 -1(设置 errno);
    • 超时:返回 0。

3、 水平触发(LT)vs 边沿触发(ET)

  • LT(默认):只要 fd 有数据可读 / 可写,epoll_wait 会持续返回该 fd 的就绪事件;
  • ET:仅当 fd 的状态发生变化时(如从无数据到有数据),epoll_wait 才返回就绪事件(效率更高,但需一次性读完 / 写完数据,否则会丢失事件)。

4、epoll 的优势场景

  • 适合高并发、长连接场景(如服务器),当监听的 fd 数量多但就绪 fd 少的时候,epoll 效率远高于 select/poll;
  • select/poll 适合监听 fd 数量少且大部分 fd 都就绪的场景(如简单的本地工具)。
  • 资源释放:
    • epoll_create 返回的文件描述符需用close()关闭,否则会造成内核资源泄漏;
    • poll/select 无需额外释放资源,调用结束后即可复用 fd 集合。
赞(0)
未经允许不得转载:171主机测评 » 网络通信 --- poll及epoll相关函数接口/三种方式的区别 --- Linux
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址