Linux高级IO——多路转接之epoll

news2024/11/27 21:06:43

本章代码Gitee地址:EpollServer

文章目录

    • 1. epoll接口
      • 1.1 epoll_create
      • 1.2 epoll_wait
      • 1.3 epoll_ctl
    • 2. epoll原理
    • 3. epoll_server
    • 4. epoll两种工作模式

1. epoll接口

1.1 epoll_create

#include <sys/epoll.h>
int epoll_create(int size);

参数int size理论上可以随便写(已废弃)

返回值:

  • 成功返回一个文件描述符
  • 失败返回-1

1.2 epoll_wait

#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

参数:

  • int epfdepoll_create的返回值

  • struct epoll_event *events, int maxevents:用户及缓冲区,返回已经就绪的文件描述符和事件

    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;      //
    };
    
  • int timeout:超时时间,单位是毫秒,0为非阻塞,-1为阻塞式

返回值:已经就绪的文件描述符的个数

1.3 epoll_ctl

#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

参数:

  • int epfdepoll_create的返回值

  • int op

    EPOLL_CTL_ADD	//增添
    EPOLL_CTL_MOD	//修改
    EPOLL_CTL_DEL	//删除
    
  • int fd, struct epoll_event *event:哪个文件描述符上的哪个事件

selectpoll都是用数组维护的,需要用户进行管理

2. epoll原理

网卡是外设,当硬件就绪之后,会以硬件中断的方式来告诉操作系统,将网卡的数据读到网卡驱动上,而操作系统读数据是从文件缓冲区读取数据。

所以为了支持epoll,操作系统支持三种机制:

  1. 内核会维护一颗红黑树,红黑树节点里面包含:

    struct rb_node
    {
        int fd;	//内核要关系的文件描述符
        uint32_t event;	//要关系的事件	位图形式
        //...
    }
    
  2. 此外还会维护一个就绪队列,一旦红黑树上有节点就绪,此时就会将该节点链入到队列当中

struct list_node
{
    int fd;	//已就绪的文件描述符
    uint32_t event;	//已就绪的事件
    //...
}
  1. 操作系统的底层网卡,是允许操作系统注册一些回调机制。

    操作系统内部提供一个回调函数,网卡以中断的方式将数据搬到了网卡驱动层,驱动层当中有数据就绪了,那么数据链路层就会自动调用对应的回调函数。
    这个回调函数要做的就是:

    • 向上交付
    • 数据到来解包交到tcp接收队列
    • 查找rb_tree->fd
    • 构建就绪节点,插入就绪队列

以上三套机制,就叫做epoll模型

Linux一切接文件,strcut file指针指向这个epoll模型,然后将struct file对象添加到进程文件描述符表里面,所以epoll的返回值是一个文件描述符。

image-20240412232048177

epoll优势:

  1. 检测就绪时间复杂度为O(1),判断队列是否为空

    获取就绪队列时间复杂度O(n)

  2. fdevent没有上限,所以的文件描述符和关系的事件都是由红黑树管理的,这颗红黑树多大,操作系统决定

如何看待这颗红黑树?

selectpoll都需要辅助数组,数组用户维护,而这颗红黑树就相当于之前我们自己维护的数组,只不过在epoll里面是由系统管理

  1. epoll_wait返回值表示有多少事件就绪,将就绪的节点一个一个弹出,依次放入数组,就绪事件是连续的

3. epoll_server

#include<iostream>
#include<memory>
#include<string>
#include"Socket.hpp"
#include"Log.hpp"
#include"Epoller.hpp"
#include"Nocopy.hpp"

uint32_t EVENT_IN = (EPOLLIN);
uint32_t EVENT_OUT = (EPOLLOUT);

class EpollServer : public Nocopy
{
    static const int defaultnum = 64;   //默认一次性最多获取64个事件
public:
    EpollServer(uint16_t port)
    :_port(port),
    _listensock_ptr(new MySocket()),
    _epoller_ptr(new Epoller())
    {}


    void Init()
    {
        //创建套接字
        _listensock_ptr->Socket();
        //绑定套接字
        _listensock_ptr->Bind(_port);
        //监听套接字
        _listensock_ptr->Listen();
        
        log(Info, "create listen socket success: %d", _listensock_ptr->Getfd());
    }

    void Accepter()
    {
        // 获取新链接
        std::string clientip;
        uint16_t clientport;
        int sock = _listensock_ptr->Accept(&clientip, &clientport);
        if (sock > 0)
        {
            // 不能直接读取,获取连接不代表发送了数据
            
            // 让epoll去关心
            _epoller_ptr->EpollerCtl(EPOLL_CTL_ADD, sock, EVENT_IN);
            log(Info, "get a new link, clientip: %s, clientport: %d", clientip.c_str(), clientport);

        }
    }

    void Recver(int fd)
    {
        // 读事件就绪
        char buffer[1024];
        ssize_t n = read(fd, buffer, sizeof(buffer) - 1);   //BUG
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << "get a message: " << buffer << std::endl;

            //返回
            std::string echo_str = "server echo $";
            echo_str += buffer;
            write(fd, echo_str.c_str(), echo_str.size());
        }
        else if (n == 0)
        {
            log(Info, "client quit, me too, close fd:%d", fd);
            //从epoll当中移除   删除红黑树节点
            _epoller_ptr->EpollerCtl(EPOLL_CTL_DEL, fd, 0);
            close(fd);  //细节  先移除再关闭
        }
        else
        {
            log(Warning, " read error, close fd:%d", fd);
        }
    }

    void Dispatcher(struct epoll_event revs[], int num)
    {
        for(int i = 0; i < num; i++)
        {
            uint32_t events = revs[i].events;
            int fd = revs[i].data.fd;

            if(events & EVENT_IN)
            {
                //读事件就绪
                if(fd == _listensock_ptr->Getfd())
                {
                    Accepter();
                }
                else
                {
                    //其他事件就绪
                    Recver(fd);
                }
            }
            else if(events & EVENT_OUT)
            {
                //写事件就绪

            }
        }
    }

    void Start()
    {
        //listensock套接字添加进epoll当中
        //listensock和它关心的事件    本质上添加到内核epoll模型的rb_tree里面
        _epoller_ptr->EpollerCtl(EPOLL_CTL_ADD, _listensock_ptr->Getfd(), EVENT_IN);    //关心读事件

        struct epoll_event revs[defaultnum];    //存放就绪的事件
        for(; ;)
        {
            //epoll只负责等待
            int n = _epoller_ptr->EpollerWait(revs, defaultnum);
            if(n > 0)
            {
                //有事件就绪
                log(Debug, "event happend, fd is : %d", revs[0].data.fd);
                //提取就绪事件   epoll_wait返回值会返回就绪的事件数量
                //如果数量大于定义的大小, 下次再捞
                Dispatcher(revs, n);  
            }
            else if(n == 0)
            {
                log(Info, "time out...");
            }
            else
            {
                log(Error, "epoll_wait error");
            }
        }
    }

    ~EpollServer()
    {
        _listensock_ptr->Close();
    }
private:
    std::shared_ptr<MySocket> _listensock_ptr;
    std::shared_ptr<Epoller> _epoller_ptr;
    //MySocket _listensock;
    uint16_t _port;
    //Epoller _epoller;
};

4. epoll两种工作模式

LT模式:

epoll默认工作模式是LT(Level Triggered水平触发)模式

当事件到来时,如果上层一直不取走,底层会一直通知

selectpoll采用的也是LT模式

EL模式:

EL(Edge Triggered边缘触发)模式是当数据变化的时候,才会通知一次

数据从无到有,从少到多

打个比方:

快递员A(LT模式)送快递的时候,如果客户一直不取,他就一直打电话,说你的快递到了,签收一下;

快递员B(ET模式)送快递的时候,只通知一次,然后就放在驿站了;如果之后又有快递到了,则又通知一次;

快递员A在一个小时只能,可能只能通知到几个客户;而快递员B在一个小时之内可以通知多个客户

ET不止通知效率高于LTIO效率也高于LT

由于ET只通知一次,所以就倒逼上层,每次都要把本轮数据全部取走

  • 如何知道本轮数据全部取完?
    比如说,我们有550g的大米,每天要吃100g,前5天正常,到第6天的时候,原本是要吃50g大米的,可是只能吃50g了,这就说明大米没有了
    也就是说当需要读取的目标数据大于实际读取的数据的时候,就表明数据已经全部取走。
    这就需要我们循环读取数据,直到读取出错为止,可是fd是默认是阻塞的,所以在ET模式下,所有的fd要设置成非阻塞Non_block,如果不设置,程序会一直阻塞住

    每次都能取走全部的数据,接收缓冲区就有空间了,这样tcp就能给对方通知更大的窗口,然后对方就可以给我们发送更多的数据

ET是否一定比LT高效?

LT所有的fd设置成non_block非阻塞,然后循环读取,这就个ET类似了

这里所谓的通知一次和每次通知,本质上其实是向就绪队列添加一次还是每次都添加

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

强化学习(三)基于动态规划 Dynamic Programming 的求解方法

文章目录 1. 动态规划与强化学习的联系2. 利用动态规划求解最优价值函数2.1 案例背景2.2 策略评估&#xff08;预测&#xff09;2.3 策略迭代&#xff08;控制&#xff09; 在前文《强化学习的数学框架&#xff1a;马尔科夫决策过程 MDP》中&#xff0c;我们用马尔可夫过程抽象…

HTML注释标签与标题标签

目录 注释标签 标题标签 注释标签 如果需要在 HTML 文档中添加一些便于阅读和理解但又不需要显示在页面中的注释文字&#xff0c;就需要使用注释标签。注释不会显示在网页界面上&#xff0c;目的是提高代码的可读性 在HTML中&#xff0c;使用<!-- 和 -->包裹注释的内…

Spring底层如何执行?

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 Spring底层如何执行refresh()prepareRefresh() 准备刷新的工作initPropertySources() obtainFreshBeanFactory()refreshBeanFactory()AbstractRefreshableApplicati…

博客系统项目测试(selenium+Junit5)

在做完博客系统项目之后&#xff0c;需要对项目的功能、接口进行测试&#xff0c;利用测试的工具&#xff1a;selenium以及Java的单元测试工具Junit进行测试&#xff0c;下面式测试的思维导图&#xff0c;列出该项目需要测试的所有测试用例&#xff1a; 测试结果&#xff08;全…

链表拓展之双向链表

前言 在前面已经总结了单链表&#xff0c;有了单链表的基础会很好理解双链表的实现&#xff0c;忘记了可以跳转——>http://t.csdnimg.cn/GFPk9 接下来就由我带着各位看官来认识今天的主角吧~ 什么是双向链表 在单链表的基础上&#xff0c;它有两个方向的链接&#xff0c;一…

SpringBlade dict-biz/list SQL 注入漏洞复现

0x01 免责声明 请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;作者不为此承担任何责任。工具来自网络&#xff0c;安全性自测&#xff0c;如有侵权请联系删…

一文看懂动态IP代理API

“动态”意味着每次连接或每隔一段时间&#xff0c;用户的IP地址都会发生改变。由于IP地址的不断变化&#xff0c;用户可以避免因频繁访问同一网站而导致的IP被封锁的问题。API叫做应用程序接口&#xff0c;是一种让软件之间相互通信的接口。API允许用户通过编程方式来调用动态…

【从浅学到熟知Linux】进程控制下篇=>进程程序替换与简易Shell实现(含替换原理、execve、execvp等接口详解)

&#x1f3e0;关于专栏&#xff1a;Linux的浅学到熟知专栏用于记录Linux系统编程、网络编程等内容。 &#x1f3af;每天努力一点点&#xff0c;技术变化看得见 文章目录 进程程序替换什么是程序替换及其原理替换函数execlexeclpexecleexecvexecvpexecvpeexecve 替换函数总结实现…

转行或者跳槽入职一家新公司,应该如何快速上手工作?

不管是干测试也好或者其它任何职业&#xff0c;没有谁会在一家公司待一辈子&#xff0c;转行不一定&#xff0c;但是跳槽是每一个打工人早晚都会面临的事情&#xff0c;今天就来跟大家聊聊这件事~ 入职一家新公司&#xff0c;你应该做什么可以最快速的上手工作&#xff1f; 这…

Python编程之旅:深入探索强大的容器——列表

在Python编程的世界中&#xff0c;容器&#xff08;Containers&#xff09;是一种用于存储多个项目的数据结构。其中&#xff0c;列表&#xff08;List&#xff09;是最常用且功能强大的容器之一。无论是初学者还是资深开发者&#xff0c;掌握列表的使用方法和技巧都是提升Pyth…

判断位数、按位输出、倒序输出(C语言)

一、运行结果&#xff1b; 二、源代码&#xff1b; # define _CRT_SECURE_NO_WARNINGS # include <stdio.h>int main() {//初始化变量值&#xff1b;int number 0;int i 1;int m 0;int z 0;int z1 0, z2 0, z3 0, z4 0;//提示用户&#xff1b;printf("请输…

电动汽车退役锂电池SOC主动均衡控制MATLAB仿真

微❤关注“电气仔推送”获得资料&#xff08;专享优惠&#xff09; 仿真简介 模型选用双向反激变换器作为主动均衡拓扑电路&#xff0c;均衡策略采用基于SOC的主动均衡策略&#xff0c;旨在解决电动汽车退役锂电池的不一致性问题。模型选用双向反激变换器作为主动均衡拓扑电路…

27.8k Star,AI智能体项目GPT Pilot:第一个真正的人工智能开发者(附部署视频教程)

作者&#xff1a;Aitrainee | AI进修生 排版太难了&#xff0c;请点击这里查看原文&#xff1a;27.8k Star&#xff0c;AI智能体项目GPT Pilot&#xff1a;第一个真正的人工智能开发者&#xff08;附部署视频教程&#xff09; 今天介绍一下一个人工智能智能体的项目GPT Pilot。…

关于idea中mybatis插件,下载后,无法生成代码模板--解决方法

一、不用相信网上其他解决方法 1.1试过&#xff0c;无效 二、解决方法 2.1【注&#xff1a;多试几次】重新下载&#xff0c;并重新启动idea 三、操作方法 3.1步骤 3.2idea重启&#xff0c;【如果没有重启】手动重启&#xff0c;必须有&#xff0c;很重要 3.3重新下载mybat…

试题 C: 质因数个数

萎了&#xff0c;整个人都萎了 快三天都没刷题了&#xff0c;想着明天就蓝桥杯了&#xff0c;就找了个真题做了下 可以看得出来这题很简单 但是没有测试点给我用&#xff0c;所以我的代码不保证正确性 代码如下&#xff1a; #include<cstdio> #include<cmath> …

2024 年10个最佳 Ruby 测试框架

QA一直在寻找最好的自动化测试框架&#xff0c;这些框架提供丰富的功能、简单的语法、更好的兼容性和更快的执行速度。如果您选择结合使用Ruby和Selenium进行Web测试&#xff0c;可能需要搜索基于Ruby的测试框架进行Web应用程序测试。 Ruby测试框架提供了广泛的功能&#xff0…

备忘录模式:恢复对象状态的智能方式

在软件开发中&#xff0c;备忘录模式是一种行为型设计模式&#xff0c;它允许捕获并外部化对象的内部状态&#xff0c;以便在未来某个时刻可以将对象恢复到此状态。这种模式是撤销操作或者回滚操作的关键实现机制。本文将详细介绍备忘录模式的定义、实现、应用场景以及优缺点。…

【产品经理修炼之道】- 产品经理如何做用户行为分析

一、为什么要做用户行为分析 观点一&#xff1a;有些功能整个平台用户都希望做&#xff0c;是没有必要耗费人力评估的&#xff0c;只要做了就可以了。用户行为分析是形式&#xff0c;不能为了分析而分析。观点二&#xff1a;我都在这个行业做了这么多年了&#xff0c;用户需要…

掼蛋小技巧(上篇)

一、一火保两单 如果手中的牌可以组成同花顺并且不会造成两张以上的单牌&#xff0c;我们就可以组成同花顺&#xff1b;如果组了同花顺后有两张以上的单张则果断放弃组同花顺。 二、十张出一对&#xff0c;九张出单张 掼蛋残局的时候&#xff0c;如果判断出下家手上只有一个四头…

pyqt的人脸识别 基于face_recognition库

参考文献&#xff1a; 1、python face_recognition实现人脸识别系统_python facerecognition检测人脸-CSDN博客 2、cv2.VideoCapture()_cv2.videocapture(0)-CSDN博客 1、camera.py文件代码如下&#xff1b;目录如下 import sys from PyQt5.QtWidgets import QApplication, …