当前位置: 首页 > news >正文

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类的具体实现!

相关文章:

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

文章目录 EpollPoller事件分发器类成员变量和成员函数解释私有的成员函数和成员变量成员函数 具体实现常量的作用构造函数和析构函数⭐️poll函数updateChannel函数removeChannel 函数removeChannel 和updateChannel⭐️fillActiveChannels 函数⭐️update 函数 总结 终于要开始…...

SD-WAN:企业网络转型的必然趋势

随着SD-WAN技术的不断进步和完善&#xff0c;越来越多的企业选择利用SD-WAN进行网络转型。根据IDC的研究&#xff0c;47%的企业已经成功迁移到SD-WAN&#xff0c;另有48%的公司计划在未来两个月内部署这一技术。 据Channel Futures报道&#xff0c;一位合作伙伴透露&#xff0c…...

构建高效稳定的短视频直播系统架构

随着短视频直播的迅猛发展&#xff0c;构建一个高效稳定的短视频直播系统架构成为了互联网企业的重要挑战。本文将探讨如何构建高效稳定的短视频直播系统架构&#xff0c;以提供优质的用户体验和满足日益增长的用户需求。 ### 1. 短视频直播系统的背景 短视频直播近年来蓬勃发…...

python分别保存聚类分析结果+KeyError: ‘CustomerID‘报错

如何在完成聚类分析后按聚类编号保存数据并且带上原数据所属ID # 将每个聚类的数据保存到不同的文件中 for cluster_id in range(6): # 假设共有6个聚类cluster_data data[data[cluster] cluster_id]cluster_data_with_customer_id cluster_data.copy()cluster_data_with_…...

Sui与Atoma合作为开发者提供AI支持

AI初创公司Atoma宣布其即将推出的推理网络将与Sui集成&#xff0c;该网络将使开发者能够在他们的应用程序中使用AI工具。Atoma选择Sui作为其第一个区块链集成对象是由于Sui的可扩展性和性能。 尽管生成式AI在过去几年中引起了轰动&#xff0c;但它尚未进入许多消费者应用程序。…...

go-gin中session实现redis前缀和db库选择+单点登录

分别实现了redigo中自动加前缀和session中自动加前缀 等有空了整理一个demo放到github上&#xff0c;到时候求个小星星 在gin-contrib/sessions/redis库中redis的前缀是被封装起来了&#xff0c;所以自定义前缀没有内部方法在这里我们自己实现一下NewStoreWithDBPrefix方法配…...

python-双胞胎字符串

[问题描述]&#xff1a;给定两个字符串s和t&#xff0c;每次可以任意交换s的奇数位和偶数位的字符&#xff0c;即奇数位的字符可以与任意其它奇数位的字符交换&#xff0c;偶数位的字符同样也可以与任意偶数位的字符的字符交换&#xff0c;问能否在有限的次数的交换下使s变为t?…...

万字长文,小白新手怎么开始做YOLO实验,从零开始教!整体思路在这里,科研指南针!

最近专栏来了很多的新手小白&#xff0c;对科研实验的过程感到困惑和无从下手&#xff0c;这篇文章就来讲解一下整体的科研流程&#xff0c;从选择数据集到发表论文的各个步骤&#xff0c;并针对大家在实验中常犯的错误进行解答。并且为大家提供通向我其他相关博客的指引&#…...

MDR-1A用什么前端:深度解析与实用指南

MDR-1A用什么前端&#xff1a;深度解析与实用指南 索尼MDR-1A作为一款备受瞩目的音乐耳机&#xff0c;其音质表现与前端设备的搭配息息相关。那么&#xff0c;MDR-1A用什么前端才能达到最佳的音效体验呢&#xff1f;本文将从四个方面、五个方面、六个方面和七个方面进行深入探…...

01Linux以及操作系统概述

课程目标 1.了解现代操作系统的整体构成及发展历史 2.了解Linux操作系统及其分支版本 3.直观上理解服务器端与桌面端版本的区别 课程实验 1.通过对CentOS和Ubuntu的演示&#xff0c;直观理解Linux与Windows的异同 课堂引入 本章内容主要为大家详细讲解Linux操作系统(以下简…...

华为OD刷题C卷 - 每日刷题 1

1、&#xff08;两数之和&#xff09;&#xff1a; 这段代码是针对力扣&#xff08;LeetCode&#xff09;上的“两数之和”问题。它提供了一个Java类Solution&#xff0c;其中包含一个方法twoSum&#xff0c;该方法接收一个整数数组nums和一个整数目标值target。目的是找出数组…...

基于ELK的日志管理【开发实践】

文章目录 一、ELK简介1.1 ELK的作用与应用1.2 ELK的组成1.3 Elasticsearch1.4 Logstash1.5 Kibana1.6 ELK架构简述1.7 基础知识1.7.1 数据格式1.7.2 正排索引和倒排索引1.7.3 全文搜索 二、ES入门---基于HTTP的使用方式&#xff08;了解&#xff09;2.1 索引操作2.1.1 创建索引…...

音视频开发—音频相关概念:数模转换、PCM数据与WAV文件详解

文章目录 前言1.模拟数字转换&#xff08;ADC&#xff09;1.1ADC的关键步骤&#xff1a; 2.数字模拟转换&#xff08;DAC&#xff09;2.1DAC 的基本流程包括&#xff1a; 3.PCM数据3.1PCM 数据的关键要素包括&#xff1a; 4.WAV文件4.1 WAV的构成4.2WAV文件的标准块结构4.3WAV的…...

Elasticsearch 8.1官网文档梳理 - 十三、Search your data(数据搜索)

Search your data 这里有两个比较有用的参数需要注意一下 Search timeout&#xff1a;设置每个分片的搜索超时时间。从集群级别可以通过 search.default_search_timeout 来设置超时时间。如果在 search.default_search_timeout 设置的时间段内未完成搜索请求&#xff0c;就会…...

笔墨挥毫如游龙 最是经典铁线篆——记著名书法家王子彬

真正的书法大家,必是经历了日积月累的求索磨炼,毕竟书法从来都不是一蹴而就的艺术,因此但凡是急功近利者,其人也是远远无法达到书入臻境的创作高度。而纵观当代书坛界内,其中王子彬先生的艺术声誉可谓是广为人知,作为一名深具传统功底的实力派书法大家,王子彬先生的取法历途无疑…...

智慧校园有哪些特征

随着科技的飞速进步&#xff0c;教育领域正经历着一场深刻的变革。智慧校园&#xff0c;作为这场变革的前沿代表&#xff0c;正在逐步重塑我们的教育理念和实践方式。它不仅仅是一个概念&#xff0c;而是一个集成了物联网、大数据、人工智能等先进技术的综合生态系统&#xff0…...

day25回溯算法part02| 216.组合总和III 17.电话号码的字母组合

216.组合总和III 题目链接/文章讲解 | 视频讲解 class Solution { public:vector<vector<int>> result;vector<int> path;int sum;void backtracking(int n, int k, int startindex) {// int sum accumulate(path.begin(), path.end(), 0);if (sum n &am…...

AWS联网和内容分发服务

概况 VPC Amazon Virtual Private Cloud (Amazon VPC) 让您能够全面地控制自己的虚拟网络环境&#xff0c;包括资源放置、连接性和安全性。首先在 AWS 服务控制台中设置 VPC。然后&#xff0c;向其中添加资源&#xff0c;例如 Amazon Elastic Compute Cloud (EC2) 和 Amazon …...

vscode设置编辑器文件自动保存

步骤 1.打开vscode的设置 2.在搜索栏输入关键字“保存”&#xff1b; 在 Files: Auto Save 设置项&#xff0c;选择自动保存的模式...

SJ705C安全帽高温预处理箱

一、仪器用途 安全帽高温预处理箱是我公司根据安全帽新国家标准检测试验要求而自主设计研发制造。是安全帽检测前做高温预处理的专用设备。 二、仪器特征 1、有PID自整定温度控制仪&#xff0c;控制准确。 2、数显计时、计温器。 3、石英灯管加热系统;。 …...

高等数学(下)题型笔记(八)空间解析几何与向量代数

目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...

Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级

在互联网的快速发展中&#xff0c;高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司&#xff0c;近期做出了一个重大技术决策&#xff1a;弃用长期使用的 Nginx&#xff0c;转而采用其内部开发…...

Psychopy音频的使用

Psychopy音频的使用 本文主要解决以下问题&#xff1a; 指定音频引擎与设备&#xff1b;播放音频文件 本文所使用的环境&#xff1a; Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序

一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...

NLP学习路线图(二十三):长短期记忆网络(LSTM)

在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...

浅谈不同二分算法的查找情况

二分算法原理比较简单&#xff0c;但是实际的算法模板却有很多&#xff0c;这一切都源于二分查找问题中的复杂情况和二分算法的边界处理&#xff0c;以下是博主对一些二分算法查找的情况分析。 需要说明的是&#xff0c;以下二分算法都是基于有序序列为升序有序的情况&#xf…...

【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分

一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计&#xff0c;提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合&#xff1a;各模块职责清晰&#xff0c;便于独立开发…...

IP如何挑?2025年海外专线IP如何购买?

你花了时间和预算买了IP&#xff0c;结果IP质量不佳&#xff0c;项目效率低下不说&#xff0c;还可能带来莫名的网络问题&#xff0c;是不是太闹心了&#xff1f;尤其是在面对海外专线IP时&#xff0c;到底怎么才能买到适合自己的呢&#xff1f;所以&#xff0c;挑IP绝对是个技…...

Python+ZeroMQ实战:智能车辆状态监控与模拟模式自动切换

目录 关键点 技术实现1 技术实现2 摘要&#xff1a; 本文将介绍如何利用Python和ZeroMQ消息队列构建一个智能车辆状态监控系统。系统能够根据时间策略自动切换驾驶模式&#xff08;自动驾驶、人工驾驶、远程驾驶、主动安全&#xff09;&#xff0c;并通过实时消息推送更新车…...

实战设计模式之模板方法模式

概述 模板方法模式定义了一个操作中的算法骨架&#xff0c;并将某些步骤延迟到子类中实现。模板方法使得子类可以在不改变算法结构的前提下&#xff0c;重新定义算法中的某些步骤。简单来说&#xff0c;就是在一个方法中定义了要执行的步骤顺序或算法框架&#xff0c;但允许子类…...