Linux——高级IO(select后续poll,epoll)

ops/2025/2/28 10:30:39/

目录

一、poll函数

1.函数原型

2.参数说明

3.struct pollfd 结构体

4.返回值

5.使用步骤

6.与 select 的对比

7.适用场景

8.缺点

9.总结

二、epoll函数

1.核心思想

2.核心函数

1. epoll_create - 创建 epoll 实例

2. epoll_ctl - 管理 epoll 事件表

3. epoll_wait - 等待事件发生

3.事件类型

4.触发模式

1. 水平触发(LT,默认)

2. 边缘触发(ET)

5.使用步骤

6.示例代码(LT 模式)

7.优点

8.缺点

9.适用场景

10.对比 select/poll

11.注意事项

12.总结

三、当网卡接收到数据流程

1. 网卡接收数据(硬件层)

2. epoll 的事件触发(内核层)

关键数据结构

事件触发流程

3. 用户层获取事件(应用层)

流程总结(结合数据结构)

性能优势的根源

触发模式的影响

1. 水平触发(LT)

2. 边缘触发(ET)

完整流程示例

总结


一、poll函数

1.函数原型

#include <poll.h>int poll(struct pollfd *fds, nfds_t nfds, int timeout);

2.参数说明

  1. fds
    指向 struct pollfd 结构体数组的指针,每个结构体描述一个要监视的文件描述符及其关注的事件。

  2. nfds
    数组 fds 的长度(即监视的文件描述符数量)。

  3. timeout
    超时时间(毫秒):

    • -1: 阻塞等待,直到某个文件描述符就绪。

    • 0: 立即返回,不阻塞。

    • >0: 最多等待指定毫秒数。


3.struct pollfd 结构体

struct pollfd {int   fd;         // 监视的文件描述符short events;     // 关注的事件(输入)short revents;    // 实际发生的事件(输出)
};
  • events:应用程序关注的事件(按位或组合):

    • POLLIN:数据可读。
    • POLLOUT:数据可写。

    • POLLERR:发生错误。

    • POLLHUP:连接挂起(如对端关闭)。

    • POLLNVAL:文件描述符未打开。

  • revents:内核返回的实际事件,可能包含 events 中的事件或错误标志(如 POLLERR)。


4.返回值

  • >0:就绪的文件描述符数量。

  • 0:超时且没有文件描述符就绪。

  • -1:发生错误,可通过 errno 获取原因(如 EINTR 表示被信号中断)。


5.使用步骤

  1. 初始化 struct pollfd 数组,设置要监视的 fd 和 events

  2. 调用 poll,阻塞等待事件发生。

  3. 检查返回的 revents,处理就绪的文件描述符。

示例代码

struct pollfd fds[2];
fds[0].fd = sock_fd;      // 监视套接字
fds[0].events = POLLIN;   // 关注可读事件
fds[1].fd = pipe_fd;      // 监视管道
fds[1].events = POLLOUT;  // 关注可写事件int ret = poll(fds, 2, 1000);  // 等待1秒
if (ret > 0) {if (fds[0].revents & POLLIN) {// 套接字可读,处理数据}if (fds[1].revents & POLLOUT) {// 管道可写,发送数据}
}

6.与 select 的对比

  1. 文件描述符数量限制

    • select 使用固定大小的 fd_set,受 FD_SETSIZE 限制(通常 1024)。

    • poll 使用动态数组,理论上无限制。

  2. 效率

    • select 每次调用需重新传入所有文件描述符。

    • poll 通过分离 events 和 revents 字段,避免重复初始化。

  3. 可移植性

    • select 在所有系统可用。

    • poll 在部分旧系统(如早期 Windows)不支持。


7.适用场景

  • 需要监视的文件描述符数量较多(超过 FD_SETSIZE)。

  • 希望避免 select 的重复初始化开销。

  • 对跨平台兼容性要求不高。


8.缺点

  • 当监视大量文件描述符时,遍历所有 struct pollfd 检查 revents 效率较低(此时 epoll 或 kqueue 更高效)。


9.总结

poll 解决了 select 的部分缺陷,适合中等规模的高效 I/O 多路复用。在 Linux 上处理海量连接时,更推荐使用 epoll;而在 macOS/BSD 上,kqueue 是更优的选择。

二、epoll函数

1.核心思想

  1. 事件驱动:仅关注活跃的文件描述符,避免遍历全部描述符。

  2. 内核事件表:通过内核维护的红黑树就绪链表,实现高效的事件注册与通知。


2.核心函数

1. epoll_create - 创建 epoll 实例

#include <sys/epoll.h>int epoll_create(int size);  // size 参数已弃用(只需 >0)
  • 返回一个 epoll 文件描述符(用于后续操作)。

  • 不再使用时需用 close() 关闭。


2. epoll_ctl - 管理 epoll 事件表

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
  • 参数

    • epfdepoll_create 返回的 epoll 描述符。

    • op:操作类型:

      • EPOLL_CTL_ADD:注册新 fd。

      • EPOLL_CTL_MOD:修改已注册的 fd。

      • EPOLL_CTL_DEL:删除 fd。

    • fd:要操作的 fd(如套接字)。

    • event:指向事件结构体的指针。

  • struct epoll_event

    struct epoll_event {uint32_t     events;  // 关注的事件(按位或组合)epoll_data_t data;    // 用户数据(如关联的 fd)
    };typedef union epoll_data {void    *ptr;int      fd;uint32_t u32;uint64_t u64;
    } epoll_data_t;

3. epoll_wait - 等待事件发生

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
  • 参数

    • epfd:epoll 描述符。

    • events:输出参数,用于接收就绪事件数组。

    • maxeventsevents 数组的最大容量。

    • timeout:超时时间(毫秒),-1 表示阻塞,0 表示立即返回。

  • 返回值

    • 成功:返回就绪的 fd 数量。

    • 超时:返回 0

    • 错误:返回 -1,设置 errno


3.事件类型

事件类型说明
EPOLLIN数据可读(包括对端关闭连接)
EPOLLOUT数据可写
EPOLLERR发生错误(自动监听,无需显式设置)
EPOLLHUP对端关闭连接或读写关闭
EPOLLET边缘触发模式(默认是水平触发)
EPOLLONESHOT事件只触发一次,需重新注册才能继续监听

4.触发模式

1. 水平触发(LT,默认)
  • 特点:只要 fd 满足就绪条件,会持续通知

  • 行为:类似 select/poll,未处理的事件会重复触发 epoll_wait

  • 适用场景:简单编程模型,无需一次性处理完所有数据。

2. 边缘触发(ET)
  • 特点:仅在 fd 状态变化时通知一次

  • 行为:需一次性处理完所有数据(否则可能丢失事件)。

  • 要求:必须使用非阻塞 I/O,循环读/写直到 EAGAIN 或 EWOULDBLOCK

  • 适用场景:高性能场景,减少事件触发次数。


5.使用步骤

  1. 调用 epoll_create 创建 epoll 实例。

  2. 调用 epoll_ctl 注册需要监听的 fd 及事件。

  3. 循环调用 epoll_wait 等待事件。

  4. 处理就绪事件(如读/写数据)。

  5. 根据需求修改或删除已注册的 fd。


6.示例代码(LT 模式)

#include <sys/epoll.h>#define MAX_EVENTS 10int main() {int epfd = epoll_create1(0);  // 创建 epoll 实例struct epoll_event ev, events[MAX_EVENTS];// 注册 socket_fd 到 epoll,监听可读事件(默认 LT)ev.events = EPOLLIN;ev.data.fd = socket_fd;epoll_ctl(epfd, EPOLL_CTL_ADD, socket_fd, &ev);while (1) {int nready = epoll_wait(epfd, events, MAX_EVENTS, -1);for (int i = 0; i < nready; i++) {if (events[i].data.fd == socket_fd) {// 处理 socket 可读事件(如 accept 新连接)} else {// 处理其他 fd 的读/写事件}}}close(epfd);return 0;
}

7.优点

  1. 高效处理海量连接:时间复杂度 O(1)(就绪事件数量相关)。

  2. 无需重复注册:内核直接维护事件表。

  3. 支持边缘触发:减少无效事件通知,提高吞吐量。


8.缺点

  1. 仅限 Linux:跨平台需改用 select/poll 或 kqueue(BSD/macOS)。

  2. 编程复杂度:ET 模式需处理非阻塞 I/O 和缓冲区。


9.适用场景

  • 高并发服务器(如 Web 服务器、游戏服务器)。

  • 需要同时处理数万甚至百万级连接。

  • 追求低延迟和高吞吐量的网络应用。


10.对比 select/poll

特性select/pollepoll
时间复杂度O(n)(遍历所有 fd)O(1)(仅处理就绪事件)
最大 fd 数量有限(select 默认 1024)理论无上限(受系统资源限制)
触发模式仅水平触发(LT)支持 LT 和 ET
内存拷贝每次调用需拷贝 fd 集合到内核内核直接管理事件表
适用场景低并发或跨平台Linux 高并发

11.注意事项

  1. ET 模式必须使用非阻塞 I/O:避免因未读完数据导致线程阻塞。

  2. 避免 EPOLLONESHOT 误用:需手动重新注册事件。

  3. 内存泄漏:及时关闭不再使用的 epoll 描述符。


12.总结

epoll 是 Linux 下高性能网络编程的基石,尤其适合高并发场景。其事件驱动模型和高效的内核机制(如红黑树、就绪链表)使其在处理海量连接时性能远超 select 和 poll。若需跨平台,可结合 epoll(Linux)、kqueue(BSD/macOS)和 IOCP(Windows)实现多路复用。


三、当网卡接收到数据流程

1. 网卡接收数据(硬件层)

  1. 数据到达网卡

    • 网卡接收到数据包后,通过 DMA 直接将数据拷贝到内核内存的 环形缓冲区(Ring Buffer) 中。

    • 网卡触发 硬件中断,通知 CPU 有数据到达。

  2. 软中断处理

    • 内核通过 ksoftirqd 线程(软中断)处理数据包:

      1. 解析数据包:拆解以太网帧、IP 头、TCP/UDP 头。

      2. 关联 Socket:根据端口和 IP 找到对应的 Socket(文件描述符 fd)。

      3. 填充接收缓冲区:将数据存入该 Socket 的 接收缓冲区(Receive Buffer)


2. epoll 的事件触发(内核层)

关键数据结构
  • 红黑树(RB-Tree):存储所有通过 epoll_ctl 注册的 fd(键为 fd,值为 epoll_event)。

  • 就绪队列(Ready List):存放已就绪的 fd 对应的事件(epoll_item 结构)。

事件触发流程
  1. 检查 epoll 监听状态

    • 当数据被存入 Socket 的接收缓冲区后,内核检查该 fd 是否被某个 epoll 实例监听(即是否在红黑树中存在对应的 epoll_item)。

    • 如果是,内核会将该 fd 关联的 epoll_item 添加到 就绪队列 中,并标记事件类型(如 EPOLLIN)。

  2. 回调机制

    • epoll 通过 回调函数(Callback) 实现事件触发,而非轮询。

    • 当数据到达时,内核直接调用 ep_poll_callback 函数,将对应的 epoll_item 插入就绪队列,无需遍历红黑树。


3. 用户层获取事件(应用层)

  1. 调用 epoll_wait

    • 用户程序调用 epoll_wait 时,内核检查 就绪队列

      • 如果队列非空,直接返回就绪的 fd 列表(通过 events 数组)。

      • 如果队列为空,根据 timeout 参数决定阻塞或超时返回。

  2. 就绪队列处理

    • 内核将就绪队列中的 epoll_item 复制到用户提供的 events 数组中,并清空就绪队列。

    • 用户程序遍历 events 数组,处理每个就绪的 fd(如读取数据)。


流程总结(结合数据结构)

  1. 数据到达网卡 → DMA 到内核缓冲区 → 协议栈解析 → 填充 Socket 接收缓冲区。

  2. 检查红黑树 → 若 fd 被 epoll 监听 → 触发回调函数 → 将 epoll_item 加入就绪队列。

  3. 用户调用 epoll_wait → 内核返回就绪队列中的事件 → 应用处理数据。


性能优势的根源

  1. 红黑树的高效管理

    • 添加/删除/查找 fd 的时间复杂度为 O(log N),适合动态管理海量连接。

  2. 就绪队列的 O(1) 事件获取

    • 直接返回就绪的 fd,无需遍历所有监听的 fd(对比 select/poll 的 O(N))。

  3. 回调驱动(而非轮询)

    • 仅在实际事件发生时触发操作,避免无意义的 CPU 开销。


触发模式的影响

1. 水平触发(LT)
  • 数据未读完时:内核会持续将 fd 加入就绪队列,直到接收缓冲区为空。

  • 行为示例
    若应用只读取部分数据,下次 epoll_wait 仍会返回该 fd 的 EPOLLIN 事件。

2. 边缘触发(ET)
  • 仅在状态变化时触发:内核仅在接收缓冲区从空变为非空时,将 fd 加入就绪队列一次。

  • 行为示例
    应用必须一次性读取所有数据(直到 read 返回 EAGAIN),否则会丢失后续事件。


完整流程示例

  1. 客户端发送数据 → 网卡接收 → 内核协议栈处理 → 数据存入 Socket 接收缓冲区。

  2. 内核检查红黑树:发现该 fd 被 epoll 监听(关注 EPOLLIN 事件)。

  3. 触发回调 → 将该 fd 的 epoll_item 插入就绪队列。

  4. 用户调用 epoll_wait → 获取到该 fd 的 EPOLLIN 事件。

  5. 应用读取数据 → 若使用 ET 模式,需循环读取直到 EAGAIN

  6. 处理完毕 → 继续调用 epoll_wait 等待新事件。


总结

  • 红黑树:管理所有注册的 fd,实现高效增删改查。

  • 就绪队列:存储实际发生的事件,避免全量遍历。

  • 回调机制:数据到达时直接触发事件,减少延迟。


http://www.ppmy.cn/ops/161930.html

相关文章

【 树 】

【树 】 目录1. 二叉搜索树&#xff08;BST&#xff09;的退化知识点示例 2. 平衡树的定义3. AVL 树知识点不平衡产生的原因旋转操作 4. AVL 树代码设计树节点旋转操作插入节点操作删除节点操作测试代码 红黑树的定义代码设计节点类设计左旋和右旋操作 插入操作删除操作黑侄情形…

【Python实战】——Python+Opencv是实现车牌自动识别

&#x1f349;CSDN小墨&晓末:https://blog.csdn.net/jd1813346972 个人介绍: 研一&#xff5c;统计学&#xff5c;干货分享          擅长Python、Matlab、R等主流编程软件          累计十余项国家级比赛奖项&#xff0c;参与研究经费10w、40w级横向 文…

tableau之网络图和弧线图

一、网络图 概念 网络图&#xff08;Network Graph&#xff09;&#xff0c;也称为网络可视化&#xff0c;是数据可视化的一种形式&#xff0c;用于显示实体&#xff08;节点&#xff09;之间的关系&#xff08;边&#xff09;。这种图表通过节点和边的结构揭示数据中的复杂关…

Eclipse 编译项目指南

Eclipse 编译项目指南 引言 Eclipse 是一款功能强大的集成开发环境&#xff08;IDE&#xff09;&#xff0c;广泛用于Java、C/C、Python等多种编程语言的开发。在Eclipse中编译项目是进行软件开发的基础步骤。本文将详细介绍如何在Eclipse中编译项目&#xff0c;包括项目设置…

想转行做春晚那种扭秧歌的机器人,大概要会点什么?

大概的流程是这样子: 机器人编程: 传感器 - > 硬件层 - > 软件层驱动行动 类比人类大脑就是: 细胞传感器->大脑->思维驱动行动 传感器输入输出端: 接收信号反馈给硬件层,执行硬件层指令 硬件层核心工作: 嵌入式开发,处理机器人驱动,处理传感器数据, 处理软件层…

DeepSeek破局:解锁智能技术评审的无限可能

DeepSeek 技术优势剖析 DeepSeek 之所以在众多智能技术中脱颖而出,得益于其在自然语言处理、机器学习、大数据分析等多领域的卓越技术成果。在自然语言处理方面,DeepSeek 采用了先进的 Transformer 架构,并在此基础上引入了创新的多头潜在注意力(MLA)机制。这种机制通过对…

React + TypeScript 数据血缘分析实战

React TypeScript 数据血缘分析实战 目录 技术选型与架构设计核心概念解析基础场景实现 场景一&#xff1a;visx库基础血缘图实现场景二&#xff1a;React-Lineage-DAG企业级方案场景三&#xff1a;动态数据源与复杂交互 TypeScript类型系统深度优化性能优化与工程化实践开源…

双碳目标下工业企业能源智慧化转型

摘要&#xff1a;能源是人类社会发展的重要推动力&#xff0c;能源缺乏&#xff0c;将导致人类无法正常开展生产生活活动。如果失去能源的支持&#xff0c;现代社会的运作将会直接瘫痪或崩溃。所以&#xff0c;为了解决目前日益严峻的能源问题&#xff0c;应采用高效率的节能监…