Linux--多路转接之epoll

上一篇:Linux–多路转接之select

epoll

epoll 是 Linux 下多路复用 I/O 接口 select/poll 的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统 CPU 利用率。它是 Linux 下多路复用 API 的一个选择,相比 selectpollepoll 提供了更高的性能,并且使用起来也更加方便。

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:直接存储文件描述符的值,当只需要管理文件描述符时,这种方式更为直接(常用)。
    u32u64:分别提供了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在处理大量并发连接时能够更加高效地利用系统资源。

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

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

相关文章

Linux性能调优,还可以从这些方面入手

linux是目前最常用的操作系统&#xff0c;下面是一些常见的 Linux 系统调优技巧&#xff0c;在进行系统调优时&#xff0c;需要根据具体的系统负载和应用需求进行调整&#xff0c;并进行充分的测试和监控&#xff0c;以确保系统的稳定性和性能。同时&#xff0c;调优过程中要谨…

【云原生技术】Docker容器进阶知识

文章目录 namespace概述一、namespace的基本概念二、namespace的主要作用三、namespace的类型四、namespace的操作五、namespace在容器技术中的应用 cgroup一、cgroup的基本概念二、cgroup的主要功能三、cgroup的子系统介绍四、cgroup的应用场景五、cgroup的使用与管理 cgroup和…

.ts文件编译为.js文件

.ts文件如何编译为.js文件 首先安装了tsc $ npm install -g typescript可以使用如下命令检查是否安装tsc,出现版本号则说明安装成功 tsc -v创建.ts文件 创建 1.ts&#xff0c;编写代码如下&#xff1a; function test(a:string):string{return a }编译为.js文件 执行如下…

Spring Cloud环境搭建

一.开发环境推荐 JDK建议使用JDK17。 因为SpringCloud是基于SpringBoot进行开发的&#xff0c;SpringBoot3.X以下的版本&#xff0c;Spring官方已经不再维护了&#xff08;还可以继续使用&#xff09;&#xff0c;SpringBoot3.X的版本使用的JDK版本基线是17&#xff0c;而且1…

IPv 4

IP协议 网络层主要由IP&#xff08;网际协议&#xff09;和ICMP&#xff08;控制报文协议&#xff09;构成&#xff0c;对应OSI中的网络层&#xff0c;网络层以实现逻辑层面点对点通信为目的。目前应用最广泛的IP协议为IPv4 基本概念给出 主机&#xff1a;配有IP地址但不具有路…

live2d 实时虚拟数字人形象页面显示,对接大模型

live2dSpeek 测试不用gpu可以正常运行 https://github.com/lyz1810/live2dSpeek 运行的话还需要额外下载https://github.com/lyz1810/edge-tts支持语音 ## 运行live2dSpeek >npm install -g http-server >http-server . ## 运行edge-tts python edge-tts.py

SpringMVC(看这一篇就够了)

目录&#xff1a; SpringMVC什么是MVC模型SpringMVC案例SpringMVC执行流程SpringMVC封装参数简单数据类型简单对象关联对象简单数据类型集合Map集合参数类型转换器编码过滤器Servlet原生对象 SpringMVC处理响应视图解析器返回值为void返回值为ModelAndView向request域设置数据向…

止步阿里一面。。。

时间过的真快&#xff0c;转眼间国庆已经过去一周了&#xff0c;又到了新的一周&#xff0c;继续分享最新的面经。 今天分享的是粉丝在阿里巴巴的一面&#xff0c;考察了数据库、redis、kafka、ES和项目&#xff0c;数据库和redis不用多说&#xff0c;项目必用面试必考&#x…

【Python时序数据系列】基于LSTM模型实现时序数据二分类(案例+源码)

这是我的第366篇原创文章。 一、引言 前面我介绍了单变量时序预测和多变量时序预测&#xff0c;都是回归任务。 相关链接&#xff1a; 时序预测系列文章 本文将介绍时序分类任务-基于LSTM模型进行时序数据二分类。 二、实现过程 2.1 准备数据 df1 pd.read_table("t…

python yolov8半自动标注

首先标注一部分图片&#xff0c;进行训练&#xff0c;生成模型&#xff0c;标注文件为xml方便后面统一做处理。 1、标注数据&#xff08;文件为xml, 转为txt用于训练&#xff0c;保留xml标签文件&#xff09; 2、模型训练&#xff08;训练配置、训练代码、&#xff09; 3、使用…

ShardingJDBC分库分表实战

目录 一、第一个分库分表的案例 1、快速搭建基础JDBC应用 2、引入ShardingJDBC快速实现分库分表 二、理解分库分表的核心概念 1、ShardingSphere分库分表的核心概念 2、垂直分片和水平分片 三、ShardingJDBC常见数据分片策略实战 1、INLINE简单分片 2、STANDARD标准分片…

ubuntu下实时查看CPU,内存(Mem)和GPU的利用率

一、实时查看CPU和内存&#xff08;Mem&#xff09;利用率 htop官网&#xff1a;htop - an interactive process viewer sudo apt-get install htop htop ①. 顶部状态栏&#xff08;System Metrics Overview&#xff09; 这个区域显示系统的全局资源使用情况&#xff0c;包括…

深入解析 Harris 角点检测算法:从孔径问题到响应函数的完整推导

在图像处理中&#xff0c;角点是非常重要的特征。为了快速、准确地检测角点&#xff0c;Harris 提出了 Harris 角点检测算法&#xff0c;它基于局部窗口内图像梯度的变化来判断角点。本文将从最基础的孔径问题&#xff08;Aperture Problem&#xff09;入手&#xff0c;通过泰勒…

MeterSphere接口自动化平台调试

1。后置脚本节目 //导入json包 import org.json.*; import com.decode.DecodeMain; String responseprev.getResponseDataAsString(); String result DecodeMain.DecodeUtil(response); log.info(“获取批次账单id result:”result); //转换为Object对象类型 JSONObject data_…

机器学习:知识蒸馏(Knowledge Distillation,KD)

知识蒸馏&#xff08;Knowledge Distillation&#xff0c;KD&#xff09;作为深度学习领域中的一种模型压缩技术&#xff0c;主要用于将大规模、复杂的神经网络模型&#xff08;即教师模型&#xff09;压缩为较小的、轻量化的模型&#xff08;即学生模型&#xff09;。在实际应…

UE5 C++: 插件编写06 | 移动文件时自动Fix up redirectors

目录 前言&#xff1a; 本文内容&#xff1a; WHY WHAT HOW 详细步骤 代码解析 1. Build.cs file中 2. QuickAssetAction.cpp中 IMPORTANT NOTES 中文解释&#xff1a; 使用 AssetToolsModule 来修复重定向器 使用 AssetRegistryModule 来过滤所有重定向器 使用 FMo…

利士策分享,美国“假旗”行动,是否成为了网络空间的阴霾?

利士策分享&#xff0c;美国“假旗”行动&#xff0c;是否成为了网络空间的阴霾? 在当今这个信息化时代&#xff0c;网络空间已经成为国家间竞争与合作的重要领域。然而&#xff0c;美国却频繁采取一种名为“假旗行动”的卑劣手段&#xff0c;污染全球网络空间&#xff0c;给世…

Java 二分查找算法详解及通用实现模板案例示范

1. 引言 二分查找&#xff08;Binary Search&#xff09;是一种常见的搜索算法&#xff0c;专门用于在有序数组或列表中查找元素的位置。它通过每次将搜索空间缩小一半&#xff0c;从而极大地提高了查找效率。相比于线性查找算法&#xff0c;二分查找的时间复杂度为 O(log n)&…

Arthas常用的命令(三)--monitor、jad 、stack

monitor&#xff1a;监控方法的执行情况 监控指定类中方法的执行情况 用来监视一个时间段中指定方法的执行次数&#xff0c;成功次数&#xff0c;失败次数&#xff0c;耗时等这些信息 参数说明 方法拥有一个命名参数 [c:]&#xff0c;意思是统计周期&#xff08;cycle of ou…

linux线程 | 同步与互斥(上)

前言&#xff1a;本节内容主要是线程的同步与互斥。 本篇文章的主要内容都在讲解互斥的相关以及周边的知识。大体的讲解思路是通过数据不一致问题引出锁。 然后谈锁的使用以及申请锁释放锁的原子性问题。 那么&#xff0c; 废话不多说&#xff0c; 现在开始我们的学习吧&#x…