【长文梳理webserver核心】核心类篇

news2024/10/10 5:18:39

前言


有三个核心组件支撑一个reactor实现 [持续] 的 [监听] 一组fd,并根据每个fd上发生的事件 [调用] 相应的处理函数。这三个组件就是 EventLoop 、Channel 以及 Poller 三个类,其中 EventLoop 可以看作是对业务线程的封装,而 Channel 可以看作是对 每个已经建立连接 的封装(即accept(3) 返回的文件描述符)


EventLoop类


先明确一点,作为一个网络服务器,需要有 持续监听、持续获取监听结果、持续处理监听结果对应事件的能力,也就是 循环 调用Poller::poll方法获取实际发生事件的Channel集合,然后调用这些Channel里面保管的不同类型事件的处理函数,也就是Channel::HandlerEvent方法。绿色部分不懂没关系,继续看。

总之EventLoop就是负责实现 循环 ,负责驱动循环的主要模块,Channel和Poller都是他的手下,因为EventLoop整合封装了两者,并且向上提供了更方便的接口来使用。

在EventLoop的类定义中,除了⼀些状态量以外,每个 EventLoop 持有⼀个 Poller 的智能指针(对 epoll / poll 的封装),⼀个用于 EventLoop 之间通信的 Channel ,自己的线程 id,互斥锁以及装有等待处理函数的 vector 。很明显, EventLoop 并不直接管理各个连接的 Channel (文件描述符的封装),而是通过Poller 来进行的。 EventLoop 中最核心的函数就是 EventLoop::Loop() 。

EventLoop::loop(),代码如下:

void EventLoop::loop()
{ 
    // 开始事件循环 调⽤该函数的线程必须是该EventLoop所在线程 
    assert(!is_looping_);
    assert(is_in_loop_thread());
    is_looping_ = true;
    is_stop_ = false;

    while(!is_stop_)
    {
         // 1、epoll_wait阻塞 等待就绪事件
         auto ready_channels = poller_->Poll();

         is_event_handling_ = true;

         // 2、处理每个就绪事件(不同channel绑定了不同的callback)
         for (auto& channel : ready_channels) {
             channel->HandleEvents();
         }

         is_event_handling_ = false;
         // 3、执⾏正在等待的函数(fd注册到epoll内核事件表)

         PerformPendingFunctions();
         // 4、处理超时事件 到期了就从定时器⼩根堆中删除(定时器析构会EpollDel掉fd)
         poller_->HandleExpire();
     }
     is_looping_ = false;
}

每个EventLoop对象都唯一绑定了一个线程,这个线程其实就在一直执行这个函数里面的while循环,这个while循环的大致逻辑比较简单。就是调用Poller::poll方法获取事件监听器上的监听结果。接下来在loop里面就会调用监听结果中每一个Channel的处理函数HandlerEvent( )。每一个Channel的处理函数会根据Channel类中封装的实际发生的事件,执行Channel类中封装的各事件处理函数。比如一个Channel发生了可读事件,可写事件,则这个Channel的HandlerEvent( )就会调用提前注册在这个Channel的可读事件和可写事件处理函数,又比如另一个Channel只发生了可读事件,那么HandlerEvent( )就只会调用提前注册在这个Channel中的可读事件处理函数。

所以总结,每个EventLoop实际上就做了四件事

  • epoll_wait阻塞 等待就绪事件(没有注册其他fd时,可以通过event_fd来异步唤醒)
  • 处理每个就绪事件
  • 执行正在等待的函数(fd注册到epoll内核事件表)
  • 处理超时事件,到期了就从定时器小根堆中删除

Channel类 

接下来解释EventLoop的两个手下之一Channel,Channel类其实相当于一个文件描述符的保姆。

想要通过 IO 多路复用(epoll / poll)监听某个文件描述符,就需要把这个 fd 和该 fd 感兴趣 的事件通过 epoll_ctl 注册到 IO 多路复用模块(事件监听器)上。当 IO 多路复用模块监听到该 fd 发生了某个事件。事件监听器返回发生事件的 fd 集合(有哪些 fd 发生了事件)以及每个 fd 的事件集合(每个 fd 具体发生了什么事件)。 

Channel类则封装了一个 [fd] 和这个 [fd感兴趣事件] 以及事件监听器监听到 [该fd实际发生的事件]。同时Channel类还提供了设置该fd的感兴趣事件,以及将该fd及其感兴趣事件注册到事件监听器或从事件监听器上移除,以及保存了该fd的每种事件对应的处理函数。

Channel类重要的成员变量

  • fd_:Channel对象照看的文件描述符
  • events_:代表fd感兴趣的事件类型集合,或者说正在监听的事件
  • revents_:代表事件监听器实际监听到该fd发生的事件类型集合,或者说是返回的就绪事件,当事件监听器监听到一个fd发生了什么事件,通过Channel::set_revents()函数来设置revents值
  • last_events_:上一此事件(主要用于记录如果本次事件和上次事件⼀样 就没必要调用
  • read_handler_,write_handler_,update_handler_,error_handler_:这些是std::function类型的各种回调函数,代表这个Channel为这个文件描述符保存的各事件类型发生时的处理函数。

Channel类重要的成员方法

Channel::HandleEvents()方法

/ IO事件的回调函数 EventLoop中调⽤Loop开始事件循环 会调⽤Poll得到就绪事件 
// 然后依次调⽤此函数处理就绪事件

void Channel::HandleEvents() {
    events_ = 0;

    // 触发挂起事件 并且没触发可读事件
    if ((revents_ & EPOLLHUP) && !(revents_ & EPOLLIN)) {
        events_ = 0;
        return;
    }

    // 触发错误事件
    if (revents_ & EPOLLERR) {
        HandleError();
        events_ = 0;
        return;
     } 

     // 触发可读事件 | ⾼优先级可读 | 对端(客户端)关闭连接
    if (revents_ & (EPOLLIN | EPOLLPRI | EPOLLRDHUP)) {
        HandleRead();
    }

     // 触发可写事件
    if (revents_ & EPOLLOUT) {
        HandleWrite();
    }

     //处理更新监听事件(EpollMod)
    HandleUpdate();
}

每个Channel对象只属于一个EventLoop ,即只属于一个IO线程,只负责一个文件描述符fd的IO时间分发,但并不拥有这个fd,Channel把不同的IO事件分发为不同的回调,回调用C++11的特性function表示。

从Channel类的定义中可以看出,每个Channel持有一个文件描述符,正在监听的事件,已经发生的事件(由Poller返回),以及各个事件的回调函数的Function对象。

总的来说,Channel就是对fd事件的封装,包括注册它的事件以及回调。EventLoop通过调用Channel::handleEvent()来执行Channel的读写事件。Channel::handleEvent() 的实现也非常简单,就是比较已经发生的事件(由 Poller 返回),来调用对应的回调函数(读、写、错误)。

Poller类

Poller 类的作用就是负责监听文件描述符事件是否触发以及返回发生事件的文件描述符以及具体事件。所以一个Poller 对象对应⼀个 IO 多路复用模块。在 muduo 中,⼀个 EventLoop 对应一个Poller 。

Epoll代码如下 :

class Epoll {
public:
    Epoll();
    ~Epoll();
    void epoll_add(const sp_Channel& request);
    void epoll_mod(const sp_Channel& request);
    void epoll_del(const sp_Channel& request);
    void poll(std::vector<sp_Channel>& req);
private:
    int epollFd_;
    std::vector<epoll_event> events_; // epoll_wait()返回的活动事件都放在这个数组⾥
    std::unordered_map<int, sp_Channel> channelMap_;
};

Poller类的主要成员变量有三个:

  • epollFd_:就是用epoll_create方法返回的epoll句柄
  • events:存放epoll_wait()返回的活动事件,是一个结构体
  • channelMap_ :这个变量是 std::unordered_map<int, std::shared_ptr<Channel>> 类型,负责记录 文件描述符fd -> Channel 的映射,也帮忙保管所有注册在你这个 Poller 上的 Channel 。

其他函数无非就是对Epoll_ctl(4)和 Epoll_wait(4)的封装

void Epoll::poll(std::vector<sp_Channel>& req) {
    int event_count = 
        Epoll_wait(epollFd_, &*events_.begin(), events_.size(), EPOLLWAIT_TIME);
    for(int i = 0; i < event_count; ++i) {
        int fd = events_[i].data.fd;
        sp_Channel temp = channelMap_[fd];
        temp->setRevents(events_[i].events);
        req.emplace_back(std::move(temp));
    }
     // LOG << "Epoll finished";
}

Epoll::poll(1) 这个函数可以说是 Poller 的核心了,当外部调用 poll 方法的时候,该方法底层其实是通过epoll_wait 获取这个事件监听器上发生事件的 fd 及其对应发生的事件,我们知道每个 fd 都是由⼀个Channel封 装的,通过哈希表 channelMap_ 可以根据 fd 找到封装这个 fd 的 Channel 。将 IO 多路复用模块监听到该 fd 发生 的事件写进这个 Channel 中的 revents 成员变量中。然后把这个 Channel 装进 req 中。这样,当外界调用完poll 之后就能拿到 IO 多路复用模块的监听结果( std::vector<sp_Channel>& req )。

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

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

相关文章

从零开始搭建一个node.js后端服务项目

一、下载node.js及配置环境 网上很多安装教程&#xff0c;此处就不再赘述了 版本信息 C:\Users\XXX>node -v v20.15.0C:\Users\XXX>npm -v 10.7.0 二、搭建node.js项目及安装express框架 在任意位置创建一个项目文件夹&#xff0c;此处项目文件夹名为test&#xff0…

右键菜单添加cmd

regedit 计算机\HKEY_CLASSES_ROOT\Directory\Background\shell\命令提示符\command 数据&#xff1a;cmd.exe /s /k title 命令提示符 或软件商店安装 Windows Terminal

基于element-ui的upload组件与阿里云oss对象存储的文件上传(采用服务端签名后直传的方式)

服务端签名后直传图解 步骤 1 开通阿里云OSS对象存储服务&#xff0c;创建新的Bucket 2 创建子账户获取密钥 创建用户 添加权限 后端 1 新建一个第三方服务的模块 third-party pom文件 <?xml version"1.0" encoding"UTF-8"?> <project x…

HAL+M4学习记录_3

一、HAL库开发框架 记录HAL学习过程 1.1 CMSIS CMSIS&#xff08;Cortex微控制器软件接口标准&#xff09;&#xff0c;用于提供用户和硬件间的接口&#xff0c;用户通过CMSIS标准对Cortex微控制器内部寄存器单元进行读写 1.2 HAL库 HAL&#xff08;硬件抽象层&#xff09;为…

加固与脱壳04 - 一些简单的脱壳方法

这里只讨论一些简单壳的脱壳方法及其原因。 FRIDA-DEXDump https://github.com/hluwa/FRIDA-DEXDump 适用于不需要研究那些被强保护起来的代码&#xff0c;只是想单纯的看看某个地方的业务逻辑。 原理&#xff1a; 对于完整的 dex&#xff0c;采用暴力搜索 dex035&#xf…

云栖实录 | Hologres3.0全新升级:一体化实时湖仓平台

本文根据2024云栖大会实录整理而成&#xff0c;演讲信息如下&#xff1a; 演讲人&#xff1a; 姜伟华 | 阿里云智能集团资深技术专家、Hologres 负责人 丁 烨 | 阿里云智能集团产品专家、Hologres 产品负责人 活动&#xff1a; 2024 云栖大会 - 商用大数据计算与分析平台论…

基于Arduino的超声波和舵机模块集成使用

一.超声波模块和舵机模块集成使用 超声波模块&#xff1a;HC-SR04舵机模块&#xff1a;SG90目的&#xff1a;通过Arduino UNO核心板控制舵机旋转到不同位置&#xff0c;同时获取不同位置超声波模块和障碍物间的距离&#xff0c;配合控制算法&#xff0c;进行基于超声波智能避障…

需求10——通过改一个小bug来学习如何定位问题

在浏览我之前完成的一些小需求时&#xff0c;我发现了一个非常有价值的需求。这个需求可以让我深入了解系统中关于故障上报的功能。通过完善这个需求&#xff0c;我能够全面掌握整个故障上报的流程。 这个需求主要是关于故障上报流程中出现的问题。当前的流程如下&#xff1a;…

ML 系列:机器学习和深度学习的深层次总结(16) — 提高 KNN 效率-使用 KD 树和球树实现更快的算法

一、说明 在机器学习系列的第 16 节&#xff0c;我们重点介绍了提高 K 最近邻 &#xff08;KNN&#xff09; 算法的效率&#xff0c;这是一种广泛用于分类和回归任务的方法。虽然 KNN 简单有效&#xff0c;但对于大型数据集来说&#xff0c;其计算成本可能会令人望而却步。为了…

基于SpringBoot问卷调查系统小程序【附源码】

基于SpringBoot问卷调查系统小程序 效果如下&#xff1a; 管理员登录界面 管理员功能界面 调查人管理界面 问卷调查管理界面 问卷题目管理界面 用户登录界面 APP首页界面 公告信息界面 研究背景 随着科学技术的飞速发展&#xff0c;各行各业都在努力与现代先进技术接轨&…

组织病理学图像中的再识别|文献速递--基于多模态-半监督深度学习的病理学诊断与病灶分割

Title 题目 Re-identification from histopathology images 组织病理学图像中的再识别 01 文献速递介绍 在光学显微镜下评估苏木精-伊红&#xff08;H&E&#xff09;染色切片是肿瘤病理诊断中的标准程序。随着全片扫描仪的出现&#xff0c;玻片切片可以被数字化为所谓…

怎么在单片机裸机程序中移植EasyLogger?

1、介绍 EasyLogger 是一款超轻量级、高性能的C日志库&#xff0c;非常适合对资源敏感的软件项目。例如&#xff1a;IoT产品、可穿戴设备、智能家居等等。相比log4c、zlog这些知名的C日志库&#xff0c;EasyLogger的功能更加简单&#xff0c;提供给用户的接口更少&#xff0c;但…

肺腺癌预后新指标:全切片图像中三级淋巴结构密度的自动化量化|文献精析·24-10-09

小罗碎碎念 本期这篇文章&#xff0c;我去年分享过一次。当时发表在知乎上&#xff0c;没有标记参考文献&#xff0c;配图的清晰度也不够&#xff0c;并且分析的还不透彻&#xff0c;所以趁着国庆假期重新分析一下。 这篇文章的标题为《Computerized tertiary lymphoid structu…

基于springboot vue 校园失物招领平台的设计与实现

博主介绍&#xff1a;专注于Java&#xff08;springboot ssm springcloud等开发框架&#xff09; vue .net php phython node.js uniapp小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设&#xff0c;从业十五余年开发设计教学工作☆☆☆ 精彩专栏推荐订阅☆☆☆☆…

【AIGC】OpenAI API在快速开发中的实践与应用:优化ChatGPT提示词Prompt加速工程

博客主页&#xff1a; [小ᶻZ࿆] 本文专栏: AIGC | ChatGPT 文章目录 &#x1f4af;前言&#x1f4af;使用最新型号确保最佳实践利用最新模型进行高效任务处理为什么要选择最新模型&#xff1f;结论 &#x1f4af;指令与上下文的分隔最佳实践分隔指令和上下文的重要性使用符…

叉车毫米波雷达防撞技术,保护叉车作业安全

在叉车作业频繁的仓库与物流中心&#xff0c;安全隐患往往隐藏于细微之处&#xff0c;稍有不便可能引发重大事故。我们的叉车毫米波防撞系统方案&#xff0c;正是针对这一痛点而精心设计的创新之作。该系统通过集成的毫米波雷达技术&#xff0c;实现了对叉车周边环境的实时、精…

【动态规划】dp之斐波那契数列模型

学习编程就得循环渐进&#xff0c;扎实基础&#xff0c;勿在浮沙筑高台 循环渐进Forward-CSDN博客 目录 循环渐进Forward-CSDN博客 第N个泰波那契序数 思路&#xff1a; 代码实现&#xff1a; 三步问题 思路&#xff1a; 代码实现&#xff1a; 使用最小花费爬楼梯 思路…

C语言 | 第十三章 | 二维数组 冒泡排序 字符串指针 断点调试

P 120 数组应用案例 2023/1/29 一、应用案例 案例一&#xff1a;创建一个char类型的26个元素的数组&#xff0c;分别 放置’A’-Z‘。使用for循环访问所有元素并打印出来。提示&#xff1a;字符数据运算 ‘A’1 -> ‘B’ #include<stdio.h>void main(){/*创建一个c…

【优选算法之BFS】No.15--- 经典BFS解决FloodFill算法和解决最短路问题

文章目录 前言一、BFS解决FloodFill算法示例&#xff1a;1.1 图像渲染1.2 岛屿数量1.3 岛屿的最⼤⾯积1.4 被围绕的区域 二、BFS解决最短路问题2.1 迷宫中离⼊⼝最近的出⼝2.2 最⼩基因变化2.3 单词接⻰2.4 为⾼尔夫⽐赛砍树 前言 &#x1f467;个人主页&#xff1a;小沈YO. &a…

Linux高级编程_31_消息队列

文章目录 消息队列作用&#xff1a;特点&#xff1a;消息队列限制值&#xff1a;注意&#xff1a;命令&#xff1a;ftok函数作用&#xff1a;语法&#xff1a; msgget函数作用&#xff1a;语法&#xff1a; msgsnd函数作用&#xff1a;语法&#xff1a; msgrcv函数作用&#xf…