高级IO之epoll模型

一、epoll模型介绍

epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,用于监视一个或多个文件描述符,以查看它们是否可以进行读取、写入或异常处理。它能够显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。


二、epoll函数参数介绍

epoll有如下函数:

  1. epoll_create函数:用于生成一个epoll专用的文件描述符,其中的参数是指定生成描述符的最大范围。在某些内核版本中,该参数用于初始化哈希表的大小。
  2. epoll_ctl函数:用于控制某个文件描述符上的事件,可以注册事件、修改事件、删除事件。
  • 参数:epfd(由epoll_create生成的epoll专用的文件描述符);op(要进行的操作,例如注册事件,可能的取值有EPOLL_CTL_ADD(注册)、EPOLL_CTL_MOD(修改)、EPOLL_CTL_DEL(删除));fd(关联的文件描述符);event(指向epoll_event的指针)。
  1. epoll_wait函数:用于轮询I/O事件的发生。
  • 参数:epfd(由epoll_create生成的epoll专用的文件描述符);events(指向epoll_event的指针);maxevents(最多可以返回的事件数量);timeout(等待的时间,如果设置为-1则无限等待)。

另外,epoll除了提供select/poll那种IO事件的水平触发外,还提供了边缘触发,这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。


三、epoll模型底层和运行过程

epoll模型在内核中的运行主要涉及以下步骤:

  1. 创建epoll模型:通过调用epoll_create函数创建一个文件描述符,这个文件描述符对应一个红黑树和一个就绪队列。红黑树用于存放需要关注的文件描述符及其对应的事件,而就绪队列则用于存放已经就绪的文件描述符及其事件。
  2. 添加文件描述符及监听事件:通过epoll_ctl函数向红黑树中添加文件描述符和监听事件。这些信息包括文件描述符、需要监听的事件以及二叉树的其他必要内容。此外,还需要建立回调策略,以便在有数据到达时通知内核处理。
  3. 查找并传递就绪的文件描述符:当某个文件描述符的事件就绪时,内核会将其加入到就绪队列中。用户态的程序可以通过调用epoll_wait函数来等待这些就绪的文件描述符。epoll_wait函数会观察就绪链表,并将就绪的文件描述符返回给用户态程序。这些文件描述符是通过内存映射的方式传递的,减少了不必要的拷贝操作。
  4. 重复监听的处理:当需要重复监听某个文件描述符时,只需再次调用epoll_ctl函数添加该文件描述符和监听事件。由于红黑树和就绪队列已经存在,所以无需重新构建数据结构,直接沿用即可。

总之,epoll模型在内核中的运行涉及到创建模型、添加文件描述符及监听事件、查找并传递就绪的文件描述符以及重复监听的处理等步骤。通过这些步骤,epoll模型能够高效地处理大量的文件描述符和事件,提供更好的系统性能和响应能力。


四、两种通信机制

ET和LT是两种不同的通信机制,它们在通信过程中有不同的特点和行为。

ET(Event Trigger)是一种事件触发的通信机制。在这种机制中,发送方在发送数据时不需要等待接收方的回应,一旦发送完成,发送方就会继续执行后续的操作。当接收方收到数据后,会触发相应的事件进行处理。这种机制的特点是发送方不会被阻塞,可以同时处理多个数据发送和执行其他任务。因此,ET通信机制具有高效性和并发性。这种机制要求程序员尽快将数据取走!

LT(Level Trigger)是一种电平触发的通信机制。在这种机制中,发送方会持续发送数据,直到接收方处理完之前的数据或者发送方主动停止发送。在这个过程中,发送方会被阻塞,直到接收方处理完之前的数据。LT通信机制的特点是简单易用,但是在处理大量数据时可能会导致发送方被长时间阻塞,影响程序的效率和响应能力。

在ET模式下,我如何直到我读的底层数据读完了?

答:循环读取,直到底层拒绝了我的读取请求!也就是没有数据了,这样的话就要求我们必须使用非阻塞fd来进行IO,这样在我们最后一次读不到数据的时候就可以不阻塞等待!


五、代码实现epoll服务端模式


#pragma once
#include <iostream>

#include "log.hpp"
#include "sock.hpp"
#include <functional>
#include <sys/epoll.h>
#define NEW_NUM 1024
static const uint16_t defaultport = 8080;
static const int size = 10;
static const int defaultvalue = 666;
namespace epoll_ns
{
    using func_t = std::function<std::string(const std::string &)>;
    class epollServer
    {
    public:
        epollServer(func_t cb, const uint16_t port = defaultport) : _port(port), _listensock(defaultvalue), _revs(nullptr), _f(cb)
        {
        }
        void Accepter()
        {
            logMessage(DEBUG, "Accepter in");

            std::string clientip;
            uint16_t clientport;
            int fd = sock::Accept(_listensock, &clientip, &clientport);
            if (fd < 0)
            {
                logMessage(WARNING, "accept error");
                return;
            }
            struct epoll_event ev;
            ev.events = EPOLLIN ;
            ev.data.fd = fd;
            epoll_ctl(_epfd, EPOLL_CTL_ADD, fd, &ev);
            logMessage(DEBUG, "Accepter out");
        }
        void Recver(int fd)
        {
            logMessage(DEBUG, "Recver in");
            char buffer[NEW_NUM];
            while (true)
            {
                //循环读取这样保证数据全部读取走
                int n = recv(fd, buffer, sizeof(buffer) - 1, 0);
                if (n > 0)
                {
                    buffer[n-1] = 0;
                    logMessage(DEBUG, "client# %s", buffer);
                    string request = buffer;                    
                    string resp = _f(request) + '\n';           //伪处理回调机制
                    write(fd, resp.c_str(), resp.size());
                }
                else if (n == 0)
                {
                    // 一定要记住关闭!
                    epoll_ctl(_epfd, EPOLL_CTL_DEL, fd, nullptr);
                    close(fd);
                    logMessage(NORMAL, "client quit!");
                    return;
                }
                else
                {
                    epoll_ctl(_epfd, EPOLL_CTL_DEL, fd, nullptr);
                    close(fd);
                    logMessage(WARNING, "recv error!");
                    return;
                }
            }
                logMessage(DEBUG, "Recver out");
        }
        void handler(int readyNum)
        {

            logMessage(DEBUG, "handler events in");
            for (int i = 0; i < readyNum; i++)
            {
                uint32_t events = _revs[i].events;
                int sock = _revs[i].data.fd;
                if (sock == _listensock && (events & EPOLLIN))
                {
                    // accept就绪!
                    Accepter();
                }
                else if (events & EPOLLIN)
                {
                    Recver(sock);
                }
            }

            logMessage(DEBUG, "handler events out");
        }

        void init()
        {
            // 1.创建->绑定->监听 套接字
            _listensock = sock::GetSocket(_port);
            sock::Bind(_listensock, _port);
            sock::Listen(_listensock);
            // 2.创建epoll模型 通知机制-> 1.LT(level trigglered) 水平触发 一直通知   2.ET(edge trigglered) 边缘触发 通知一次直到有新的数据到来
            _epfd = epoll_create(size);
            if (_epfd < 0)
            {
                logMessage(FALTAL, "epoll create error: %s", strerror(errno));
                exit(3);
            }
            // 3.添加listen套接字到epoll中
            struct epoll_event ev;
            ev.events = EPOLLIN;
            ev.data.fd = _listensock; // 作用:当事件就绪时,被重新获取时候,我没要知道是哪个fd就绪了!
            epoll_ctl(_epfd, EPOLL_CTL_ADD, _listensock, &ev);

            // 4.申请就绪空间
            _revs = new struct epoll_event[size];
        }

        void start()
        {
            while (true)
            {
                int timeout = 5000;
                int n = epoll_wait(_epfd, _revs, size, timeout);    //等待五秒就通知一次
                switch (n)
                {
                case 0:         //没有任务就绪
                    logMessage(NORMAL, "time out....");
                    break;
                case -1:        //出现异常
                    logMessage(WARNING, "epoll_wait error code:%d, error string : %s", errno, strerror(errno));
                    break;
                default:       //有任务就绪
                    logMessage(NORMAL, "events get ready!");
                    handler(n);
                    break;
                }
            }
        }
        ~epollServer()
        {
            if (_listensock != defaultvalue)
                close(_listensock);
            if (_epfd != defaultvalue)
                close(_epfd);
            if (_revs)
                delete[] _revs;
        }

    private:
        uint16_t _port;              //设置的端口号
        int _listensock;             //监听套接字
        int _epfd;                   //epoll模型文件描述符
        struct epoll_event *_revs;  //关心的事件
        func_t _f;                  //回调处理函数
    };
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C语言系列-浮点数在内存中的存储

&#x1f308;个人主页: 会编程的果子君 ​&#x1f4ab;个人格言:“成为自己未来的主人~” 目录 浮点数在内存中的存储 浮点数的存储 浮点数存的过程 浮点数取的过程 题目解析 浮点数在内存中的存储 常见的浮点数&#xff1a;3.14159.1E10等&#xff0c;浮点数家族包括&…

微信开发者工具 git 拉取 failed invalid authentication scheme

微信开发者工具 git 拉取 failed invalid authentication scheme 拉取代码时报错,无效身份认证 解决方案: 1.检查git地址是否正常 2.检查git用户名密码是否正确

【Vue2 + ElementUI】更改el-select的自带的下拉图标为倒三角,并设置相关文字颜色和大小

效果图 实现 <template><div class"search_resources"><div class"search-content"><el-select class"search-select" v-model"query.channel" placeholder"请选择" change"handleResource&q…

【Matlab】音频信号分析及FIR滤波处理——凯泽(Kaiser)窗

一、前言 1.1 课题内容: 利用麦克风采集语音信号(人的声音、或乐器声乐),人为加上环境噪声(窄带)分析上述声音信号的频谱,比较两种情况下的差异根据信号的频谱分布,选取合适的滤波器指标(频率指标、衰减指标),设计对应的 FIR 滤波器实现数字滤波,将滤波前、后的声音…

258:vue+openlayers加载mapbox-style的地图

第258个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+openlayers中添加mapbox地图,跟之前的不同处理方式是,这里采用了ol-mapbox-style插件来加载mapbox地图。具体请参考源代码和API。 直接复制下面的 vue+openlayers源代码,操作2分钟即可运行实现效果 文章目录 示…

基于Micropython利用ESP32-C3墨水屏电子时钟方法

本篇笔记介绍一下我们设计制作的墨水屏时钟。 1、所需硬件 1&#xff09;合宙的ESP32-C3&#xff1a; 2&#xff09;电子价签拆出来的2.9寸墨水屏&#xff1a; ——电子价签型号为&#xff1a;Stellar-L&#xff0c;墨水屏型号为&#xff1a;E029A01。 3&#xff09;自己设计…

Linux-ROS学习之旅(一)

##本人使用的是双系统&#xff0c;noetic版本&#xff0c;学习ROS初衷是学习控制机械臂&#xff0c;具体下载方法见B站&#xff0c;观看的教程是古月居早年的教学视频&#xff0c;和ROS_wiki&#xff1a;ROS/Tutorials - ROS Wiki ##下一篇文章有具体的实例&#xff0c;但是所用…

OJ_阶乘的和

题干 c语言实现 #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<vector> #include<set> using namespace std;int main() {vector<int> factorialArr;//把0&#xff01;放入数组factorialArr.push_back(1);int curFactorial 1;for (in…

《合成孔径雷达成像算法与实现》Figure5.16

clc clear close all距离向参数 R_eta_c 20e3; % 景中心斜距 Tr 25e-6; % 发射脉冲时宽 Kr 0.25e12; % 距离向调频率 Fr 7.5e6; % 距离向采样率 Nrg 256; % 距离线采样点数 Bw abs(Kr*Tr); …

uniapp,页面当有按钮的时候,可以做一个动态的效果

效果&#xff1a; 这个是当点着按钮的时候没有松开按钮的效果&#xff08;没有阴影&#xff09; 这个是当松开按钮的效果&#xff08;有阴影&#xff09; 原理讲解&#xff1a; 这段代码实现的业务逻辑是在一个Vue组件中控制“现金”按钮的阴影效果。具体来说&#xff0c;它通…

系统架构17 - 软件工程(5)

软件工程 软件测试测试原则测试方法静态测试动态测试黑盒测试白盒测试灰盒测试自动化测试 测试阶段单元测试集成测试系统测试性能测试验收测试其它测试AB测试Web测试链接测试表单测试 测试用例设计黑盒测试用例白盒测试用例 系统维护遗留系统系统转换转换方式数据转换与迁移 评…

C++力扣题目416--分割等和子集 1049--最后一块石头的重量II

416. 分割等和子集 力扣题目链接(opens new window) 题目难易&#xff1a;中等 给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集&#xff0c;使得两个子集的元素和相等。 注意: 每个数组中的元素不会超过 100 数组的大小不会超过 200 示例 1: 输入: […

openssl3.2 - 测试程序的学习 - test\aesgcmtest.c

文章目录 openssl3.2 - 测试程序的学习 - test\aesgcmtest.c概述笔记能学到的流程性内容END openssl3.2 - 测试程序的学习 - test\aesgcmtest.c 概述 openssl3.2 - 测试程序的学习 aesgcmtest.c 工程搭建时, 发现没有提供 test_get_options(), cleanup_tests(), 需要自己补上…

数据结构与算法:复杂度

友友们大家好啊&#xff0c;今天开始正式学习数据结构与算法有关内容&#xff0c;后续不断更新数据结构有关知识内容&#xff0c;希望多多支持&#xff01; 数据结构&#xff1a; 数据结构是用于存储和组织数据的方式&#xff0c;以便可以有效地访问和修改数据。不同的数据结构…

翻译: GPT-4 Vision从图像转换为完全可编辑的表格 升级Streamlit四

GPT-4 Vision 系列: 翻译: GPT-4 with Vision 升级 Streamlit 应用程序的 7 种方式一翻译: GPT-4 with Vision 升级 Streamlit 应用程序的 7 种方式二翻译: GPT-4 Vision静态图表转换为动态数据可视化 升级Streamlit 三 当您需要从不可复制或不可下载的表中提取数据时&#x…

Java+Spring Cloud +Vue+UniApp微服务智慧工地云平台源码

目录 智慧工地云平台功能 【劳务工种】所属工种有哪些&#xff1f; 1.管理人员 2.信息采集 3.证件管理 4.考勤管理 5.考勤明细 6.工资管理 7.现场统计 8.WIFI教育 9.课程库管理 10.工种管理 11.分包商管理 12.班组管理 13.项目管理 智慧工地管理平台是以物联网、…

C++进阶(七)AVL树

&#x1f4d8;北尘_&#xff1a;个人主页 &#x1f30e;个人专栏:《Linux操作系统》《经典算法试题 》《C》 《数据结构与算法》 ☀️走在路上&#xff0c;不忘来时的初心 文章目录 一、AVL树的概念二、AVL树的旋转1、左单旋2、右单旋3、左右双旋4、右左双旋 三、AVL树的基本实…

跨平台框架Flutter工作原理初探

前言 Flutter是开发跨平台应用的框架&#xff0c;支持将应用打包到几乎市面所有平台&#xff0c;本文较浅层次探究flutter框架的工作原理 参考来源为flutter中文社区官方文档 Flutter 开发文档 - Flutter 中文文档 - Flutter 中文开发者网站 - Flutter flutter的布局 组合…

防御保护--第一次实验

目录 一&#xff0c;vlan的划分及在防火墙上创建单臂路由 二&#xff0c;创建安全区域 三&#xff0c;配置安全策略 四&#xff0c;配置认证策略 五&#xff0c;配置NAT策略 1.将内网中各个接口能够ping通自己的网关 2..生产区在工作时间内可以访问服务器区&#xff0c;仅…

vivado 2018.3 烧写固化FPGA verilog代码以及出现的问题解决

vivado一般是与SDK同时使用的,像zynq系列,通过SDK烧写固化代码很方便,但是有的时候比如本人目前使用的是XC7K325T FPGA进行的开发,不会用到SDK软件,所以烧写固化代码想通过vivado直接操作。 1、按照网上百度的方法进行设置,如下 遇到的第一个问题就是在vivado2018.3的fl…