重写muduo之EPollPoller

news2024/12/27 11:02:43

1、EPollPoller.h

EPollPoller的主要实现:作为poller的派生类,把基类给派生类保留的这些纯虚函数的接口实现出来。

override表示在派生类里面,这些方法是覆盖方法。必须由编译器来保证在基类里面一定有这些函数的接口的声明。在派生类要重写他们。
给EPollPoller的析构函数写override,就是让编译器给你检查基类的析构一定是虚函数。
底层是vector,放EventList,可以动态地扩容。
成员变量的epollfd要通过epoll_create来创建,映射的就是epoll底层的文件系统

#pragma once
#include "Timestamp.h"
#include "Poller.h"

#include <vector>
#include <sys/epoll.h>

/**
 * epoll的使用
 * epoll_create
 * epoll_ctl  add/mod/del
 * epoll_wait
*/
class EPollPoller:public Poller
{
public:
    EPollPoller(EventLoop* loop);//epoll_create
    ~EPollPoller()override;

    //重写基类Poller的抽象方法
    Timestamp poll(int timeoutMs,ChannelList* activeChannels)override;//epoll_wait
    void updateChannel(Channel* channel)override;//epoll_ctl
    void removeChannel(Channel* channel)override;//epoll_ctl
private:
    static const int kInitEventListSize=16;//给vector<epoll_event>初始化的长度
    using EventList=std::vector<epoll_event>;

    //填写活跃的连接
    void fillActiveChannels(int numEvents,ChannelList* activeChannels)const;

    //更新channel通道
    void update(int operation,Channel* channel);

    int epollfd_;
    EventList events_;
};

2、EPollPoller.cc

epoll_wait:第2个参数epoll_event 是最终发生事件的fd,返回值是发生事件fd的数量。

epoll_create的参数size在Liunx内核2.6.8以后没有意义了,但是必须是大于0的数


epoll_create1,flags=0时和epoll_create一样,提供了行为选项EPOLL_CLOEXEC

当我们去使用epoll_create1的时候,创建的epollfd,然后在当前线程里面再去fork创建一个子进程,然后用exec替换子进程的时候,在子进程里面就把父进程设置成标志的fd,资源就都给关闭了。

结构体的fd就是epoll要监听的事件,ptr指向fd对应的channel,channel包含fd以及感兴趣的事件

#include "EPollPoller.h"
#include "Logger.h"
#include "Channel.h"

#include <errno.h>
#include <unistd.h>
#include <string.h>

// channel还没有被添加到Poller中
const int kNew = -1; // channel的成员index_=-1
// channel已经添加到Poller中
const int kAdded = 1;
// channel从Poller中删除
const int kDeleted = 2;

EPollPoller::EPollPoller(EventLoop *loop)
    : Poller(loop), epollfd_(::epoll_create1(EPOLL_CLOEXEC)), events_(kInitEventListSize) // vector<epoll_event> 默认大小16
{
    if (epollfd_ < 0)
    {
        LOG_FATAL("epoll_create error:%d \n", errno);
    }
}

EPollPoller::~EPollPoller()
{
    ::close(epollfd_);
}

//epoll_wait 
//eventloop会创建一个channellist,并把创建好的channellist的地址传给poll
//poll通过epoll_wait监听到哪些fd发生了事件,把真真正正发生事件的channel通过形参发送到eventloop提供的实参里面 
Timestamp EPollPoller::poll(int timeoutMs, ChannelList *activeChannels)
{
    // 实际上应该用LOG_DEBUG输出日志更为合理,可以设置开启或者不开启 因为LOG_INFO是每次都要输出的,会影响epoll的效率
    LOG_INFO("func=%s => fd total count:%lu\n", __FUNCTION__, channels_.size());

    //events_.begin()返回首元素的迭代器(数组),也就是首元素的地址,是面向对象的,要解引用,就是首元素的值,然后取地址 
    //就是vector底层数组的起始地址   static_cast类型安全的转换   timeoutMs超时时间 
    int numEvents = ::epoll_wait(epollfd_, &*events_.begin(), static_cast<int>(events_.size()), timeoutMs); 

    //全局的变量errno,库里的,poll可能在多个线程eventloop被调用 ,所以用局部变量存起来 
    int saveErrno = errno;  // 在loop开始时,保存当前loop的errno,防止中间操作发生错误对全局的errno进行改写,那在日志打印的时候就获取不到当前loop的errno了
    Timestamp now(Timestamp::now());

    if (numEvents > 0)//表示有已经发生相应事件的个数 
    {
        LOG_INFO("%d events happened \n", numEvents);
        fillActiveChannels(numEvents, activeChannels);

        // 如果返回的numEvents和实际vector中的events的长度是一样的,说明这一轮监听的所有的event都发生事件了,就要进行扩容了
        if (numEvents == events_.size())
        {
            events_.resize(events_.size() * 2);
        }
    }
    else if (numEvents == 0) // 没有事件发生,超时
    {
        LOG_DEBUG("%s timeout!\n", __FUNCTION__);
    }
    else // 发生错误
    {
        if (saveErrno != EINTR) // EINTR=>外部中断,不等于外部的中断 ,是由其他错误类型引起的 
        {
            errno = saveErrno; //适配 ,把errno重置成当前loop之前发生的错误的值 
            LOG_ERROR("EPollPoller::poll() err!");
        }
    }
    return now;
}

// channel update remove=>EventLoop updateChannel removeChannel=>Poller updateChannel removeChannel
/**
 *                   EventLoop   =>    poller.poll
 *          ChannelList        Poller
 *                             ChannelMap   <fd,Channel*>  (保存的是向poller注册过的channel)  epollfd
 */
void EPollPoller::updateChannel(Channel *channel)
{
    const int index = channel->index();
    LOG_INFO("func=%s=> fd=%d events=%d index=%d \n", __FUNCTION__, channel->fd(), channel->events(), index);

    if (index == kNew || index == kDeleted)//未添加或者已删除 
    {
        if (index == kNew)//未添加,键值对写入map中 
        {
            int fd = channel->fd();
            channels_[fd] = channel;
        }
        channel->set_index(kAdded);
        update(EPOLL_CTL_ADD, channel);//相当于调用epoll_ctl,添加1个channel到epoll中 
    }
    else // channel已经在poller上注册过了
    {
        int fd = channel->fd();
        if (channel->isNoneEvent()) // channel对任何事件都不感兴趣,不需要poller帮忙监听了 
        {
            update(EPOLL_CTL_DEL, channel);//删除已注册的channel的感兴趣的事件 
            channel->set_index(kDeleted);
        }
        else
        {
            update(EPOLL_CTL_MOD, channel);
        }
    }
}
// 从poller中删除channel
void EPollPoller::removeChannel(Channel *channel)
{
    int fd = channel->fd();
    channels_.erase(fd);

    LOG_INFO("func=%s => fd=%d\n", __FUNCTION__, fd);

    int index = channel->index();
    if (index == kAdded)//如果已注册过 
    {
        update(EPOLL_CTL_DEL, channel);//通过epoll_ctl 删掉 
    }
    channel->set_index(kNew);//设置成未添加的状态 
}

// 填写活跃的连接
void EPollPoller::fillActiveChannels(int numEvents, ChannelList *activeChannels) const
{
    for(int i=0;i<numEvents;i++)
    {
        Channel* channel=static_cast<Channel*>(events_[i].data.ptr);
        channel->set_revents(events_[i].events);
        activeChannels->push_back(channel);//EventLoop就拿到了它的poller给它返回的所有发生事件的channel列表了
        //至于EventLoop拿到这些channel干什么事情,我们看 EventLoop的代码 
    }
}

// 更新channel通道  epoll_ctl add/mod/del
void EPollPoller::update(int operation, Channel *channel)
{
    epoll_event event;
    memset(&event, 0, sizeof event);

    int fd = channel->fd();

    event.events = channel->events();//返回的就是fd所感兴趣的事件 
    event.data.fd = fd;
    event.data.ptr = channel;//绑定的参数 

    if (::epoll_ctl(epollfd_, operation, fd, &event) < 0)//把fd相关事件更改 
    {
        if (operation == EPOLL_CTL_DEL)
        {
            LOG_ERROR("epoll_ctl del error:%d\n", errno);
        }
        else
        {
            LOG_FATAL("epoll_ctl add/mod error:%d\n", errno); // add/mod如果失败了,是无法挽回的,所以LOG_FATAL会自动exit
        }
    }
}

channel要把自己注册到poller上,但channel无法与poller直接通信,channel调用的是EventLoop的updatechannel和removechannel,EventLoop的updatechannel和removechannel最终还是调用的EPollPoller,做的相当于是epoll_ctl,最后进行epoll_wait就是EPollPoller中的poll函数,使用vector数组存放发生的事件,如果返回值numEvents和vector数组长度一样,说明可能还有更多的事件没有处理,需要扩容,下一轮再来处理,因为muduo库采用的是LT模式,没有处理的事件会不断上报。

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

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

相关文章

[蓝桥杯2024]-PWN:ezheap解析(堆glibc2.31,glibc2.31下的double free)

查看保护 查看ida 大致就是只能创建0x60大小的堆块&#xff0c;并且uaf只能利用一次 完整exp&#xff1a; from pwn import* #context(log_leveldebug) pprocess(./ezheap2.31)def alloc(content):p.sendlineafter(b4.exit,b1)p.send(content) def free(index):p.sendlineaft…

代码随想录算法训练营DAY46|C++动态规划Part8|139.单词拆分、多重背包理论基础、背包问题总结篇

文章目录 139.单词拆分思路CPP代码 多重背包理论基础处理输入把所有个数大于1的物品展开成1个开始迭代&#xff0c;计算dp数组代码优化 背包问题总结篇 139.单词拆分 力扣题目链接 文章讲解&#xff1a;139.单词拆分 视频讲解&#xff1a;你的背包如何装满&#xff1f;| LeetCo…

体育老师工资高吗,奖金有吗

教师的薪资水平与多种因素相关&#xff0c;包括教育经验、工作地点、学校类型以及个人的教学成果等。在讨论体育教师的工资问题时&#xff0c;不能仅仅关注数字&#xff0c;更应了解教育价值和个人发展。 初中体育教师的工资水平受多种因素影响。根据网络统计的数据&#xff0c…

STM32F4xx开发学习—GPIO

GPIO 学习使用STM32F407VET6GPIO外设 寄存器和标准外设库 1. 寄存器 存储器映射 存储器本身是不具有地址的&#xff0c;是一块具有特定功能的内存单元&#xff0c;它的地址是由芯片厂商或用户分配&#xff0c;给存储器分配地址的过程就叫做存储区映射。给内存单元分配地址之后…

页面多开、谷歌浏览器解决不能批量打开问题、批量打开被限制

目录 问题原因谷歌浏览器解决办法来看效果 问题 我们使用批量打开页面的时候 只能打开第一个页面 原因 这种问题是因为 浏览器限制了浏览器的弹出 并不是人家页面功能不能用 谷歌浏览器解决办法 在浏览器输入这个路径 chrome://settings/content/popups?search%E9%87%…

Vue MVVM这一篇就够啦!

Vue vs React 相似之处: 它们都有使用 Virtual DOM虚拟DOM-CSDN博客&#xff1b;提供了响应式&#xff08;Reactive&#xff09;和组件化&#xff08;Composable&#xff09;的视图组件。将注意力集中保持在核心库&#xff0c;而将其他功能如路由和全局状态管理交给相关的库。R…

Python安装以及环境配置

目录 一、下载安装包二级目录三级目录 一、下载安装包 方式网址Python官网python.org镜像下载地址Download pythonpycharmDownload PyCharm https://blog.csdn.net/sun80760/article/details/135256627 二级目录 三级目录

Spring+SpringMVC+Jsp实现校园二手交易系统

前言介绍 在社会快速发展的影响下&#xff0c;使校园二手交易系统的管理和运营比过去十年更加理性化。依照这一现实为基础&#xff0c;设计一个快捷而又方便的网上校园二手交易系统是一项十分重要并且有价值的事情。对于传统的管理控制模型来说&#xff0c;网上校园二手交易系…

C++构造函数和析构函数的调用顺序

一般情况下&#xff0c;调用析构函数的次序正好与调用构造函数的次序相反&#xff0c;也就是最先被调用的构造函数&#xff0c;其对应的析构函数最后被调用&#xff0c;而最后被调用的构造函数&#xff0c;其对应的析构函数最先被调用。 当然对象的构造函数和析构函数调用时机和…

图片浏览器-PicView

一、前言 PicView 是一款适用于 Windows 10 或 11 的快速高效的图像查看器&#xff0c;配备了干净简洁的用户界面&#xff0c;可以在不需要时方便地隐藏。 二、支持类型 它支持广泛的图像文件类型&#xff0c;包括&#xff1a;WEBP、GIF、SVG、PNG、JXL、HEIC、PSD 三、软件特…

Docker 的网络实现

简介 标准的 Docker 支持以下 4 类网络模式&#xff1a; 1&#xff09;host 模式&#xff1a;使用 --nethost 指定 2&#xff09;container 模式&#xff1a;使用–netcontainer:NAME_or_ID 指定 3&#xff09;none模式&#xff1a;使用 --netnone 指定 4&#xff09;bridge 模…

2.5W字 一文读懂汽车智能座舱的FLASH 存储市场、技术

吃瓜群众&#xff1a;机哥&#xff0c;存储是什么玩意&#xff0c;我买手机、电脑的时候导购员都说买内存大的&#xff0c;三星的好&#xff0c;品牌大&#xff0c;问题少&#xff0c;我也只有看哪个内存大就买那个。 机哥&#xff1a;额&#xff0c;这个嘛&#xff0c;说来话长…

SpringBoot+Vue+Element-UI实现学生综合成绩测评系统

前言介绍 学生成绩是高校人才培养计划的重要组成部分&#xff0c;是实现人才培养目标、培养学生科研能力与创新思维、检验学生综合素质与实践能力的重要手段与综合性实践教学环节。而学生所在学院多采用半手工管理学生成绩的方式&#xff0c;所以有必要开发学生综合成绩测评系…

Day62:单调栈 LeedCode503. 下一个更大元素 II 42. 接雨水

503. 下一个更大元素 II 给定一个循环数组 nums &#xff08; nums[nums.length - 1] 的下一个元素是 nums[0] &#xff09;&#xff0c;返回 nums 中每个元素的 下一个更大元素 。 数字 x 的 下一个更大的元素 是按数组遍历顺序&#xff0c;这个数字之后的第一个比它更大的数…

分布式与一致性协议之ZAB协议(三)

ZAB协议 主节点崩溃了&#xff0c;怎么办&#xff1f; 众所周知&#xff0c;系统在运行中不可避免会出现各种各样的问题&#xff0c;比如进程崩溃了、服务器死机了&#xff0c;这些问题会导致很严重的后果&#xff0c;让系统没办法继续运行。在ZAB协议中&#xff0c;写请求是…

【软考】模拟考卷错题本2024-05-05

1 算法 关键词&#xff1a;按照单位重量价值大优先&#xff0c;那就是1、2、3即430&#xff1b;之后的根据排除法又可以得到630&#xff1b;故C。 2 UML 序列图 上图已经基本上有解析&#xff1b;重点在于在四个选项中选正确的。根据概念排除&#xff1a;异步和同步是不一样的&…

GateWay检查接口耗时

添加gateway依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId> </dependency>创建一个LogTimeGateWayFilterFactory类&#xff0c;可以不是这个名字但是后面必须是x…

opencv基础篇 ——(十六)图形绘制与填充

OpenCV 提供了丰富的图形绘制和填充功能&#xff0c;主要通过 cv::rectangle, cv::circle, cv::line, cv::polylines, cv::fillPoly 和 cv::ellipse 等函数实现。以下是一些基本的图形绘制和填充操作的说明&#xff1a; 矩形: 函数: cv::rectangle语法: cv::rectangle(img, rec…

10000 字详细讲解 Spring 中常用注解及其使用

如下图京东购物页面&#xff0c;当我们选择点击访问某一类商品时&#xff0c;就会向后端发起 HTTP 请求&#xff0c;当后端收到请求时&#xff0c;就会找到对应的代码逻辑对请求进行处理&#xff0c;那么&#xff0c;后端是如何来查找处理请求相对应的代码呢&#xff1f;答案就…

JRT1.6发布

经过51的三天努力&#xff0c;完成基于JRT的质控核心部分。框架部分已经达到了第一个可生产版本。 可生产包括以下部分&#xff1a; 1.Web开发基础和发布运维基础 2.Linux和WIndows客户端浏览器 3.Linux和WIndows客户端打印导出程序 4.Linux和WIndows初始化程序 5.Linux和WInd…