当前位置: 首页 > news >正文

Linux中I/O复用机制epoll

1. 为什么会出现 epoll

在早期的网络编程中,select 是一个非常常用的 I/O 复用机制,用于在多个文件描述符(如套接字)上进行 I/O 操作的检测。select 会将多个文件描述符传入,轮询检查它们的状态,看哪些是可以读取、写入或者异常的。然而,select 存在以下几个问题,特别是在需要处理大量文件描述符时,epoll 的出现就是为了解决这些问题:

Linux中的 I/O 复用机制 select-CSDN博客

select 存在的问题:
  1. 文件描述符限制select 的一个关键限制是它最大支持的文件描述符数量,通常是 1024(这个数量在 Linux 中默认定义为 FD_SETSIZE)。如果需要监控更多的文件描述符,select 就不适用了。

  2. 每次调用 select 都要传入整个文件描述符集合:在每次调用 select 时,系统需要扫描整个文件描述符集合,检查每个文件描述符的状态,即使其中很多文件描述符并未发生变化。这种轮询会带来较高的开销,尤其是在大量文件描述符的情况下。

  3. 性能低下select 的机制是阻塞式的,当有大量文件描述符时,性能会下降,特别是当许多文件描述符没有变化时,select 还是需要遍历所有文件描述符集合。

 select示意图

为了应对这些问题,Linux 引入了 epoll,它是一种更加高效的 I/O 复用机制,专门用于处理大量的文件描述符。

2. epoll 的作用

epoll 是一种 事件驱动的 I/O 复用机制,主要解决了传统的 selectpoll 的性能瓶颈。它的出现,主要是为了更高效地处理成千上万的连接。epoll 的优势包括:

  • 高效的事件通知机制:与 selectpoll 不同,epoll 采用基于内核的事件通知机制,只有当文件描述符的状态发生变化时,才会通知应用程序。这避免了不必要的遍历和检查,从而提高了效率。

  • 支持大规模文件描述符epoll 可以支持数万个文件描述符,不再受 select 文件描述符限制(1024个)。

  • 单次注册,持续监控:通过一次注册文件描述符,就可以持续地对其进行监控,避免每次调用时都要传递整个文件描述符集合。

 epoll示意图

3. epoll 的工作原理

epoll 的工作原理基于 事件通知,它的基本流程包括以下几个步骤:

  1. 创建 epoll 实例: 使用 epoll_create() 创建一个 epoll 实例,返回一个 epoll 文件描述符。

  2. 注册文件描述符: 使用 epoll_ctl() 向 epoll 实例注册需要监控的文件描述符,指定这些文件描述符上的事件(如读事件、写事件、异常事件)。

  3. 等待事件发生: 使用 epoll_wait() 阻塞或非阻塞地等待文件描述符上的事件发生。当某个文件描述符的事件发生时,epoll_wait() 返回。

  4. 事件处理: 事件发生后,应用程序处理相关文件描述符的数据传输或者其他操作。

epoll 的 API 解释
  1. epoll_create() 用于创建一个 epoll 实例。

    int epoll_create(int size);
    • size 参数用于指定内核为该 epoll 实例分配多少空间,这个值在现代 Linux 中并没有实际作用。

    • 返回值:成功时返回一个 epoll 实例的文件描述符,失败时返回 -1。

  2. epoll_ctl() 用于添加、修改或删除文件描述符的监控事件。

    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    • epfd:epoll 实例的文件描述符。

    • op:操作类型(EPOLL_CTL_ADDEPOLL_CTL_MODEPOLL_CTL_DEL)。

    • fd:要监控的文件描述符。

    • event:指定要监控的事件类型(例如 EPOLLINEPOLLOUT 等)。

  3. epoll_wait() 用于等待并获取已经就绪的文件描述符事件。

    int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
    • epfd:epoll 实例的文件描述符。

    • events:保存就绪事件的数组。

    • maxevents:最多返回的事件数。

    • timeout:等待时间,单位为毫秒,0 为不阻塞,-1 为无限等待。

4. epollselect 的区别
特性selectepoll
文件描述符数目有最大限制(一般为1024)无限制,支持成千上万的文件描述符
性能文件描述符多时性能差只有状态变化的文件描述符会被通知,性能优越
事件通知方式每次调用都需要遍历所有文件描述符基于事件的通知机制,只有状态变化才通知
内存开销需要传递整个文件描述符集合只需要传递事件队列,开销较小
5. 示例代码

下面是一个简单的使用 epoll 的服务器端示例,它可以同时处理多个客户端连接:

// 服务器端 (`epoll` 示例)
​
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <sys/socket.h>
​
#define BUF_SIZE 1024
#define MAX_EVENTS 10
​
void error_handling(char *message);
​
int main(int argc, char *argv[]) {int serv_sock, clnt_sock;struct sockaddr_in serv_adr, clnt_adr;socklen_t clnt_adr_sz;char buf[BUF_SIZE];int str_len;struct epoll_event ev, events[MAX_EVENTS];int epfd, event_count;
​if (argc != 2) {printf("Usage : %s <port>\n", argv[0]);exit(1);}// 创建服务端套接字serv_sock = socket(PF_INET, SOCK_STREAM, 0);memset(&serv_adr, 0, sizeof(serv_adr));serv_adr.sin_family = AF_INET;serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);serv_adr.sin_port = htons(atoi(argv[1]));// 绑定地址if (bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1)error_handling("bind() error");// 开始监听if (listen(serv_sock, 5) == -1)error_handling("listen() error");// 创建 epoll 实例epfd = epoll_create1(0);if (epfd == -1)error_handling("epoll_create1() error");// 注册监听套接字到 epollev.events = EPOLLIN;ev.data.fd = serv_sock;if (epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &ev) == -1)error_handling("epoll_ctl() error");while (1) {// 等待事件发生event_count = epoll_wait(epfd, events, MAX_EVENTS, -1);if (event_count == -1)error_handling("epoll_wait() error");// 处理就绪的事件for (int i = 0; i < event_count; i++) {if (events[i].data.fd == serv_sock) {// 新客户端连接clnt_adr_sz = sizeof(clnt_adr);clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &clnt_adr_sz);if (clnt_sock == -1)error_handling("accept() error");// 将客户端套接字添加到 epoll 中ev.events = EPOLLIN;ev.data.fd = clnt_sock;if (epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &ev) == -1)error_handling("epoll_ctl() error");printf("New client connected: %d\n", clnt_sock);} else {// 处理客户端数据str_len = read(events[i].data.fd, buf, BUF_SIZE);if (str_len == 0) {// 客户端关闭连接epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, NULL);close(events[i].data.fd);printf("Client disconnected: %d\n", events[i].data.fd);} else {// 回显数据write(events[i].data.fd, buf, str_len);}}}}close(serv_sock);return 0;
​
}
​
void error_handling(char *message) {fputs(message, stderr);fputc('\n', stderr);exit(1);
}
6. 总结
  • epoll 是一种比 select 更高效的 I/O 复用机制,特别适用于需要处理大量并发连接的服务器。

  • 它通过事件驱动的方式来提高性能,避免了每次调用时遍历所有文件描述符的开销。

  • epoll 具有高效的性能,并且不受文件描述符数量的限制,适合大规模的并发处理场景。

epoll 对比 select 的优势使得它成为高并发网络编程中的主流选择,尤其是在 Linux 系统中。

http://www.xdnf.cn/news/566443.html

相关文章:

  • 【Netty】- 入门2
  • dify基于文本模型实现微调Fine-tune语料构造工作流
  • 在 Ubuntu 下通过 C APP程序实现串口发送数据并接收返回数据
  • OSCP备战-Stapler靶场详细步骤
  • 用java实现内网通讯,可多开客户端链接同一个服务器
  • 离线服务器算法部署环境配置
  • 深度解析 Element Plus
  • Flink CDC 3.4 发布, 优化高频 DDL 处理,支持 Batch 模式,新增 Iceberg 支持
  • naive-ui切换主题
  • 基于RT-Thread的STM32F4开发第六讲——PWM输出(CH1和CH1N)
  • DevOps学习回顾03-ops三部曲之配置管理(CM)
  • C++核心编程_初始化列表
  • Unity3D序列化机制详解
  • 云计算与大数据进阶 | 28、存储系统如何突破容量天花板?可扩展架构的核心技术与实践—— 分布式、弹性扩展、高可用的底层逻辑(下)
  • 游戏盾功能与技术解析
  • 电力设备制造企业数字化转型路径研究:从生产优化到生态重构
  • SpringBoot3+Vue3(2)-前端基本页面配置-登录界面编写-Axios请求封装-后端跨越请求错误
  • 【Java高阶面经:微服务篇】4.大促生存法则:微服务降级实战与高可用架构设计
  • 使用计算机视觉实现目标分类和计数!!超详细入门教程
  • uni-app(2):页面
  • 用python实现汉字转拼音工具
  • 【AI News | 20250521】每日AI进展
  • 【Java高阶面经:微服务篇】9.微服务高可用全攻略:从架构设计到自动容灾
  • Ajax快速入门教程
  • OpenCV CUDA模块特征检测与描述------用于创建一个最大值盒式滤波器(Max Box Filter)函数createBoxMaxFilter()
  • PostgreSQL日志维护
  • 阿里云合集(不定期更新)
  • 适合初学者的 Blender 第二部分
  • 1.4 C++之运算符与表达式
  • leetcode hot100刷题日记——8.合并区间