I/O复用之 epoll使用详解 Linux编程

epoll 是 Linux 内核提供的一种用于多路复用 I/O 事件通知的机制,专为高效处理大量并发连接而设计。它被广泛应用于网络服务器和高性能应用中,主要用于监控多个文件描述符(如套接字、管道、文件等)上的事件(可读、可写、异常等)。

相比 selectpollepoll 提供了更好的性能,尤其在文件描述符数量非常大的情况下。epoll 的优势来自其对事件的高效管理和通知机制。

epoll 的核心特性

  1. 事件驱动epoll 是事件驱动的多路复用技术,可以监控大量的 I/O 事件,并在事件发生时通知用户程序处理。

  2. 常量级性能(O(1))epoll 在监控大量文件描述符时,性能不会随着描述符的数量线性增长。相比之下,selectpoll 需要遍历整个文件描述符集,因而性能会随着描述符数量增加而下降。

  3. 水平触发 (Level-Triggered, LT) 和边缘触发 (Edge-Triggered, ET)

    • 水平触发:默认模式,当文件描述符处于就绪状态时,每次调用 epoll_wait 都会返回该描述符。
    • 边缘触发:当文件描述符从非就绪变为就绪状态时,仅在状态变化时通知一次。因此,必须将文件描述符设置为非阻塞模式,并在每次事件通知时一次性读取所有可用数据,否则会错过后续事件。
  4. 内核事件队列epoll 会在内核中创建一个事件队列,用户程序可以通过 epoll_wait 从该队列获取已经就绪的事件。

epoll 的工作原理

epoll 的工作可以分为以下三个步骤:

  1. 创建 epoll 实例:通过 epoll_create1 创建一个 epoll 实例,该实例类似于一个容器,用于存储需要监控的文件描述符及其事件。

  2. 注册、修改或删除文件描述符:通过 epoll_ctl 将需要监控的文件描述符添加到 epoll 实例中,或者修改、删除现有的文件描述符。

  3. 等待事件:通过 epoll_wait 等待注册的文件描述符发生指定的事件,当有事件发生时,返回事件的文件描述符。

epoll 函数详细说明

1. epoll_create1

epoll_create1 用于创建一个 epoll 实例,并返回一个用于管理文件描述符的文件描述符。

函数原型:
int epoll_create1(int flags);
参数:
  • flags:用于指定创建时的行为。可取值为:
    • 0:默认行为。
    • EPOLL_CLOEXEC:设置 FD_CLOEXEC 标志,表示在 exec() 调用后自动关闭该 epoll 文件描述符。
返回值:
  • 成功时返回一个 epoll 文件描述符(正整数)。
  • 失败时返回 -1,并设置 errno 指明错误类型。
示例:
int epfd = epoll_create1(0); // 创建 epoll 实例
if (epfd == -1) {perror("epoll_create1 failed");exit(EXIT_FAILURE);
}
注意:
  • 早期的 epoll_create 函数要求传入一个参数来表示监听的最大文件描述符数量,但该参数已经被忽略,推荐使用 epoll_create1 代替。
  • EPOLL_CLOEXEC 可以防止文件描述符在执行 fork()exec() 时被意外继承。

2. epoll_ctl

epoll_ctl 是用于向 epoll 实例中添加、修改或删除文件描述符。它是 epoll 的核心控制函数。

函数原型:
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
参数:
  • epfd:通过 epoll_create1 返回的 epoll 文件描述符。
  • op:操作类型,表示是添加、修改还是删除文件描述符。可选值:
    • EPOLL_CTL_ADD:将 fd 添加到 epoll 实例中,监控指定的事件。
    • EPOLL_CTL_MOD:修改 fd 已注册的事件。
    • EPOLL_CTL_DEL:从 epoll 实例中删除 fd
  • fd:需要监控的文件描述符。
  • event:描述监控的事件。是 struct epoll_event 类型的指针:
    struct epoll_event {uint32_t events;   // 要监控的事件类型(如 EPOLLIN、EPOLLOUT 等)epoll_data_t data; // 用户自定义的数据,可以是 fd 或指针
    };
    
    常见的事件类型包括:
    • EPOLLIN:文件描述符可读。
    • EPOLLOUT:文件描述符可写。
    • EPOLLRDHUP:对端关闭连接。
    • EPOLLERR:发生错误。
    • EPOLLHUP:挂起事件。
    • EPOLLET:边缘触发模式。
返回值:
  • 成功时返回 0
  • 失败时返回 -1,并设置 errno 指明错误类型。
示例:
struct epoll_event ev;
ev.events = EPOLLIN | EPOLLET; // 监控可读事件,并启用边缘触发
ev.data.fd = sockfd;           // 用户数据,这里存储的是套接字文件描述符if (epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev) == -1) {perror("epoll_ctl: sockfd");exit(EXIT_FAILURE);
}
注意:
  • 边缘触发 vs 水平触发:默认是水平触发模式。如果需要边缘触发,需要在 events 中设置 EPOLLET
  • 非阻塞模式:当使用边缘触发模式时,推荐将文件描述符设置为非阻塞模式,以确保不会错过事件。

3. epoll_wait

epoll_wait 用于等待 epoll 实例中的文件描述符上发生事件。该函数会阻塞直到有事件发生或超时时间到。

函数原型:
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
参数:
  • epfd:通过 epoll_create1 创建的 epoll 文件描述符。
  • events:指向 epoll_event 结构体的数组,用于返回已发生事件的文件描述符及事件类型。
  • maxevents:该数组的大小,表示最多返回多少个事件。
  • timeout:等待事件的超时时间(毫秒),可取值:
    • 0:立即返回,不阻塞(可用于轮询)。
    • -1:无限期阻塞,直到至少一个事件发生。
    • 0:阻塞指定的毫秒数。

返回值:
  • 成功时返回准备好的文件描述符的数量(正整数)。
  • 超时时返回 0
  • 失败时返回 -1,并设置 errno
示例:
struct epoll_event events[MAX_EVENTS];
int nfds = epoll_wait(epfd, events, MAX_EVENTS, -1); // -1 表示无限期等待
if (nfds == -1) {perror("epoll_wait failed");exit(EXIT_FAILURE);
}for (int i = 0; i < nfds; ++i) {if (events[i].events & EPOLLIN) {// 处理可读事件int fd = events[i].data.fd;char buf[1024];int n = read(fd, buf, sizeof(buf));if (n == -1) {perror("read error");close(fd);} else if (n == 0) {// 对端关闭close(fd);} else {// 处理读取到的数据printf("Read %d bytes from fd %d: %s\n", n, fd, buf);}}
}
注意:
  • 返回的 events 数组中,包含的是已经触发事件的文件描述符和事件类型,使用 events[i].data.fd 访问文件描述符,events[i].events 访问事件类型。
  • 超时机制:在高并发应用中,设置合适的超时时间可以确保程序在等待时不会无限期阻塞,或者可以结合 timeout = 0 实现非阻塞轮询。

epoll 使用流程

  1. 创建 epoll 实例

    int epfd = epoll_create1(0);
    
  2. 注册文件描述符

    struct epoll_event ev;
    ev.events = EPOLLIN;  // 监控可读事件
    ev.data.fd = sockfd;  // 需要监控的套接字文件描述符
    epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);
    
  3. 等待事件

    struct epoll_event events[MAX_EVENTS];
    int nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
    for (int i = 0; i < nfds; i++) {if (events[i].events & EPOLLIN) {// 处理可读事件}
    }
    
  4. 删除文件描述符并关闭 epoll 实例

    epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL);
    close(epfd);
    

epoll 使用示例

文件保存

将下面的 epoll 使用示例代码保存为 epoll_server.c

#include <sys/epoll.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <errno.h>#define MAX_EVENTS 10
#define PORT 8080int main() {int server_fd, new_socket, epfd, nfds;struct sockaddr_in address;struct epoll_event ev, events[MAX_EVENTS];// 创建监听套接字server_fd = socket(AF_INET, SOCK_STREAM, 0);address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons(PORT);bind(server_fd, (struct sockaddr *)&address, sizeof(address));listen(server_fd, 10);// 创建 epoll 实例epfd = epoll_create1(0);if (epfd == -1) {perror("epoll_create1");exit(EXIT_FAILURE);}// 注册监听套接字的可读事件ev.events = EPOLLIN;ev.data.fd = server_fd;if (epoll_ctl(epfd, EPOLL_CTL_ADD, server_fd, &ev) == -1) {perror("epoll_ctl: server_fd");exit(EXIT_FAILURE);}while (1) {// 等待事件发生nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);if (nfds == -1) {perror("epoll_wait");exit(EXIT_FAILURE);}// 处理每个已发生的事件for (int i = 0; i < nfds; i++) {if (events[i].data.fd == server_fd) {// 新连接到来new_socket = accept(server_fd, NULL, NULL);ev.events = EPOLLIN;ev.data.fd = new_socket;epoll_ctl(epfd, EPOLL_CTL_ADD, new_socket, &ev);} else {// 处理客户端连接上的可读事件char buf[1024];int fd = events[i].data.fd;int bytes = read(fd, buf, sizeof(buf));if (bytes <= 0) {// 客户端断开连接close(fd);epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);} else {// 处理读取到的数据printf("Received data: %s\n", buf);}}}}close(server_fd);close(epfd);return 0;
}
编译

在终端中,使用 GCC 编译该程序:

gcc -o epoll_server epoll_server.c

2. 运行步骤

启动服务器

编译完成后,运行服务器程序:

./epoll_server

此时服务器将在 8080 端口监听客户端的连接。

运行客户端(使用 telnetnc

你可以使用 telnetnetcat (nc) 工具来连接服务器:

  • 使用 telnet

    telnet localhost 8080
    
  • 使用 nc

    nc localhost 8080
    

在成功连接到服务器后,你可以输入一些文本数据并按回车键,服务器会收到并显示发送的数据。

3. 预期输出结果

在服务器端的终端上,程序运行后将等待客户端连接和数据输入。当客户端连接并发送数据时,服务器会输出接收到的数据。

服务器端输出:
Received data: Hello from client 1
Received data: How are you?
Received data: Another message from client 1
客户端输出:

如果使用 telnetnc,则会显示你输入的数据,如:

Hello from client 1
How are you?
Another message from client 1

4. 注意事项

  • 如果多个客户端连接到服务器,epoll 会同时监控这些连接,并处理它们发送的数据。
  • 服务器不会自动关闭,需要手动通过 Ctrl+C 终止。

epollselectpoll 的对比

特性selectpollepoll
复杂度O(n)O(n)O(1)
文件描述符限制1024(默认)
性能随文件描述符增多而下降随文件描述符增多而下降文件描述符数量无关
内存拷贝每次调用都要将 fd 集合拷贝到内核同上初次注册后无需拷贝

epoll 应用场景

  1. 高并发网络服务器:处理成千上万的连接,例如 Web 服务器、反向代理、负载均衡器等。
  2. 长连接服务:需要持续与大量客户端保持连接的服务。
  3. 高效事件驱动应用:如聊天系统、推送系统等。

总结

epoll 提供了一种高效的 I/O 事件管理方式,适用于处理大量并发连接。通过合理使用 epoll_ctlepoll_wait,以及选择合适的触发模式,可以在高并发场景下大幅提高程序的性能和可扩展性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/53491.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MyBatis ——在java层面对MySQL数据库进行操作

目录 MyBatis 是一款优秀的 持久层框架&#xff0c;用于简化JDBC&#xff08;java操作数据库&#xff09;的开发&#xff1b; 使用MyBatis 查询所有用户数据的过程 Lombok是一个实用的]ava类库&#xff0c;能通过注解的形式 简化 JavaBean的代码 注解&#xff1a; 引入Myb…

【Leetcode152】乘积最大子数组(动态规划)

文章目录 一、题目二、思路三、代码 一、题目 二、思路 &#xff08;0&#xff09;读懂题意&#xff1a;题目的“连续”是指位置的连续&#xff0c;而不是说数字的连续&#xff0c;这是个大坑。 &#xff08;1&#xff09;确定状态&#xff1a;定义两个状态来记录当前子数组的…

Python 爬虫项目实战(一):爬取某云热歌榜歌曲

前言 网络爬虫&#xff08;Web Crawler&#xff09;&#xff0c;也称为网页蜘蛛&#xff08;Web Spider&#xff09;或网页机器人&#xff08;Web Bot&#xff09;&#xff0c;是一种按照既定规则自动浏览网络并提取信息的程序。爬虫的主要用途包括数据采集、网络索引、内容抓…

MySQL笔记2(DQL查询语言【条件、分组、排序、限制、子查询、左右连接、内连接、联合查询】)

DQL数据查询语言与项目高级查询实战 先安装数据库并创建一个库 并创建以下数据 /*创建部门表*/CREATE TABLE dept( deptnu INT PRIMARY KEY comment 部门编号, dname VARCHAR(50) comment 部门名称, addr VARCHAR(50) comment 部门地址 );/*某个公司的员工表*/ CREATE TABLE…

Python闭包示例代码

代码示例&#xff1a; def outer_function(x):def inner_function(y):return x yreturn inner_functionclosure_example outer_function(5) result closure_example(3) # 输出为 8在这个示例中&#xff0c;outer_function 是一个包含内部函数的闭包。让我们逐步分析它的工…

金融业开源技术 术语

金融业开源技术 术语 1 范围 本文件界定了金融业开源技术的常用术语。 本文件适用于金融业中涉及开源技术的相关标准及规范性文件制定和信息沟通等活动。

安全设备类型

一、隔爆型 隔爆型定位设备是将可能产生火花、电弧和危险温度的零部件都放置在一个隔爆外壳内。当内部发生爆炸时&#xff0c;外壳能够承受爆炸压力而不损坏&#xff0c;并且不会使内部爆炸传播到周围的爆炸性环境中。隔爆型产品通常比较坚固耐用&#xff0c;但相对来说体积和重…

「ComfyUI」比 joy_caption 更好用的提示词反推模型!

前言 距离我们上次介绍 joy_caption 还没几天呢&#xff0c;这就又出新模型来与 joy_caption 一争高下了。 今天我们要介绍的是&#xff1a;Florence-2-X-PromptGen-v1.5&#xff0c;是在 Florence-2 的基础上进行精细调优的一款高级图像标注工具&#xff0c;专门为生成和标注…

Flutter框架——2.状态-路由-包-资源

文章参考了Flutter中国开源项目发起人杜文&#xff08;网名wendux&#xff09;创作的一本系统介绍Flutter技术的中文书籍《Flutter实战第二版》&#xff0c;网址&#xff1a;第二版序 | 《Flutter实战第二版》 https://book.flutterchina.club/#第二版变化 文章目录 一、状态管…

【C#跨平台开发详解】C#跨平台开发技术之.NET Core基础学习及快速入门

1. C#与.NET的发展历程 C#是由Microsoft开发的现代编程语言&#xff0c;最初伴随着.NET Framework发布。随着技术的进步&#xff0c;特别是针对跨平台开发的需求&#xff0c;Microsoft推出了.NET Core&#xff0c;这是一个开源且跨平台的框架&#xff0c;支持Windows、macOS和…

项目实战 ---- 商用落地视频搜索系统(8)---优化(2)---查询逻辑层优化

目录 背景 技术衡量与方案 一种可实现方案 可实现方案及设计描述 可能存在的问题 一种创新实现方案 方案的改良设计 策略公式 优化的实现 完整代码 代码解释 异常场景的考量 处理方式 运行注意事项 运行结果 结果优化对比与解释 背景 在项目实战 ---- 商用落地…

发票真伪识别接口费用-发票真伪查验接口-发票验真示例

发票信息核验是一个重要的财务和会计过程&#xff0c;涉及到对发票上的信息进行验证&#xff0c;以确保其真实性和准确性。在数字化时代&#xff0c;这一过程企业通常想通过调用发票查验接口的方式实现自动化管理模式。 发票查验接口费用不同的服务提供商会有不同的收费标准&a…

利用数据分析提升SEO排名的7种方法

我们都听过“大数据分析”这个词。科技让我们能够清晰地了解我们的活动和内容的表现——向我们提供了关于受众的宝贵信息&#xff0c;甚至可以精确到他们在Google和其他搜索引擎上使用的具体搜索词。 你已经在你的业务中使用数据分析了吗&#xff1f;如果是&#xff0c;你有利…

LeetCode 每日一题 2024/9/2-2024/9/8

记录了初步解题思路 以及本地实现代码&#xff1b;并不一定为最优 也希望大家能一起探讨 一起进步 目录 9/2 3153. 所有数对中数位不同之和9/3 2708. 一个小组的最大实力值9/4 2860. 让所有学生保持开心的分组方法数9/5 3174. 清除数字9/6 3176. 求出最长好子序列 I9/7 3177. 求…

828华为云征文|华为云Flexus X实例docker部署MinIO对象存储系统obs

828华为云征文&#xff5c;华为云Flexus X实例docker部署MinIO对象存储系统obs 华为云最近正在举办828 B2B企业节&#xff0c;Flexus X实例的促销力度非常大&#xff0c;特别适合那些对算力性能有高要求的小伙伴。如果你有自建MySQL、Redis、Nginx等服务的需求&#xff0c;一定…

数据分析:R语言计算XGBoost线性回归模型的SHAP值

禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍SHAP用途计算方法:应用加载R包导入数据数据预处理函数模型介绍 SHAP(SHapley Additive exPlanations)值是一种解释机器学习模型预测的方法。它基于博弈论中的Shapley值概念,…

5G网络建设

题目描述 现需要在基城市进行5G网络建设&#xff0c;已经选取N个地点设置5G基站&#xff0c;编号固定为1到N&#xff0c;接下来需要各个基站之间使用光纤进行连接以确保基 站能互联互通&#xff0c;不同基站之间假设光纤的成本各不相同&#xff0c;且有些节点之间已经存在光纤…

GeekDesk:不只是桌面美化,更是你的时间管理与效率提升专家

前言 科技&#xff0c;如同织就未来的经纬线&#xff0c;以智慧为梭&#xff0c;穿梭于生活的每一个角落&#xff0c;编织出一张便捷之网&#xff0c;让人类的生活如诗如画&#xff0c;绚烂多彩--这一理念深刻地影响着每一个科技产品的诞生与发展。在众多旨在提升工作效率与生…

基于SpringBoot的宠物服务系统+uniapp小程序+LW参考示例

系列文章目录 1.基于SSM的洗衣房管理系统原生微信小程序LW参考示例 2.基于SpringBoot的宠物摄影网站管理系统LW参考示例 3.基于SpringBootVue的企业人事管理系统LW参考示例 4.基于SSM的高校实验室管理系统LW参考示例 5.基于SpringBoot的二手数码回收系统原生微信小程序LW参考示…

函数指针和指针函数

指针 指针函数 指针函数一个函数&#xff0c;只不过这个函数的返回值是一个地址值 和普通函数唯一的区别就是在函数名前面多了一个*号 函数返回值必须用同类型的指针变量来接受 也可以将其返回值定义为 void*类型&#xff0c;在调用的时候强制转换返回值为自己想要的类型 str…