webserver服务器从零搭建到上线(八)|EpollPoller事件分发器类

文章目录

  • EpollPoller事件分发器类
    • 成员变量和成员函数解释
      • 私有的成员函数和成员变量
      • 成员函数
    • 具体实现
      • 常量的作用
      • 构造函数和析构函数
      • ⭐️poll函数
      • `updateChannel`函数
      • `removeChannel` 函数
      • `removeChannel` 和`updateChannel`
      • ⭐️`fillActiveChannels` 函数
      • ⭐️update 函数
  • 总结

终于要开始我们的重点:事件分发起和事件循环了,在这里我们将揭开事件驱动的IO多路复用模型的神秘面纱!

EpollPoller事件分发器类

成员变量和成员函数解释

这些都是在头文件中声明的,我们可以先对类中封装的各个方法进行合理的研究和猜测。

私有的成员函数和成员变量

在这里我们简单介绍一下私有成员函数和成员变量
私有成员函数如下

void fillActiveChannels(int numEvents, ChannelList *activeChannels) const;
void update(int operation, Channel *channel);
  • fillActiveChannels()这里主要就是将 epoll_wait 返回的活跃事件填充到 activeChannels中。
  • update()这里的根据操作类型(添加、修改、删除),调用epoll_ctl来更新epoll实例中的Channel对象。
    在Channel类中,我们也写了一个update,它的具体实现是loop_->updateChannel(this);,调用了EventLoop中的updateChannel,所以我们有理由怀疑,其中的updateChannel()就是在调用这里的update方法

私有成员变量

static const int kInitEventListSize = 16;
using EventList = std::vector<epoll_event>;int epollfd_;
EventList events_;
  • kInitEventListSize :初始事件列表大小。
  • EventList :用于存储epoll事件的向量类型。
  • int epollfd_ :epoll实例的文件描述符。
  • EventList events_ :存储从epoll_wait返回的事件列表。

成员函数

    EPollPoller(EventLoop *Loop);~EPollPoller() override;//重写基类Poller的抽象方法Timestamp poll(int timeoutMs, ChannelList *activeChannels) override;void updateChannel(Channel *channel) override;void removeChannel(Channel *channel) override;
  • Timestamp poll(int timeoutMs, ChannelList *activeChannels)
    • 调用epoll_wait等待事件发生,将活跃的事件填充到activeChannels中。
  • void updateChannel(Channel *channel)
    • 更新或添加一个Channel对象到epoll实例中,调用epoll_ctl。
  • void removeChannel(Channel *channel)
    • 从epoll实例中移除一个Channel对象,调用epoll_ctl。

具体实现

#include "EpollPoller.h"
#include "Logger.h"
#include "Channel.h"#include <errno.h>
#include <unistd.h>
#include <strings.h>const int kNew = -1;
const int kAdded = 1;
const int kDeleted = -1;EPollPoller::EPollPoller(EventLoop *loop) : Poller(loop), epollfd_(::epoll_create1(EPOLL_CLOEXEC)), events_(kInitEventListSize) { //创建了vector<epoll_events>if (epollfd_ < 0) LOG_FATAL("epoll_create error:%d\n", errno);}EPollPoller::~EPollPoller() {::close(epollfd_);
}Timestamp EPollPoller::poll(int timeoutMs, ChannelList *activeChannels)
{LOG_INFO("func=%s => fd total count:%lu \n", __FUNCTION__, channels_.size());int numEvents = ::epoll_wait(epollfd_, &*events_.begin(), static_cast<int>(events_.size()), timeoutMs);int saveErrno = errno;Timestamp now(Timestamp::now());if (numEvents > 0) {LOG_INFO("%d events happened \n", numEvents);fillActiveChannels(numEvents, activeChannels);if (numEvents == events_.size()) {events_.resize(events_.size() * 2);}} else if (numEvents == 0) {LOG_DEBUG("%s timeout! \n", __FUNCTION__);} else {if (saveErrno != EINTR) {errno = saveErrno;LOG_ERROR("EPollPoller::poll() err!");}}return Timestamp();
}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) {int fd = channel->fd();channels_[fd] = channel;}channel->set_index(kAdded);update(EPOLL_CTL_ADD, channel);} else { //说明channel已经在Poller注册过了int fd = channel->fd();if (channel->isNoneEvent()) {update(EPOLL_CTL_DEL, 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);}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列表了}
}//更新channel通道 epoll_ctl add/mod/del
void EPollPoller::update(int operation, Channel *channel) {epoll_event event;bzero(&event, sizeof event);int fd = channel->fd();event.events = channel->events();event.data.fd = fd;event.data.ptr = channel;if (::epoll_ctl(epollfd_, operation, fd, &event) < 0) {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);}}
}

常量的作用

// channel未添加到poller中
const int kNew = -1;  // channel的成员index_ = -1
// channel已添加到poller中
const int kAdded = 1;
// channel从poller中删除
const int kDeleted = 2;

他们主要用于表示 channel的状态,在后续的方法具体实现中会体现到。

构造函数和析构函数

EPollPoller::EPollPoller(EventLoop *loop): Poller(loop), epollfd_(::epoll_create1(EPOLL_CLOEXEC)), events_(kInitEventListSize)  // vector<epoll_event>
{if (epollfd_ < 0){LOG_FATAL("epoll_create error:%d \n", errno);}
}EPollPoller::~EPollPoller() 
{::close(epollfd_);
}
  • 构造函数:创建一个epoll实例epollfd_,随后我们需要初始化我们所关注的事件列表大小events_
  • 析构函数:我们知道,我们将所有监控的事件都委托给了内核的epoll实例来进行管理,该实例底层是一颗红黑树。我们最后析构的时候,可以直接关闭close,就可以关闭所有网络IO的文件描述符了。

⭐️poll函数

Timestamp EPollPoller::poll(int timeoutMs, ChannelList *activeChannels)
{LOG_INFO("func=%s => fd total count:%lu \n", __FUNCTION__, channels_.size());int numEvents = ::epoll_wait(epollfd_, &*events_.begin(), static_cast<int>(events_.size()), timeoutMs);int saveErrno = errno;Timestamp now(Timestamp::now());if (numEvents > 0) {LOG_INFO("%d events happened \n", numEvents);fillActiveChannels(numEvents, activeChannels);if (numEvents == events_.size()) {events_.resize(events_.size() * 2);}} else if (numEvents == 0) {LOG_DEBUG("%s timeout! \n", __FUNCTION__);} else {if (saveErrno != EINTR) {errno = saveErrno;LOG_ERROR("EPollPoller::poll() err!");}}return Timestamp();
}

他就是实现我们多路分发的函数:

  • poll 函数使用 epoll_wait 等待事件发生,并将活跃的事件填充到 activeChannels 中。
  • 如果发生事件,将这些事件填充到 activeChannels,并在必要时扩展事件列表。
  • 返回当前的时间戳,主要是为了后续方便打日志和进行管理。

updateChannel函数

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){int fd = channel->fd();if (index == kNew){channels_[fd] = channel;}channel->set_index(kAdded);update(EPOLL_CTL_ADD, channel);}else{int fd = channel->fd();if (channel->isNoneEvent()){update(EPOLL_CTL_DEL, channel);channel->set_index(kDeleted);}else{update(EPOLL_CTL_MOD, channel);}}
}
  • updateChannel 函数根据 Channel 的当前状态(新添加或已删除)来决定是否添加或更新 epoll 实例中的事件,该函数肯定会被EventLoop封装,然后再由Channel自己来进行调用。
  • 如果是新添加的 Channel,则在 epoll 中注册该文件描述符。
  • 如果 Channel 没有感兴趣的事件,则将其从 epoll 中删除。

removeChannel 函数

void EPollPoller::removeChannel(Channel *channel)
{int fd = channel->fd();LOG_INFO("func=%s => fd=%d\n", __FUNCTION__, fd);int index = channel->index();if (index == kAdded){update(EPOLL_CTL_DEL, channel);}channel->set_index(kNew);
}
  • removeChannel 函数将 Channel 从 epoll 实例中删除,并更新其状态。这一看就是我们的EventLoop需要调用的函数。

removeChannelupdateChannel

从这两个函数理我们可以看出,他们其实是为EventLoop提供操作Channel的方法。从代码的具体实现细节来看,我们可以领略到 channel 为什么要设置一个 index_ 标志,主要就是为了实现channel的复用,我们总不能每次有新连接都新建一个channel,连接断开就删除channel吧!

⭐️fillActiveChannels 函数

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);}
}
  • fillActiveChannels 函数将 epoll_wait 返回的所有活跃事件填充到 activeChannels 列表中。

  • 然手我们介绍一下 event.data,我们将已经被激活的event直接拿到手,这里就需要用到我们的event.data.ptr:
    data字段是一个联合体,具体结构包含了我们常用的int fdvoid *ptr
    ptr 是一个通用指针,可以用来指向任何类型的数据。它通常用于关联用户自定义的数据结构(这里是我们的Channel*),以便在事件触发时可以快速访问这些数据。例如,你可以将 ptr 设置为你的应用程序中某个特定对象的指针,当对应的文件描述符触发事件时,你的应用程序可以通过 ptr 直接访问到这个对象

  • 然后调用channel的set_revents方法,可以将已经被激活的事件直接初始化到我们的channel中。

  • 随后把 channel 推到我们的 activeChannels

⭐️update 函数

void EPollPoller::update(int operation, Channel *channel)
{epoll_event event;bzero(&event, sizeof event);int fd = channel->fd();event.events = channel->events();event.data.fd = fd; event.data.ptr = channel;if (::epoll_ctl(epollfd_, operation, fd, &event) < 0){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);}}
}

update 函数根据操作类型(添加、修改或删除)调用 epoll_ctl 来更新 epoll 实例中的 Channel。

其实说白了update就是用来封装epoll_ctl的。

该函数被 EPollPoller::removeChannelEPollPoller::updateChannel调用,用来更新Channel的封装的fd以及其需要监控的相关事件。

总结

EPollPoller 类实现了基于 epoll 的 I/O 多路复用,通过监控多个文件描述符上的事件,并在事件发生时通知相应的 Channel 对象来处理事件。通过实现这些函数,EPollPoller 能够高效地管理和分发事件。

下一节,我们将讲解EventLoop类的具体实现!

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

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

相关文章

[数据集][目标检测]喝水检测数据集VOC+YOLO格式995张3类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;995 标注数量(xml文件个数)&#xff1a;995 标注数量(txt文件个数)&#xff1a;995 标注类别…

jeecgboot 同一账号只允许一个人登录

1.需求分析 jeecgboot 框架要实现同一个账号只允许一个人登录&#xff0c;就跟游戏账号类似&#xff0c;“我登录了就把你踢下去&#xff0c;你登录了就把我踢下去”&#xff1b;jwt 原理是生成 token 后一段时间内登录都有效&#xff0c;jeecgboot 中 jwt 和 redis 联合使用后…

OpenCV学习(2.1) 初识图像

1.图像对象 图像是由一个个像素组成的&#xff0c;像素越多&#xff0c;体现到图像就是更加清晰&#xff0c;有更多的细节。举个例子&#xff0c;通常来说的分辨率&#xff0c;1080P&#xff0c;720P&#xff0c;480P就是指像素的数量&#xff0c;数量越多就越清晰。 2.打印图…

第四周:心态和角色

1. 关注自己到关注他人 关注自己到关注他人&#xff0c;就是利己到利他&#xff0c;基本上就是从全局的角度去看待事情&#xff0c;而不单单是自己一亩三分地里耕耘&#xff0c;团队出的任何事情&#xff0c;首要责任就在管理者身上&#xff0c;不再是单打独斗了&#xff0c;你…

在LINQ中,如何使用Include方法加载关联的实体或集合?

Include 方法允许你在查询数据时一并加载关联的实体或集合。这有助于减少数据库访问次数&#xff0c;因为你可以一次性获取所有需要的数据&#xff0c;而不是分别查询每个关联的实体。 一、以下是如何在 Entity Framework 中使用 Include 方法来加载关联实体或集合的步骤&…

NV-LIO:一种基于法向量的激光雷达-惯性系统(LIO)

论文&#xff1a;NV-LIO: LiDAR-Inertial Odometry using Normal Vectors Towards Robust SLAM in Multifloor Environments 作者&#xff1a;Dongha Chung, Jinwhan Kim NV-LIO&#xff1a;一种基于法向量的激光雷达-惯性系统&#xff08;LIO&#xff09;NV-LIO利用从激光雷…

vue3主题切换按钮与功能实现

代码: <template><div class"slideThree"><label class"theme-switch"><inputtype"checkbox"class"checkbox"v-model"isChecked"change"setTheme"id"slideThree"name"check…

Day08:CSS 高级

目标&#xff1a;掌握定位的作用及特点&#xff1b;掌握 CSS 高级技巧 一、定位 作用&#xff1a;灵活的改变盒子在网页中的位置 实现&#xff1a; 1.定位模式&#xff1a;position 2.边偏移&#xff1a;设置盒子的位置 leftrighttopbottom 水平方向偏移&#xff1a;left、…

mac下载安装好软件后提示已损坏

mac下载安装好软件后提示已损坏 解决方法&#xff1a; 首先确保系统安全设置已经改为任何来源。 打开任何来源后&#xff0c;到应用程序目录中尝试运行软件&#xff0c;如果仍提示损坏&#xff0c;请在应用图标上&#xff0c;鼠标右键&#xff0c;在弹出菜单中点打开。 如果…

埃及媒体分发投放-新闻媒体通稿发布

埃及商业新闻 大舍传媒近日宣布将在埃及商业新闻领域展开新的媒体分发投放。作为埃及最具影响力的商业新闻平台之一&#xff0c;埃及商业新闻将为大舍传媒提供广阔的市场和受众群体。这一合作意味着大舍传媒将有机会通过埃及商业新闻的平台向埃及的商业精英和投资者传递最新的…

Web前端:探秘几乎听不懂的技术领域

Web前端&#xff1a;探秘几乎听不懂的技术领域 Web前端&#xff0c;这个看似熟悉却又充满神秘的词汇&#xff0c;对许多人来说可能意味着一串串复杂的代码和难以理解的逻辑。然而&#xff0c;正是这个“几乎听不懂”的领域&#xff0c;在数字世界中扮演着至关重要的角色。本文…

解决无法直接抓取链接地址

当我们在爬取一些文章列表的时候&#xff0c;可能无法从接口或者html界面上获取到文章的详细列表 这个时候我们可以通过模拟点击且重写window.open方法&#xff0c;将跳转的地址捕获&#xff0c;并且放到html中去。 这样我们就可以获取到某个文章的详细地址了 // 保存原始的 …

SpringBoot项目启动时提示程序包不存在和找不到符号

一、前言 最近接手同事开发的一个Springboot工作项目&#xff0c;从svn上整体拉取下来后&#xff0c;构建完成后&#xff0c;启动的时候遇到了程序包找不到的情况&#xff0c;记录一下处理过程&#xff1b; 二、项目问题 1、报错信息&#xff1a;启动后报 java: 程序包org.sp…

劳动者最担忧的事情

一、通常的担忧 (一)怕钱不够用 怕钱不够吃饭、住宿、看病、坐车、消费。 (二)怕衰老&#xff0c;没钱养老 二、职业者的担忧 (一)销售员 最怕费劲口舌&#xff0c;力气&#xff0c;最后没把产品推销出去&#xff0c;最后没有业绩&#xff0c;工资不够用。 (二)工厂员工 …

条款10:在constructors 内阻止资源泄漏(resource leak)

想象你正在开发一个多媒体通信簿软件。这个软件可以放置包括人名、地址、电话号码等文字&#xff0c;以及一张个人相片和一段个人声音&#xff08;或许是其姓名的发音&#xff09;。 为实现此软件&#xff0c;你可能设计如下&#xff1a; class Image {//给影像数据使用。 pub…

升级 JDK17 一个不可拒绝的理由!

插&#xff1a; AI时代&#xff0c;程序员或多或少要了解些人工智能&#xff0c;前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家(前言 – 人工智能教程 ) 坚持不懈&#xff0c;越努力越幸运&#xff0c;大家…

Spring AOP基于动态代理的实现的 AOP

目录 代理什么是代理代理模式 静态代理动态代理JDK动态代理CGLIB动态代理Spring AOP使用的是哪种代理&#xff1f; 代理 什么是代理 生活中的代理 房产中介 &#xff1a; 房屋进行租赁时&#xff0c;卖方会把房子授权给中介&#xff0c;由中介代理带客户看房&#xff0c;商谈…

设计师可以学什么程序编程

设计师可以学什么程序编程 在数字化日益发展的今天&#xff0c;设计师们不仅需要具备出色的创意和设计能力&#xff0c;同时掌握一定的程序编程技能也变得越来越重要。这不仅可以帮助设计师更好地将创意转化为实际产品&#xff0c;还能提高工作效率&#xff0c;拓宽职业发展空…

枚举(C语言)

1.枚举定义 枚举是 C 语言中的一种基本数据类型&#xff0c;用于定义一组具有离散值的常量&#xff0c;它可以让数据更简洁&#xff0c;更易读。 枚举类型通常用于为程序中的一组相关的常量取名字&#xff0c;以便于程序的可读性和维护性。 定义一个枚举类型&#xff0c;需要使…