当前位置: 首页 > 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、石英灯管加热系统;。 …...

DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径

目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...

DAY 47

三、通道注意力 3.1 通道注意力的定义 # 新增&#xff1a;通道注意力模块&#xff08;SE模块&#xff09; class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...

聊聊 Pulsar:Producer 源码解析

一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台&#xff0c;以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中&#xff0c;Producer&#xff08;生产者&#xff09; 是连接客户端应用与消息队列的第一步。生产者…...

376. Wiggle Subsequence

376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...

2025盘古石杯决赛【手机取证】

前言 第三届盘古石杯国际电子数据取证大赛决赛 最后一题没有解出来&#xff0c;实在找不到&#xff0c;希望有大佬教一下我。 还有就会议时间&#xff0c;我感觉不是图片时间&#xff0c;因为在电脑看到是其他时间用老会议系统开的会。 手机取证 1、分析鸿蒙手机检材&#x…...

C++ 求圆面积的程序(Program to find area of a circle)

给定半径r&#xff0c;求圆的面积。圆的面积应精确到小数点后5位。 例子&#xff1a; 输入&#xff1a;r 5 输出&#xff1a;78.53982 解释&#xff1a;由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982&#xff0c;因为我们只保留小数点后 5 位数字。 输…...

鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/

使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题&#xff1a;docker pull 失败 网络不同&#xff0c;需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...

短视频矩阵系统文案创作功能开发实践,定制化开发

在短视频行业迅猛发展的当下&#xff0c;企业和个人创作者为了扩大影响力、提升传播效果&#xff0c;纷纷采用短视频矩阵运营策略&#xff0c;同时管理多个平台、多个账号的内容发布。然而&#xff0c;频繁的文案创作需求让运营者疲于应对&#xff0c;如何高效产出高质量文案成…...

Mysql8 忘记密码重置,以及问题解决

1.使用免密登录 找到配置MySQL文件&#xff0c;我的文件路径是/etc/mysql/my.cnf&#xff0c;有的人的是/etc/mysql/mysql.cnf 在里最后加入 skip-grant-tables重启MySQL服务 service mysql restartShutting down MySQL… SUCCESS! Starting MySQL… SUCCESS! 重启成功 2.登…...

Qt 事件处理中 return 的深入解析

Qt 事件处理中 return 的深入解析 在 Qt 事件处理中&#xff0c;return 语句的使用是另一个关键概念&#xff0c;它与 event->accept()/event->ignore() 密切相关但作用不同。让我们详细分析一下它们之间的关系和工作原理。 核心区别&#xff1a;不同层级的事件处理 方…...