Linux网络:多路转接 epoll

  • 一、epoll三个接口函数
    • 1、epoll_create
    • 2、epoll_ctl
    • 3、epoll_wait
  • 二、epoll的工作原理
  • 三、epoll的echo_server
    • 1、EpollServer类
    • 2、构造函数
    • 3、事件循环
    • 4、事件派发
    • 5、事件处理
    • 6、测试
  • 四、LT和ET模式
    • 1、LT
    • 2、ET
  • 五、项目代码

一、epoll三个接口函数

多路转接是非常高效的一种IO模型,它可以在同一时间等待多个套接字,从而提高效率。Linux提供了三种系统调用实现多路转接:select、poll、epoll。本博客讲解epoll。

epoll是经过改进的poll,在Linux 2.5.44版本引入内核,并认为是Linux2.6最好的多路转接实现方案

1、epoll_create

epoll_create用于创建一个epoll模型,需要头文件<sys/epoll.h>,函数原型如下:

int epoll_create(int size);

此处的参数size已经被废弃,可以填入大于0的任何值。

返回值是一个文件描述符,通过这个文件描述符,可以操控Linux底层创建的epoll(主要是那两个模型)

2、epoll_ctl

epoll_ctl用于控制epoll模型,需要头文件<sys/epoll.h>,函数原型如下:

int epoll_ctl(int epfd, int op, int fd,struct epoll_event *_Nullable event);

参数:

  1. epfd:通过epoll_create获取到的文件描述符
  2. op:本次执行的操作,传入宏
  3. fd:要监听的文件的文件描述符
  4. event:对文件要执行的监听类型

其中:op操作包括

  • EPOLL_CTL_ADD新增一个文件描述符到epoll中
  • EPOLL_CTL_MOD修改一个epoll中的文件描述符
  • EPOLL_CTL_DEL:从epoll中删除一个文件描述符

其中event的类型是struct epoll_event*,该结构体定义如下:

struct epoll_event {uint32_t      events;  /* Epoll events */epoll_data_t  data;    /* User data variable */
};union epoll_data {void     *ptr;int       fd;uint32_t  u32;uint64_t  u64;
};

这个结构体中,包含events和data两个字段:

  1. events:一个位图,存储要监听的事件以及一些其它配置
  2. data:当epoll返回时,携带的数据

此处的data是一个联合体,它可以存储四种类型的数据:ptr指针,int文件描述符,uint32_t和uint64_t的整型。

其中events包括:

  • EPOLLIN:监听读事件
  • EPOLLOUT:监听写事件
  • EPOLLERR:监听错误事件
  • EPOLLHUP:文件描述符被关闭
  • EPOLLONESHOT:只监听一次事件,本次监听完毕,文件描述符被从epoll中移除

当一个epoll返回已经就绪的文件时,用户其实无法得知这个文件的描述符,那么就可以通过这个data.fd获取到文件描述符,当然也可以通过其它的参数,传递更复杂的信息。

3、epoll_wait

epoll_wait用于等待epoll模型中的文件就绪,需要头文件<sys/epoll.h>,函数原型如下:

int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);

参数:

  • epfd:通过epoll_create获取到的文件描述符
  • events:输出型参数,指向一个epoll_event就绪数组,获取之前通过epoll_ctl传入的events
  • maxevents:用户传入的events数组的最大长度(就绪事件个数)
  • timeout:超时时间,以ms为单位

此处用户要传入一个epoll_event数组,这个数组用于存储本次就绪的所有文件的epoll_event,为了防止越界,所以还要传入maxevents。

就是说,epoll的使用方式是通过epoll_wait获取就绪的文件,这些文件存储到epoll_event数组中。

函数返回,用户可以遍历数组,获取到所有就绪的文件的epoll_event结构体。

这个结构体是在epoll_ctl时传入的,从它的events字段可以得知这个文件监听的事件,从data字段可以获取之前预设的其他信息,一般会预设data.fd获取这个文件的描述符

返回值:

  • 0:超时,指定时间内没有文件就绪
  • <0:出现错误
  • >0:就绪的文件的个数

通过此处,已经可以看出epoll相比于select的优势了:

epoll返回时,把已经就绪的文件放到数组中,后续遍历数组,每一个元素都是已经就绪的文件

在select中,就绪的事件通过一张位图返回,用户需要遍历整个位图所有元素,并判断该元素是否就绪,那么就会浪费大量的时间在未就绪的文件上。

epoll返回时,不会把已经加入epoll的文件删除,而是继续监听该文件

这是另一大优势,在select中,每次返回都会重置用户传入的位图,因此用户在每次轮询都要重新把文件描述符设置到select。

当然,用户也可以在epoll_ctl的时候,设置EPOLLONESHOT,那么这个文件被epoll返回后,就会从epoll中删除,也就是只监听一次事件

二、epoll的工作原理

在这里插入图片描述
在这里插入图片描述
Epoll的工作原理:
在这里插入图片描述
一种特殊的数据结构:
在这里插入图片描述
双层结构体:结构体嵌套结构体

在这里插入图片描述

三、epoll的echo_server

接下来使用epoll系统调用,实现一个简单的echo server

1、EpollServer类

// 多路转接:事件循环、事件派发、事件处理!
class EpollServer
{
public:// 构造函数EpollServer(uint16_t port){}// 初始化函数void InitServer(){}// 转换字符串std::string EventsToString(uint32_t events)// 事件处理:网络套接字文件void Accepter(){}// 事件处理:普通文件void HandlerIO(int fd){}// 事件派发void HandlerEvent(int n){}// 事件循环void Loop(){}// 析构~EpollServer(){}
private:uint16_t _port;std::unique_ptr<Socket> _listensock;int _epfd;  // epoll_create的返回值!epoll句柄struct epoll_event revs[num]; // 将内核epoll模型里面的就绪事件,存入revs(revs是epoll_wait的参数)
};

2、构造函数

构造函数代码如下:

// 构造函数EpollServer(uint16_t port): _port(port), _listensock(std::make_unique<TcpSocket>()){_listensock->BuildListenSocket(port); // 根据传来的port来创建监听套接字!!!// 1、epoll_create创建成功,说明底层内核已经创建好了:红黑树+就绪队列_epfd = ::epoll_create(size);// 这里的参数size>0即可if (_epfd < 0){LOG(FATAL, "epoll create fail\n");exit(-1);}LOG(INFO, "epoll create sucess ,epfd:%d\n", _epfd);}

3、事件循环

事件循环代码如下:

开启循环后,进入一个while(true)死循环,每一轮循环通过epoll_wait获取本轮循环就绪的文件:

// 事件循环void Loop(){int timeout = 1000; // 设置时限!1swhile (true){int n = ::epoll_wait(_epfd, revs, num, timeout); // epoll_wait的返回值就是就绪事件的个数switch (n){case 0:LOG(INFO, "epoll time out...\n");break;case -1:LOG(ERROR, "epoll wait fail\n");break;default:LOG(INFO, "have event happend! n:%d\n", n);HandlerEvent(n);// 事件派发break;}}}

4、事件派发

事件派发就是判断文件描述符是_listenfd还是普通的sockfd,调用不同的函数进行处理。

    // 事件派发void HandlerEvent(int n){for (int i = 0; i < n; i++){int fd = revs[i].data.fd;uint32_t revents = revs[i].events; // 具体是哪一个fd里面的什么事件就绪了!?LOG(INFO, "%d 上面的有事件就绪了,具体事件是:%s\n", fd, EventsToString(revents).c_str());// 事件处理:封装!// 1、listensock就绪if (fd = _listensock->Sockfd()){Accepter();}// 2、处理普通fdelse{HandlerIO(fd);}}}

5、事件处理

void Accepter(){InetAddr addr;int sockfd = _listensock->Accepter(&addr);if (sockfd < 0){LOG(ERROR, "获取连接失败\n");return ;}LOG(INFO, "得到一个新链接:%d,客户端信息:%s:%d\n", sockfd, addr.Ip().c_str(), addr.Port());struct epoll_event ev;ev.events = EPOLLIN;ev.data.fd = sockfd;// 必须将sockfd加入到epoll里面,这样才能让epoll对提取出来的sockfd进行处理!::epoll_ctl(_epfd, EPOLL_CTL_ADD, sockfd, &ev);LOG(INFO, "add sucess to epoll,new sockfd:%d\n", sockfd);}void HandlerIO(int fd){char buffer[4096];int n = ::recv(fd, buffer, sizeof(buffer) - 1, 0);if (n > 0){buffer[n] = 0;std::cout << buffer;}else if (n == 0){LOG(INFO, "client,quit...\n");// 先将这个退出的fd从epoll中移除,再关闭fd::epoll_ctl(_epfd, EPOLL_CTL_DEL, fd, nullptr);::close(fd);}else{LOG(ERROR, "client,fail...\n");::epoll_ctl(_epfd, EPOLL_CTL_DEL, fd, nullptr);::close(fd);}}

6、测试

在这里插入图片描述

四、LT和ET模式

思考一个问题:如果用户通过epoll检测到某个socket的事件已经就绪了,但是这个用户没有处理这个事情,下一次epoll_wait还要不要返回这一个事件?

就是基于这个问题,衍生了两种epoll工作模式:LT模式与ET模式

1、LT

LT模式下,当用户没有处理事件,那么事件就一直保留在就绪链表rdlink中,每次调用epoll_wait都会返回这个事件

这种模式是epoll的默认模式

用户接收到事件后,可能某个报文太长了,一次读不完。那么LT模式下一次还会进行通知,用户可以把剩下的报文读完。但是这就可能导致一个报文,需要调用更多次的epoll_wait。

2、ET

ET模式下,当用户通过epoll_wait拿到事件后,事件直接从rdlink中删除,下一次不再进行通知

这种模式比LT更加高效,这可以从两个角度解读:

  1. 这种模式下,一个报文只需要调用一次epolll_wait,因此效率高一点
  2. 这倒逼程序员必须一次性把报文读完,那么就会更快的进行业务处理,报文响应速度也更快

这里主要是第二点比较重要,当一个报文太长了,但是ET模式下只进行一次通知。那么程序员收到通知后,就需要用一个while循环一直读取套接字,直到读不出数据为止。这样一次通知程序员就能拿到完整报文,进而更早的进行业务处理,更早响应。而且提早把数据读走,内核的缓冲区也会被空出来,接收更多的新数据。

默认情况下,从文件读取文件是阻塞的,当最后一次while循环读取不出内容了,程序就会阻塞住。因此这种情况下,要把文件读取改为非阻塞读取,如果读不出内容直接返回。

但是这也导致ET的程序会比LT更加复杂,实际开发中需要进行权衡。

五、项目代码

epoll和select代码

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

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

相关文章

uniapp 微信小程序 列表点击分享 不同的信息

<button open-type"share" plain class"item share" click.stop"shareFn(item)"><text>分享</text> </button>import {onShareAppMessage} from dcloudio/uni-applet shareObj ref({})// 将点击后的分享设置信息 关键…

C# 匿名方法详解

C# 匿名方法详解 引言 在C#编程语言中,匿名方法是使用Lambda表达式创建的没有名称的方法。它们在LINQ查询、事件处理和其他场合中非常有用。本文将详细介绍C#匿名方法的基本概念、语法、使用场景以及优势。 匿名方法的概念 匿名方法是一种无需显式定义名称的方法。在C#中,…

SD卡简介与驱动开发

基本概念 存储卡有很多种类&#xff0c;CF卡、记忆棒、SD卡、XD卡、MMC卡、MS卡、TF卡、MicroSD卡等。平时最常见的有SD卡和MicroSD卡两种&#xff0c; SD卡和MicroSD只是两张卡的大小不同&#xff0c;规格版本是完全相同的&#xff0c;均由SD卡协会推出。 SD卡有不少规范&…

大数据平台数仓数湖hive之拉链表高效实现

对于缓慢变化的维度表&#xff0c;如客户表&#xff0c;员工表&#xff0c;为了不丢失历史数据&#xff0c;又不至于太浪费存储空间&#xff0c;我们采用拉链表实现。 实现过程如下&#xff1a; 1、采集初始数据&#xff1a; 1.1 从mysql导出数据到hdfs /data/dolphinschedu…

【VSCode】常用插件推荐(持续更新~)

以下的这些插件都有使用过&#xff0c;可取对自己编码有用的选择安装。&#x1f9e0; 智能补全 / 提示类插件 1. Auto Import在编码时选择有对应导入包的选项&#xff0c;自动为 JS/TS 文件中的使用项补全并添加 import 声明&#xff0c;极大提高开发效率。2. Iconify IntelliS…

ICML 2025 | 深度剖析时序 Transformer:为何有效,瓶颈何在?

本文介绍帝国理工学院等机构在 ICML 2025 发表的最新研究成果。该研究并未提出新模型&#xff0c;而是对现有时间序列 Transformer 模型进行了一次深刻的拷问——为何结构更简单的 Transformer&#xff08; PatchTST, iTransformer&#xff09;在各大基准测试中&#xff0c;反而…

AIBOX内置5G天线设计

AIBOX内置5G天线设计AIBOX的天线种类天线种类及数量&#xff1a;运营商5G天线*4&#xff0c;1.4G天线*2、wifi天线*1天线形式&#xff1a;内置PCB天线。天线安装方式&#xff1a;卡扣固定&#xff0c;安装至设备外壳内壁。RG-178同轴线或UFL1.37mm同轴线连接至主板&#xff0c;…

低通滤波器的原理以及作用

低通滤波器&#xff08;Low-Pass Filter, LPF&#xff09;是一种允许低频信号通过&#xff0c;同时衰减或阻止高频信号的电子电路或数字信号处理算法。其核心原理和作用如下&#xff1a;一、工作原理 1. 频率选择性- 低通滤波器基于频率对信号进行筛选&#xff0c;其传递函数在…

[AI Coding] 一.腾讯CodeBuddy IDE内测、安装及基本用法(国产AI IDE启航)

在人工智能迅猛发展的今天&#xff0c;AI Coding 正逐步改变传统编程范式。广义上&#xff0c;AI Coding 是指以大语言模型&#xff08;LLMs&#xff09;为核心驱动&#xff0c;借助自然语言理解能力&#xff0c;自动生成、补全、调试与解释代码的智能编程方式。它不仅显著降低…

《网安处罚裁量基准》码农合规指北 v1.0——if (违规) { 罚金++ } else { 合规运行 }

尊敬的审核&#xff1a; 本人文章《〈网安处罚裁量基准〉码农合规指北 v1.0——if (违规) { 罚金 } else { 合规运行 }》 1. 纯属技术交流&#xff0c;无任何违法内容 2. 所有法律引用均来自公开条文 3. 请依据《网络安全法》第12条“不得无故删除合法内容”处理 附&#xff1a…

机器学习——逻辑回归(LogisticRegression)实战案例:信用卡欺诈检测数据集

使用逻辑回归识别 信用卡欺诈行为&#xff1a;基于creditcard.csv的实战与评估分析 项目背景 在金融行业中&#xff0c;信用卡欺诈检测是一项关键任务。欺诈交易在整个交易中占比极低&#xff0c;导致数据极度不平衡。本案例通过经典数据集 creditcard.csv&#xff0c;构建逻辑…

Helm在Kubernetes中的应用部署指南与案例解析

在上一章节中&#xff0c;我们已经介绍了Helm的部署和基本使用方法。本章将通过实际案例&#xff0c;详细演示如何使用Helm在Kubernetes集群中部署应用。一、Helm 核心价值解析优势解决的问题类比传统方式应用模板化重复编写 YAML 文件手动编写 20 资源清单文件版本控制缺乏部署…

如何最简单、通俗地理解线性回归算法? 线性回归模型在非线性数据上拟合效果不佳,如何在保持模型简单性的同时改进拟合能力?

线性回归作为统计学与机器学习领域中最基础且最重要的算法之一&#xff0c;其应用广泛且深远。它不仅是回归分析的入门方法&#xff0c;更是后续复杂模型构建的重要理论基础。理解线性回归算法的本质&#xff0c;既有助于提升数据分析的能力&#xff0c;也能为掌握更复杂的机器…

蓝桥杯----超声波

&#xff08;一&#xff09;、超声波1、原理&#xff08;图 一&#xff09;发送信号阶段&#xff1a;单片机通过翻转发送的引脚P1^0&#xff0c;发送8个40MHZ的方波&#xff0c;此时开始计时。等待接收信号&#xff1a;通过单片机的接收引脚P1^1检测&#xff0c;未接收到信号时…

Java学习-运算符

1.在代码中&#xff0c;如果有小数参与计算&#xff0c;结果有可能不精确。2.整数参与计算&#xff0c;结果只能是整数。3.数字进行运算时&#xff0c;数据类型不一样不能运算&#xff0c;需要转成一样的&#xff0c;才能运算。&#xff08;1&#xff09;隐式转换&#xff08;自…

一句话指令实现“2D转3D”、“图片提取线稿”

你是否曾为一张2D图片无法完美展示3D效果而遗憾&#xff1f;是否曾因需要将手绘草图转为清晰线稿而耗时费力&#xff1f;这些曾让设计师、电商卖家、内容创作者头疼的难题&#xff0c;如今只需一句话指令&#xff0c;即可迎刃而解。一、案例一&#xff1a;2D图片→3D模型痛点场…

层次聚类:无需“猜”K值,如何让数据自己画出“家族图谱”?

层次聚类&#xff1a;无需“猜”K值&#xff0c;如何让数据自己画出“家族图谱”&#xff1f;&#x1f44b; 大家好&#xff0c;我是小瑞瑞&#xff01;欢迎回到我的专栏&#xff01; 在上一期&#xff0c;我们学会了强大的K-Means算法&#xff0c;但它也给我们留下了一个“灵魂…

数据结构:链表(Linked List)

目录 结构推导 回到最原始的问题 —— 我们如何存数据&#xff1f; 第二步&#xff1a;我们来看看数组的限制 第三步&#xff1a;那我们该怎么做呢&#xff1f; 第四步&#xff1a;我们推导链表的数据结构 结构讲解 什么是链表&#xff1f; 什么是节点&#xff1f; …

[RK3566-Android11] U盘频繁快速插拔识别问题

问题描述 做老化测试时&#xff0c;在使用U盘频繁快速插拔的情况下&#xff0c;SDCard目录会突然被Kill掉&#xff0c;然后又重新挂载上&#xff0c;这会导致系统及APP的数据因为读写异常&#xff0c;从而界面卡死正常U盘插拔不应该导致内部存储卸载解决方案&#xff1a; SDK根…

【Golang】Go语言Map数据类型

Go语言Map数据类型 文章目录Go语言Map数据类型一、Map1.1.1、map定义1.1.2、map的基本使用1.1.3、判断某个键是否存在1.1.4、map的遍历1.1.5、使用delete()函数删除键值对1.1.6、按照指定顺序遍历map1.1.7、元素为map类型的切片1.1.8、值为切片类型的map一、Map map是一种无序…