【Linux | IO多路复用】epoll的底层原理详解
epoll
是一种高效的 I/O 多路复用机制,广泛用于 Linux 系统中,用于处理大量并发的文件描述符。它比传统的 select
和 poll
方法具有更好的性能,特别是在处理大量并发连接时。
1.epoll的设计思路
epoll是在select 出现 N 多年后才被发明的,是select 和 poll(poll 和 select 基本一样,有少量改进)的增强版本。epoll通过以下一些措施来改进效率:
-
措施一:功能分离
-
select 低效的原因之一是将“维护等待队列”和“阻塞进程”两个步骤合二为一。
如上图所示,每次调用select都需要这两步操作,然而大多数应用场景中,需要监视的socket相对固定,并不需要每次都修改。
epoll将这两个操作分开,先用epoll_ctl 维护等待队列,再调用epoll_wait 阻塞进程。显而易见,效率就能得到提升。
为方便理解后续的内容,我们先了解一下epoll的用法。如下的代码中,先用epoll_create 创建一个epoll对象 epfd,再通过epoll_ctl 将需要监视的socket添加到 epfd 中,最后调用epoll_wait 等待数据:
int s =socket(AF_INET, SOCK_STREAM, 0);
bind(s, ...)
listen(s, ...) int epfd =epoll_create(...);
epoll_ctl(epfd, ...); //将所有需要监听的socket添加到epfd中 while(1){ int n =epoll_wait(...) for(接收到数据的socket){ //处理 }
}
功能分离,使得epoll有了优化的可能。
措施二:就绪列表
select低效的另一个原因在于程序不知道哪些socket收到数据,只能一个个遍历。如果内核维护一个“就绪列表”,引用收到数据的socket,就能避免遍历。
如上图所示,计算机共有三个socket,收到数据的sock2和sock3 被就绪列表rdlist 所引用。当进程被唤醒后,只要获取rdlist 的内容,就能够知道哪些socket收到数据。
2.epoll底层使用的数据结构
2.1索引的数据结构
既然epoll将“维护监视队列”和“进程阻塞”分离,也意味着需要有个数据结构来保存监视的socket,至少要方便地添加和移除,还要便于搜索,以避免重复添加。
epoll 在内核里使用红黑树来跟踪进程所有待检测的文件描述字,把需要监控的 socket 通过epoll_ctl() 函数加入内核中的红黑树里,红黑树是个高效的数据结构,增删改一般时间复杂度是0(logn)。而 select/poll 内核里没有类似 epoll 红黑树这种保存所有待检测的 socket 的数据结构,所以select/poll 每次操作时都传入整个 socket 集合给内核,而 epoll 因为在内核维护了红黑树,可以保存所有待检测的 socket ,所以只需要传入一个待检测的 socket,减少了内核和用户空间大量的数据拷贝和内存分配。
2.2就绪列表的数据结构
就绪列表引用着就绪的socket,所以它应能够快速的插入数据。程序可能随时调用epoll_ctl 添加监视socket,也可能随时删除。当删除时,若该socket已经存放在就绪列表中,它也应该被移除。所以就绪列表应是一种能够快速插入和删除的数据结构。双向链表就是这样一种数据结构,epoll使用双向链表来实现就绪队列(对应上图的rdlist)。
第二点, epoll使用事件驱动的机制,内核里维护了一个双向链表来记录就绪事件,当某个socket 有事件发生时,通过回调函数内核会将其加入到这个就绪事件列表中,当用户调用 epoll_wait()函数时,只会返回有事件发生的文件描述符的个数,不需要像 select/poll 那样轮询扫描整个socket 集合,大大提高了检测的效率。
epoll 的方式即使监听的 Socket 数量越多的时候,效率不会大幅度降低,能够同时监听的 Socket 的数目也非常的多了,上限就为系统定义的进程打开的最大文件描述符个数。因而,epoll 被称为解决 C10K 问题的利器。插个题外话,网上文章不少说, epoll_wait 返回时,对于就绪的事件,epoll 使用的是共享内存的方式,即用户态和内核态都指向了就绪链表,所以就避免了内存拷贝消耗。
这是错的!看过 epoll 内核源码的都知道,压根就没有使用共享内存这个玩意。你可以从下面这份代码看到,epoll_wait 实现的内核代码中调用了put_user 函数,这个函数就是将数据从内核拷贝到用户空间。
3.epoll的工作流程
3.1.创建epoll对象
如下图所示,当某个进程调用epoll_create 方法时,内核会创建一个 eventpoll 对象(也就是程序中 epfd 所代表的对象)。
eventpoll 对象也是文件系统中的一员,和socket一样,它也会有等待队列。创建一个代表该epoll的 eventpoll 对象是必须的,因为内核要维护“就绪列表”等数据,“就绪列表”可以作为 eventpoll 的成员。
3.2.维护监视列表
创建epoll对象后,可以用epoll_ctl 添加或删除所要监听的socket。以添加socket为例。
如上图,如果通过epoll_ctl 添加sock1、sock2 和sock3 的监视,内核会将 eventpoll 添加到这三个socket的等待队列中。当socket收到数据后,中断程序会操作 eventpoll 对象,而不是直接操作进程。
3.3.接收数据
当socket收到数据后,中断程序会给 eventpoll 的“就绪列表”添加socket引用。
如上图展示的是sock2 和sock3 收到数据后,中断程序让rdlist 引用这两个socket。
eventpoll 对象相当于socket和进程之间的中介,socket的数据接收并不直接影响进程,而是通过改变 eventpoll 的就绪列表来改变进程状态。
当程序执行到epoll_wait 时,如果rdlist 已经引用了socket,那么epoll_wait 直接返回,如果 rdlist 为空,阻塞进程。
3.4.阻塞和唤醒进程
假设计算机中正在运行进程 A 和进程 B,在某时刻进程 A 运行到了epoll_wait 语句。
如上图所示,内核会将进程 A 放入 eventpoll 的等待队列中,阻塞进程。
当socket接收到数据,中断程序一方面修改rdlist,另一方面唤醒 eventpoll 等待队列中的进程,进程 A 再次进入运行状态(如下图)。
也因为rdlist 的存在,进程 A 可以知道哪些socket发生了变化。
4.实例代码
下面是一个使用 epoll
的示例代码,演示了如何创建 epoll
实例、注册文件描述符、等待事件和处理事件。此示例是一个简单的 TCP 服务器,能够接受客户端连接并处理数据。
#include <stdio.h>
#include <ctype.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <errno.h>// 设置文件描述符为非阻塞
void set_nonblocking(int fd) {int flags = fcntl(fd, F_GETFL, 0);if (flags == -1) {perror("fcntl F_GETFL 错误");exit(1);}if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {perror("fcntl F_SETFL 错误");exit(1);}
}// 服务器主函数
int main(int argc, const char* argv[])
{// 创建监听套接字int lfd = socket(AF_INET, SOCK_STREAM, 0);if(lfd == -1){perror("socket 错误");exit(1);}// 设置监听套接字为非阻塞set_nonblocking(lfd);// 绑定服务器地址和端口struct sockaddr_in serv_addr;memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(9999); // 监听端口9999serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 绑定所有网络接口的IP地址// 设置端口复用int opt = 1;setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));// 将套接字绑定到指定地址int ret = bind(lfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));if(ret == -1){perror("绑定错误");exit(1);}// 开始监听连接请求ret = listen(lfd, 64);if(ret == -1){perror("监听错误");exit(1);}// 创建一个 epoll 实例int epfd = epoll_create(100);if(epfd == -1){perror("epoll_create 错误");exit(1);}// 将监听套接字 lfd 加入 epoll 实例,监听读事件,使用ET模式struct epoll_event ev;ev.events = EPOLLIN | EPOLLET; // 监听读事件,ET模式ev.data.fd = lfd; // 数据是监听套接字 lfdret = epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);if(ret == -1){perror("epoll_ctl 错误");exit(1);}// 用于存放触发事件的数组struct epoll_event evs[1024];int size = sizeof(evs) / sizeof(struct epoll_event);// 进入事件处理循环while(1){// 等待事件触发int num = epoll_wait(epfd, evs, size, -1);if(num == -1){perror("epoll_wait 错误");exit(1);}// 处理所有触发的事件for(int i = 0; i < num; ++i){int curfd = evs[i].data.fd; // 获取当前事件对应的文件描述符// 如果是监听套接字 lfd 有事件发生,表示有新连接if(curfd == lfd){// 接受所有新连接while (1) {int cfd = accept(lfd, NULL, NULL);if(cfd == -1){if (errno == EAGAIN || errno == EWOULDBLOCK) {// 所有连接都已处理break;} else {perror("accept 错误");continue;}}// 设置新连接为非阻塞set_nonblocking(cfd);// 将新连接 cfd 添加到 epoll 实例中监听其读事件,使用ET模式ev.events = EPOLLIN | EPOLLET;ev.data.fd = cfd;ret = epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);if(ret == -1){perror("epoll_ctl-accept 错误");exit(1);}printf("新连接 %d 加入\n", cfd);}}else{// 处理已连接套接字的数据收发char buf[1024];int len;// 使用循环确保将缓冲区中所有数据读取完毕while ((len = recv(curfd, buf, sizeof(buf), 0)) > 0) {printf("客户端 %d 说: %s", curfd, buf);send(curfd, buf, len, 0);memset(buf, 0, sizeof(buf));}if(len == -1 && (errno != EAGAIN && errno != EWOULDBLOCK)){perror("recv 错误");// 出错时关闭连接,并从 epoll 实例中删除epoll_ctl(epfd, EPOLL_CTL_DEL, curfd, NULL);close(curfd);}else if(len == 0){// 客户端断开连接printf("客户端 %d 已断开连接\n", curfd);epoll_ctl(epfd, EPOLL_CTL_DEL, curfd, NULL);close(curfd);}}}}close(lfd);return 0;
}
相关文章:
【Linux | IO多路复用】epoll的底层原理详解
epoll 是一种高效的 I/O 多路复用机制,广泛用于 Linux 系统中,用于处理大量并发的文件描述符。它比传统的 select 和 poll 方法具有更好的性能,特别是在处理大量并发连接时。 1.epoll的设计思路 epoll是在select 出现 N 多年后才被发明的&a…...
npm run serve 提示异常Cannot read property ‘upgrade‘ of undefined
npm run serve 提示Cannot read property ‘upgrade’ of undefined 一般是proxy的target代理域名问题导致的,如下: 解决方案: proxy: { “/remoteDealerReportApi”: { target: ‘http://demo-.com.cn’, //此域名有问题,会导致…...
Muggle OCR 是一个高效的本地OCR(光学字符识别)模块
Muggle OCR 是一个高效的本地OCR(光学字符识别)模块,专为“麻瓜”设计,用于简化文本识别的过程。这个模块特别适用于处理印刷文本和解析验证码1。 以下是一些关于 Muggle OCR 的主要特点和使用方法: 特点:…...
【SpringBoot】万字源码解析——启动流程
Spring Boot启动流程 Spring Boot 的入口类: SpringBootApplication public class IntelGradingApplication {public static void main(String[] args) {SpringApplication.run(IntelGradingApplication.class, args);} }Spring Boot 的启动过程可以分为两方面&am…...
Nginx 配置初步 下
Nginx 配置初步(下) 一行代表一个指令; 每个指令有其上下文环境,比如 listen 指令只能在 http 指令块中出现,不能单独出现。1. Http 服务配置初步 1.1 常用指令 Nginx 的所有模块,打开模块我们就能看到模块中支持的指令。最常用…...
可视化ETL平台-Kettle的安装及简单使用
本章知识简介 主线A: 自连接查询; 主线B: 安装JDK与Kettle; 主线C: 使用Kettle工具. 本章目标: 1: 知道使用一张表可以实现自连接查询; [了解]注意: 左表、右表都是同一张表 2: 了解Kettle环境的安装流程; [了解]a.安装JDKb.安装Kettle 3: 熟悉使用kettle将txt数…...
java8 动态加载jar包至系统的classpath
1. io.test包 创建MyMain.java类,创建addJarToClasspath方法将jar包动态加载进系统的classpath中 package io.test;import java.io.File; import java.net.URL; import java.net.URLClassLoader; import java.lang.reflect.Method;public class MyMain {public st…...
C++二级题 计算好数:1数大于0数(二进制的位运算)
1、题目 若将一个正整数化为二进制数,在此二进制数中,我们将数字1的个数多于数字0的个数的这类二进制数称为好数。 例如: (13)10 (1101)2,其中1的个数为3,0的个数为1,则此数是好数; (10)10 (1…...
数字孪生城市:智慧城市的未来蓝图
在当今数字化时代,智能技术的广泛应用正在改变人们的生活和工作方式。数字孪生城市作为未来新型智慧城市演进的重要方向,数字孪生城市是一种将城市物理世界的各个方面转化为数字形式的技术,通过网络空间与物理世界之间的实时数据交换和仿真分…...
Java篇图书管理系统
目录 前言 一. 图书管理系统的核心 二. 图书管理系统基本框架 2.1 book包 2.1.1 Book(书籍类) 2.1.2 Booklist (书架类) 2.2 user包 2.2.1 User类 2.2.2 Administrator(管理员类) 2.2.3 Visitor(用户类) 2.…...
BUUCTF之web篇
第一题 [极客大挑战 2019]EasySQL 打开靶机后可以看到这是一个登陆的页面 我们可以尝试两种方式登录 弱口令爆破(burpsuite) 通过SQL注入里的万能密码来跳过账户和密码验证的过程 这里就需要万能密码aor true # 在这里单引号的作用是结束用户名或者密码…...
010——二叉树(2)线索化
引入: 问题1: n个节点的二叉树,用二叉链表存储,问在这个二叉链表中一共有 __个指针域? 其中,有 __个指针域不为NULL,__个指针域为NULL? 答:2n n-1 n1 在二叉链表中…...
鸿蒙拍照小助手02
项目文件目录 为了确保项目文件目录清晰,以下是完整的项目文件目录结构: code 拍照小助手/ │ ├── entry/ │ ├── src/ │ │ ├── main/ │ │ │ ├── js/ │ │ │ │ └── 默认/ │ │ │ │ ├── 页面/ │ │ │ │ │ ├── 主页/ │ │ │ │ │ │ ├…...
lua while循环
软考鸭微信小程序 过软考,来软考鸭! 提供软考免费软考讲解视频、题库、软考试题、软考模考、软考查分、软考咨询等服务 Lua作为一种小巧精致的语言,特别适用于嵌入其他程序提供脚本支持。在编程中,循环结构是不可或缺的一部分,而while循环则是…...
JAVA篇之类和对象
目录 一. 面向对象 1.1 面向对象和面向过程 二. 类的定义和使用 2.1 什么是类 2.2 类的定义格式 三. 类的实例化 四. this引用 4.1 this引用的作用 五. 构造方法 5.1 构造方法重载 5.2 通过this调用其他构造方法 5.3 默认初始化 结语 一. 面向对象 Java 是一门面向对…...
IO流详解_CoderLix
主要内容 File类IO流字节流字符流异常处理Properties缓冲流转换流序列化流打印流 File类 1.1 概述 java.io.File 类是文件和目录路径名的抽象表示,主要用于文件和目录的创建、查找和删除等操作。 1.2 构造方法 public File(String pathname) :通过…...
241023-RHEL非管理员安装Docker并开放指定宿主机端口部署Gitlab
A. RHEL非管理员安装Docker 要在没有管理员权限的情况下离线安装 Docker 和 Docker Compose,虽然受到一定限制,仍有一些可行的步骤可以帮助你在有限权限下完成这项任务。需要注意的是,这种方式适用于本地用户环境下的 Docker 安装࿰…...
python ubuntu安装加速
ubuntu升级python到python3.11(可能是全网最靠谱的方法,亲测有效)_ubuntu python3.11-CSDN博客 python-release安装包下载_开源镜像站-阿里云...
100种算法【Python版】第12篇——快速幂算法
本文目录 1 基本原理2 基本步骤3 数学示例4 python代码1 基本原理 快速幂算法(Fast Exponentiation)是一种高效计算整数幂的方法,尤其适用于计算大数的幂。其主要思想是利用分治法和二进制表示来减少乘法运算的次数,从而加快计算速度。 计算 x n x^n x...
Java多线程详解②(全程干货!!!)Thread Runnable
这里是Themberfue 上节主要讲完了多线程的一些基础知识,这节通过代码进一步理解多线程🫡 多线程 Java标准库中提供了Thread类,以程序员们编写多线程代码,我们可以查看官方文档进一步了解Thread的特性以及提供的接口。 类似于Sy…...
机器学习——图神经网络
图神经网络(GNN):理解复杂网络数据的有效工具 图神经网络(Graph Neural Network, GNN)是近年来机器学习领域的热门话题。GNN 以图结构数据为核心,能够高效地捕捉节点和边的复杂关系,广泛应用于社交网络、推荐系统、生…...
一、在cubemx下RTC配置调试实例测试
一、rtc的时钟有lse提供。 二、选择rtc唤醒与闹钟功能 内部参数介绍 闹钟配置 在配置时间时,注意将时间信息存储起来,防止复位后时间重新配置。 if(HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR0)! 0x55AA)//判断标志位是否配置过,没有则进…...
【Nas】X-DOC:Mac mini Docker部署中国特供版Jellyfin
【Nas】X-DOC:Mac mini Docker部署中国特供版Jellyfin 1、拉取镜像:2、启动镜像3、访问服务4、参考文档 Mac mini Docker部署中国特供版Jellyfin 1、拉取镜像: docker pull nyanmisaka/jellyfin:230901-amd64jellyfin 10.8.10版本ÿ…...
合合信息:生成式Al时代的内容安全与系统构建加速,开启智能文档的全新潜能
文章目录 写在前面图像内容安全图像篡改应用场景伪造文档/证照检测伪造人脸检测 GAI时代系统构建加速通用文档解析 合合信息 写在前面 随着人工智能技术的飞速发展,生成式AI已经悄然步入了我们的日常生活,以其强大的内容生成能力,重塑了信息…...
京东双十一高并发场景下的分布式锁性能优化
背景 在电商领域,尤其是像京东双十一这样的大促活动,系统需要处理极高的并发请求。这些请求往往涉及库存的查询和更新,如果处理不当,很容易出现库存超卖、数据不一致等问题。分布式锁作为一种有效的解决方案,能够在多…...
华为ICT题库-AI 人工智能部分
1178、以下哪个选项是华为的云端AI芯片?(云服务考点) (A)Inferentia (B)MLU100 (C)Cloud TPU (D)Ascend 910 答案:D 解析:华为的云端AI芯片被称为Ascend芯片系列,其中Ascend 910是其旗舰产品。Ascend 910…...
React Native 修改安卓应用图片和名称
在React Native(RN)项目中,修改安卓应用图标和名称通常涉及对Android原生代码的一些修改。以下是详细步骤: 修改应用图标 准备图标资源: 创建或获取你想要的图标,并确保它们符合Android的图标规范…...
普推知产:商标初审已下,商标申请通过如何高些!
近期下来一批商标注册的初步审公告通知书,一些客户对商标下证要求比较高的,普推知产商标老杨发现,要像下证高核心还是在于名称,名称起好备用的多,让商标专业人士经检索后层层过滤后提报,通过会好很多。 普推…...
HICP--2
在area 0的路由器只生成 area 0 的数据库,只在area 1 的一样。但是既在又在的生成两个 area的 LSDB 一、区域间三类LSA 在OSPF(Open Shortest Path First)协议中,区域间三类LSA(Link-State Advertisement)…...
sheng的学习笔记-AI基础-正确率/召回率/F1指标/ROC曲线
AI目录:sheng的学习笔记-AI目录-CSDN博客 分类准确度问题 假设有一个癌症预测系统,输入体检信息,可以判断是否有癌症。如果癌症产生的概率只有0.1%,那么系统预测所有人都是健康,即可达到99.9%的准确率。 但显然这样的…...
成都市建设网站首页/网店推广的重要性
单线程模型指的是,JavaScript 只在一个线程上运行。也就是说,JavaScript 同时只能执行一个任务,其他任务都必须在后面排队等待。 注意,JavaScript 只在一个线程上运行,不代表 JavaScript 引擎只有一个线程。事实上&…...
彬州市人民政府门户网站/yahoo搜索引擎入口
1.先安装IIS啊,就是都勾选上就可以了 在控制面板->程序->程序和功能,点击左边的“打开或关闭WIndows功能”,在弹出的窗口中就可以看到IIS啦; 2.ArcServer 当中竟然有一步需要Restart IIS服务!重启了之后他还有这…...
太原做app网站建设/企业培训公司
开发四年只会写业务代码,分布式高并发都不会还做程序员? Google 云服务提供了方便的文档处理平台,不过如果要进行进阶操作就比较麻烦了。最近 Google 终于推出了 Google Docs API,可以在平台上进行更复杂的文档操作。它可帮助开…...
百度制作网站推广/网络平台推广运营公司
今天踩了一堆的坑,记录一下。安装logstash的过程很不顺利https://www.elastic.co/downloads/logstash在releases版本中选择与ES相同版本下载$echo "export PATH\$PATH:/opt/app/logstash-6.3.1/bin" > /etc/profile.d/logstash.sh$cd logstash-6.3.1/a…...
北京网站建设itcask/简述网站建设流程
本文转载自公众号 Miss XY狂风暴雨来得更猛烈和突然了。谷歌宣布将“断供”华为的Android服务之后,紧张的情绪正在其他手机厂商中急速蔓延。庞大的中国智能手机产业,除了缺少核心芯片之外,操作系统同样一片空白,受制于人的问题&a…...
那个网站做代买/网站备案查询工信部官网
题目描述有三个整数a b c,由键盘输入,输出其中的最大的数。输入 一行数组,分别为a b c输出 a b c其中最大的数样例输入 10 20 30 样例输出 30#include <stdio.h> int main() {int a, b, c, max;scanf("%d %d %d", &a, &b, &a…...