Linux--多路转接之epoll
上一篇:Linux–多路转接之select
epoll
epoll
是 Linux 下多路复用 I/O 接口 select/poll 的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统 CPU 利用率。它是 Linux 下多路复用 API 的一个选择,相比 select
和 poll
,epoll
提供了更高的性能,并且使用起来也更加方便。
epoll的工作原理
eventpoll框架的核心在于它能够高效地处理多个文件描述符上的事件,避免了传统I/O多路复用机制(如select和poll)中的轮询开销。eventpoll通过以下方式实现:
- 注册文件描述符:当文件描述符被注册到eventpoll时,会创建一个epitem(eventpoll item)结构体,用于表示该文件描述符及其关心的事件类型。这个epitem会被插入到eventpoll的红黑树(rbtree)中,以便快速查找和管理。
- 等待事件发生:通过调用epoll_wait()系统调用,应用程序会在eventpoll的等待队列(wq)上等待。此时,指定的回调函数是default_wake_function,用于在事件发生时唤醒等待的线程。
- 事件通知:当被监测的文件描述符上有事件发生时,会调用ep_poll_callback()回调函数,将相应的epitem插入到eventpoll的就绪链表(rdllist)中。epoll_wait()会从这个链表中取出epitem,并将对应的事件通知给应用程序。
注意:以上操作均有系统自主完成
epoll 的相关系统调用
epoll_create()
创建一个 epoll 的句柄.
#include <sys/epoll.h> int epoll_create(int size);
size
参数用于告诉内核这个监听列表(epoll 实例)打算同时监视多少个文件描述符。
返回值:
如果调用成功,epoll_create 返回一个新的文件描述符,该描述符用于后续的 epoll_ctl()和 epoll_wait()调用。
如果调用失败,则返回 -1,并设置 errno 以指示错误原因。
epoll_ctl()
允许程序在 epoll 实例中添加、修改或删除文件描述符(file descriptors)的监听事件.
#include <sys/epoll.h> int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
参数说明
- epfd:由
epoll_create ()
函数生成的 epoll 实例的文件描述符。- op:指定要执行的操作,常用的值包括:
EPOLL_CTL_ADD
:向 epoll 实例注册新的文件描述符和事件。
EPOLL_CTL_MOD
:修改已注册的文件描述符的事件。
EPOLL_CTL_DEL
:从 epoll 实例中删除一个文件描述符。- fd:要操作的目标文件描述符,即要注册、修改或删除的文件描述符。
- event:指向 struct epoll_event 结构体的指针,该结构体包含了要注册或修改的事件信息。对于
EPOLL_CTL_DEL
操作,该参数可以为 NULL。
typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t; struct epoll_event { uint32_t events; /* 事件类型 */ epoll_data_t data; /* 与事件相关的数据 */ };
events:这是一个位掩码,用于指示发生的事件类型。常见的事件类型包括:
EPOLLIN
:表示对应的文件描述符可以进行读操作。
EPOLLOUT
:表示对应的文件描述符可以进行写操作。
EPOLLERR
:表示发生错误。
EPOLLHUP
:表示挂起(hang up)事件,比如对端关闭了连接。
EPOLLET
:将事件设置为边缘触发(Edge Triggered)模式,这是与水平触发(Level Triggered)模式相对的一种触发模式。
EPOLLONESHOT
:用于确保事件被触发一次后,除非再次使用 epoll_ctl 重新注册,否则不再接收该事件。data:这是一个联合体,可以存储与事件相关的数据。它提供了多种方式来关联事件和特定的数据或文件描述符:
ptr
:可以指向任意类型的数据,通常用于存储用户自定义的数据结构指针。
fd
:直接存储文件描述符的值,当只需要管理文件描述符时,这种方式更为直接(常用)。
u32
和u64
:分别提供了32位和64位的无符号整数存储,这些字段可以用来存储特定的值或标识符。
epoll_wait()
程序调用 epoll_wait 时,它会阻塞当前线程,直到注册在 epoll 实例上的文件描述符上有事件发生,或者超时时间到达。
#include <sys/epoll.h> int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
参数说明
epfd
:由epoll_create
函数生成的 epoll 实例的文件描述符。events
:指向struct epoll_event
数组的指针,用于存储发生的事件。当epoll_wait
返回时,该数组将被填充有发生事件的文件描述符和事件类型的信息。maxevents
:指定 events 数组的最大长度,即 epoll_wait 一次可以处理的最大事件数。timeout
:指定等待 I/O 事件发生的超时时间(毫秒)。如果设置为 -1,则epoll_wait
将无限期地等待,直到有事件发生。如果设置为 0,则 epoll_wait 将立即返回,无论是否有事件发生。如果设置为一个正整数,则 epoll_wait 将等待指定的毫秒数,如果在这段时间内有事件发生,则返回;否则返回 0,表示超时。
返回值
- 成功时,epoll_wait 返回发生事件的文件描述符数量。如果返回 0,则表示在指定的超时时间内没有事件发生。
- 如果发生错误,epoll_wait 返回 -1,并设置 errno 以指示错误原因。
epoll的工作方式
水平触发(Level Triggered, LT)
工作原理:在水平触发模式下,只要被监控的文件描述符上有可读写事件发生(即数据到达但未被读取,或可写空间可用但未被写入),epoll_wait就会通知用户程序。
如果数据到达但是没有被读取,或者可写空间可用但是没有被写入,epoll_wait会再次通知用户程序,直到相应的操作被执行。
特点:
- 通知次数:只要条件满足,就会不断地通知。
- 读写策略:可以更灵活地处理读写,不需要连续读取或写入直到遇到错误。
- 效率:由于频繁的通知,可能会引起较多的上下文切换,影响效率。
- 编程复杂度:相对容易理解和使用。
边缘触发(Edge Triggered, ET)
工作原理:边缘触发模式是一种更高效的触发方式。在这种模式下,epoll_wait仅在状态变化时通知用户程序一次,比如从无数据到有数据,或者从不可写变为可写。
当收到一个可读事件时,需要一直读取数据,直到返回EAGAIN错误(表示没有更多数据可读)。同样,对于可写事件,需要一直写入数据,直到不能再写入为止。
知次数:只在状态发生变化时通知一次。
读写策略:读操作需要一直进行,直到遇到EAGAIN错误;写操作也是如此,需要一直写,直到无法继续写入。
效率:减少了系统调用的次数,提高了应用程序的效率。
编程复杂度:要求程序必须更加小心地处理事件,以避免错过任何事件,这使得编程变得更加复杂。
主要区别
. | 水平触发(LT) | 边缘触发(ET) |
---|---|---|
通知次数 | 只要条件满足,就会不断地通知 | 只在状态发生变化时通知一次 |
读写策略 | 可以更灵活地处理读写 | 读操作需要一直进行,直到遇到EAGAIN错误;写操作也是如此 |
效率 | 可能会引起较多的上下文切换,影响效率 | 减少了系统调用的次数,提高了应用程序的效率 |
编程复杂度 | 相对容易理解和使用 | 要求程序必须更加仔细地处理事件,以避免错过任何事件,编程复杂度高 |
epoll_server实例(LT方式)
我们将对上一篇的select_server进行一定的修改即可;
epollServer.hpp
#pragma once
#include <iostream>
#include <string>
#include <memory>
#include <sys/epoll.h>
#include "InetAddr.hpp"
#include "Socket.hpp"
#include "Log.hpp"using namespace socket_ns;class EpollServer
{const static int gnum = 64;
public:EpollServer(uint16_t port = 8080): _port(port),_listensock(std::make_unique<TcpSocket>()),_epfd(-1){// 1. 创建listensockInetAddr addr("0", _port);//0表示任意ip_listensock->BuildListenSocket(addr);// 2. 创建epoll模型_epfd = ::epoll_create(128);//返回值是epoll的fdif (_epfd < 0){LOG(FATAL, "epoll_create error\n");exit(5);}LOG(DEBUG, "epoll_create success, epfd: %d\n", _epfd);// 3. 只有一个listensock, listen sock 关心的事件:读事件struct epoll_event ev; //结构体包含事件的信息ev.events = EPOLLIN;//事件可读ev.data.fd = _listensock->SockFd(); //将listenfd放入到信息中epoll_ctl(_epfd, EPOLL_CTL_ADD, _listensock->SockFd(), &ev);}//对事件的处理void handlerEvent(int num){for (int i = 0; i < num; i++)//可处理多个事件{// 逐一将事件取出uint32_t revents = _revs[i].events;int sockfd = _revs[i].data.fd;// 读事件就绪if (revents & EPOLLIN){if (sockfd == _listensock->SockFd())//监听fd,表示将创建连接fd{InetAddr clientaddr;int newfd = _listensock->Accepter(&clientaddr); // 不会被阻塞,事件已知被响应if (newfd < 0)continue;// 获取新链接成功struct epoll_event ev;ev.events = EPOLLIN;ev.data.fd = newfd;epoll_ctl(_epfd, EPOLL_CTL_ADD, newfd, &ev);//将新事件添加到epoll中LOG(DEBUG, "_listensock ready, accept done, epoll_ctl done, newfd is: %d\n", newfd);}else//表示连接的fd有事情发生{char buffer[1024];ssize_t n = ::recv(sockfd, buffer, sizeof(buffer), 0); //接收客户端数据if (n > 0){LOG(DEBUG, "normal fd %d ready, recv begin...\n", sockfd);buffer[n] = 0;std::cout << "client say# " << buffer << std::endl;std::string echo_string = "server echo# ";echo_string += buffer;::send(sockfd, echo_string.c_str(), echo_string.size(), 0);//将结果返回}else if (n == 0)//表示连接已被断开,没有断开无数据传输将阻塞于epoll{LOG(DEBUG, "normal fd %d close, me too!\n", sockfd);// 对端连接关闭了::epoll_ctl(_epfd, EPOLL_CTL_DEL, sockfd, nullptr);::close(sockfd);}else{::epoll_ctl(_epfd, EPOLL_CTL_DEL, sockfd, nullptr); // 这里表示将epoll中的sockfd删除::close(sockfd);//而fd是拷贝进去的,只是将拷贝在epoll中的fd擦除,对应的fd事件还没有被关闭}}}}}//循环执行void Loop(){int timeout = -1;//表示epoll阻塞等待,直到有事件发生while (true){int n = ::epoll_wait(_epfd, _revs, gnum, timeout);//用于等待事件的发生switch (n){case 0://规定时间内无事件发生LOG(DEBUG, "epoll_wait timeout...\n");break;case -1://发生错误LOG(DEBUG, "epoll_wait failed...\n");break;default://有事件发生LOG(DEBUG, "epoll_wait haved event ready..., n : %d\n", n);handlerEvent(n);break;}}}~EpollServer(){_listensock->Close();//关闭listen_fdif (_epfd >= 0)//关闭epoll的fd::close(_epfd);}
private:uint16_t _port; //端口号std::unique_ptr<Socket> _listensock; //监听sockint _epfd; //epoll的fdstruct epoll_event _revs[gnum];//事件数组,存储对应事件
};
_epfd
: epoll是Linux底层中一种高效的I/O多路复用机制,所以也是属于一种事件,需要在用户层创建对应的文件描述符用于表示对epoll的创建;
_revs
: 虽然在底层有红黑树来进行存储对应的事件,但是在用户层是无法了解到底层的存储执行的,因为epoll的底层全由系统来完成的,用户无法操作,所以还需要一个事件数组来存储对应的事件。
初始化:
128是设置这次的最大事件管理数量,相比于select来说他是无上限的,比较灵活;
对于事件的控制 :将事件信息包含在ev结构体中即可;
Loop:
这里我们将事件数组放入到函数中,当epoll_wait
返回时,该数组将被填充有发生事件的文件描述符和事件类型的信息。这样就不用我们手动添加到事件数组中。
正是因为底层的红黑树会先存储着对应的事件信息,当被监测的文件描述符上有事件发生时,将相应的epitem插入到eventpoll的就绪链表(rdllist)中。epoll_wait()会从这个链表中取出epitem,放到_revs中,所以调用该函数会存到事件数组中。
handlerEvent:
EPOLLIN是0x001, revents如果对应位上是可读的(如:0x003)那么就能表示读事件就绪了;
main.cc
#include "epollServer.hpp"
#include "Log.hpp"#include <iostream>
#include <memory>// ./selectserver port
int main(int argc, char *argv[])
{if (argc != 2){std::cout << "Usage: " << argv[0] << " port" << std::endl;return 0;}uint16_t port = std::stoi(argv[1]);EnableScreen();std::unique_ptr<EpollServer> svr = std::make_unique<EpollServer>(port);svr->Loop();return 0;
}
注:ET模式相对来说比较复杂,需要涉及到非阻塞的程序,等下一篇Reactor再详细展示。
epoll的优点
- 支持水平触发(LT)和边缘触发(ET)
- 接口简单易用
- 没有最大文件描述符数量的限制 :select 和 poll 都有文件描述符数量的限制,而 epoll 则没有。
- 只管理“活跃”的连接:epoll 会检查注册在其上的所有 socket,只将那些真正活跃的 socket 返回给用户,即减少了无效的等待时间。
- 高效处理大量并发连接:epoll能够高效地处理大量并发连接,尤其适用于只有少量活跃连接的大量并发场景。它通过内核与用户空间共享一个事件表来跟踪所有需要监控的文件描述符,当文件描述符的状态发生变化时,内核会通知用户空间,从而避免了传统方法中的线性扫描。
- 提高CPU利用率:epoll在等待事件就绪时,如果就绪队列中没有事件,会主动让出CPU,从而提高了CPU的利用率。这使得epoll在处理大量并发连接时能够更加高效地利用系统资源。
相关文章:

Linux--多路转接之epoll
上一篇:Linux–多路转接之select epoll epoll 是 Linux 下多路复用 I/O 接口 select/poll 的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统 CPU 利用率。它是 Linux 下多路复用 API 的一个选择,相比 select 和 poll,…...

自动化工具Nico,从零开始干掉Appium,移动端自动化测试框架实现
这篇将用较短的篇幅给大家介绍我是如何实现iOS和Android的inspector(元素审查工具)的。 实现原理 为了更方便的显示UI界面,且更容易制作,我选择了使用web端来承载整个元素树展示。同时我选用Flask一次性梭哈前后端(因…...

Fast CRC32
链接: Fast CRC32 Error Checking Real life data tends to get corrupted because machines (and humans) are never as reliable as we wish for. One efficient way is make sure your data wasnt unintendedly modifiied is to generate some kind of hash. T…...

生成一个带有二维数据和对应标签的螺旋形数据集(非线性可分数据集)的代码解析
def create_dataset():np.random.seed(1)m 400 # 数据量N int(m/2) # 每个标签的实例数D 2 # 数据维度X np.zeros((m,D)) # 数据矩阵Y np.zeros((m,1), dtypeuint8) # 标签维度a 4 for j in range(2):ix range(N*j,N*(j1))t np.linspace(j*3.12,(j1)*3.12,N) np.rando…...

PHP unset() 函数的作用
PHP 中的 unset() 函数用于销毁指定的变量。具体来说,它会解除变量名与其数据之间的关联,从而释放该变量所占用的内存。不过需要注意的是,unset() 并不是删除变量的内容,而是取消对变量名的引用。如果变量是数组中的某个元素或者对…...

长篇故事可视化方法Story-Adapter:能够生成更高质量、更具细腻交互的故事图像,确保每一帧都能准确地传达故事情节。
今天给大家介绍一个最新的长篇故事可视化方法Story-Adapter,它的工作原理可以想象成一个画家在创作一幅长画卷。首先,画家根据故事的文本提示画出初步的图像。这些图像就像是画卷的草图。接下来,画家会不断回顾这些草图,逐步添加细…...

C++基础面试题 | 什么是C++中的运算符重载?
文章目录 回答重点:示例: 运算符重载的基本规则和注意事项: 回答重点: C的运算符重载是指可以为自定义类型(如类或结构体)定义运算符的行为,使其像内置类型一样使用运算符。通过重载运算符&…...

深入 IDEA 字节码世界:如何轻松查看 .class 文件?
前言: 作为一名 Java 开发者,理解字节码对于优化程序性能、调试错误以及深入了解 JVM 运行机制非常重要。IntelliJ IDEA 作为最流行的开发工具之一,为开发者提供了查看 .class 文件字节码的功能。在本文中,我将带你一步步探索如何…...

NodeJS 利用代码生成工具编写GRPC
生成的 gRPC 代码优点 自动化和效率: 减少手动编码:生成代码自动处理了消息的序列化和反序列化、服务接口的定义等,减少了手动编码的工作量。一致性:生成的代码确保了客户端和服务器之间的一致性,避免了手动编码可能带来的错误。跨语言支持: 多语言兼容:gRPC 支持多种编…...

uni-app基础语法(一)
我们今天的学习目标 基础语法1. 创建新页面2.pages配置页面3.tabbar配置4.condition 启动模式配置 基础语法 1. 创建新页面 2.pages配置页面 属性类型默认值描述pathString配置页面路径styleObject配置页面窗口表现,配置项参考pageStyle 我们来通过style修改页面的…...

Linux:进程控制(三)——进程程序替换
目录 一、概念 二、使用 1.单进程程序替换 2.多进程程序替换 3.exec接口 4.execle 一、概念 背景 当前进程在运行的时候,所执行的代码来自于自己的源文件。使用fork创建子进程后,子进程执行的程序中代码内容和父进程是相同的,如果子进…...

LeetCode279:完全平方数
题目链接:279. 完全平方数 - 力扣(LeetCode) 代码如下 class Solution { public:int numSquares(int n) {vector<int> dp(n 1, INT_MAX);dp[0] 0;for(int i 1; i * i < n; i){for(int j i * i; j < n; j){dp[j] min(dp[j …...

python爬虫--某动漫信息采集
python爬虫--tx动漫 一、采集主页信息二、采集详情页信息三、代码供参考一、采集主页信息 略。 二、采集详情页信息 如上图所示,使用xpath提取详情页的标题、作者、评分、人气、评论人数等数据。 三、代码供参考 import csv import time import random import requests fr…...

使用Rollup.js快速开始构建一个前端项目
Rollup 是一个用于 JavaScript 项目的模块打包器,它将小块代码编译成更大、更复杂的代码,例如库或应用程序。Rollup 对代码模块使用 ES6 模块标准,它支持 Tree-shaking(摇树优化),可以剔除那些实际上没有被…...

10.15学习
1.程序开发的步骤 定义程序的目标→设计程序→编写代码(需要选择语言,一种语言对应一种编译器)→编译→运行程序→测试和调试程序→维护和修改程序 2.ANSI/ISO C标准 1989年ANSI批准通过,1990年ISO批准通过,因此被称…...

mongodb-7.0.14分片副本集超详细部署
mongodb介绍: 是最常用的nosql数据库,在数据库排名中已经上升到了前六。这篇文章介绍如何搭建高可用的mongodb(分片副本)集群。 环境准备 系统系统 BC 21.10 三台服务器:192.168.123.247/248/249 安装包:…...

C++运算出现整型溢出
考虑如下代码: int aINT_MAX; int b 1; long c ab; 这段代码没有编过! 原因是a和b都是int型,相加之后会溢出。 请记住,c语言没有赋值,只有表达式,右侧会存在一个暂存的int保存ab的值,而明…...

LeetCode岛屿数量
题目描述 给你一个由 1(陆地)和 0(水)组成的的二维网格,请你计算网格中岛屿的数量。 岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。 此外,你可以假设该网…...

Karmada核心概念
以下内容为翻译,原文地址 Karmada 是什么? | karmada 一、Karmada核心概念 一)什么是Karmada 1、Karmada:开放,多云,多集群Kubernetes业务流程 Karmada (Kubernetes Armada)是一个Kubernetes管理系统&…...

Rust 与生成式 AI:从语言选择到开发工具的演进
在现代软件开发领域,Rust 语言正在逐步崭露头角,尤其是在高性能和可靠性要求较高的应用场景。与此同时,生成式 AI 的崛起正在重新塑造开发者的工作方式,从代码生成到智能调试,生成式 AI 的应用正成为提升开发效率和质量…...

Python爬虫高效数据爬取方法
大家好!今天我们来聊聊Python爬虫中那些既简洁又高效的数据爬取方法。作为一名爬虫工程师,我们总是希望用最少的代码完成最多的工作。下面我ll分享一些在使用requests库进行网络爬虫时常用且高效的函数和方法。 1. requests.get() - 简单而强大 requests.get()是我们最常用的…...

C语言之扫雷小游戏(完整代码版)
说起扫雷游戏,这应该是很多人童年的回忆吧,中小学电脑课最常玩的必有扫雷游戏,那么大家知道它是如何开发出来的吗,扫雷游戏背后的原理是什么呢?今天就让我们一探究竟! 扫雷游戏介绍 如下图,简…...

Spring WebFlux 响应式概述(1)
1、响应式编程概述 1.1、响应式编程介绍 1.1.1、为什么需要响应式 传统的命令式编程在面对当前的需求时的一些限制。在应用负载较高时,要求应用需要有更高的可用性,并提供低的延迟时间。 1、Thread per Request 模型 比如使用Servlet开发的单体应用&a…...

Unity游戏通用框架——事件的订阅和发布(观察者模式)
在游戏开发的基本思想中,逻辑与表现的分离极为重要,相互之间并不关心具体实现,只注册对应的事件,有事件发生时才调用相应的函数 事件管理器 using System.Collections; using System.Collections.Generic;public class event_ma…...

将 Ubuntu 系统中的 **swap** 空间从 2GB 扩展到 16GB
要将 Ubuntu 系统中的 swap 空间从 2GB 扩展到 16GB,可以按照以下步骤操作: 1. 关闭现有 Swap 文件 首先需要禁用当前的 swap 文件,以便重新调整其大小。 sudo swapoff -a2. 删除旧的 Swap 文件 假设当前的 swap 文件位于 /swapfile&…...

流程图 LogicFlow
流程图 LogicFlow 官方文档:https://site.logic-flow.cn/tutorial/get-started <script setup> import { onMounted, ref } from vue import { forEach, map, has } from lodash-es import LogicFlow, { ElementState, LogicFlowUtil } from logicflow/core …...

Mac通过键盘选取内容
问题: 我们在使用键盘的时候经常懒得动手去拿鼠标了,并且熟练使用键盘可以提高我们的工作效率,比如在我们需要复制内容的时候,可以仅仅通过键盘来选取想要的内容; 解决: 将鼠标光标移动到想要选取的内容…...

如何通过OpenCV实现图像融合拼接?
图像拼接的意义 2024年了,谈论图像拼接,不算新事物,我们这里探讨图像拼接,主要探讨图像拼接的意义、难点和大概的实现思路。图像拼接可以突破设备视野限制,通过拼接低分辨率图像获得高分辨率图像。 扩展视野ÿ…...

Qt5.14.2 安装详细教程(图文版)
Qt 是一个跨平台的 C 应用程序开发框架,主要用于开发图形用户界面(GUI)程序,但也支持非 GUI 程序的开发。Qt 提供了丰富的功能库和工具,使开发者能够在不同平台上编写、编译和运行应用程序,而无需修改代码。…...

深圳市步步精科技有限公司荣获发明专利,彰显技术研发实力
2024年8月13日,深圳市步步精科技有限公司(BBJconn)正式获得了其新开发的防水连接器专利,授权公告号为CN 118352837 B。这项技术的突破标志着公司在连接器领域的持续创新,进一步巩固了其行业领先地位。 专利技术概述 此…...