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需要调用的函数。
removeChannel 和updateChannel
从这两个函数理我们可以看出,他们其实是为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 fd和void *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::removeChannel、EPollPoller::updateChannel调用,用来更新Channel的封装的fd以及其需要监控的相关事件。
总结
EPollPoller 类实现了基于 epoll 的 I/O 多路复用,通过监控多个文件描述符上的事件,并在事件发生时通知相应的 Channel 对象来处理事件。通过实现这些函数,EPollPoller 能够高效地管理和分发事件。
下一节,我们将讲解EventLoop类的具体实现!
相关文章:
webserver服务器从零搭建到上线(八)|EpollPoller事件分发器类
文章目录 EpollPoller事件分发器类成员变量和成员函数解释私有的成员函数和成员变量成员函数 具体实现常量的作用构造函数和析构函数⭐️poll函数updateChannel函数removeChannel 函数removeChannel 和updateChannel⭐️fillActiveChannels 函数⭐️update 函数 总结 终于要开始…...
SD-WAN:企业网络转型的必然趋势
随着SD-WAN技术的不断进步和完善,越来越多的企业选择利用SD-WAN进行网络转型。根据IDC的研究,47%的企业已经成功迁移到SD-WAN,另有48%的公司计划在未来两个月内部署这一技术。 据Channel Futures报道,一位合作伙伴透露,…...
构建高效稳定的短视频直播系统架构
随着短视频直播的迅猛发展,构建一个高效稳定的短视频直播系统架构成为了互联网企业的重要挑战。本文将探讨如何构建高效稳定的短视频直播系统架构,以提供优质的用户体验和满足日益增长的用户需求。 ### 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集成,该网络将使开发者能够在他们的应用程序中使用AI工具。Atoma选择Sui作为其第一个区块链集成对象是由于Sui的可扩展性和性能。 尽管生成式AI在过去几年中引起了轰动,但它尚未进入许多消费者应用程序。…...
go-gin中session实现redis前缀和db库选择+单点登录
分别实现了redigo中自动加前缀和session中自动加前缀 等有空了整理一个demo放到github上,到时候求个小星星 在gin-contrib/sessions/redis库中redis的前缀是被封装起来了,所以自定义前缀没有内部方法在这里我们自己实现一下NewStoreWithDBPrefix方法配…...
python-双胞胎字符串
[问题描述]:给定两个字符串s和t,每次可以任意交换s的奇数位和偶数位的字符,即奇数位的字符可以与任意其它奇数位的字符交换,偶数位的字符同样也可以与任意偶数位的字符的字符交换,问能否在有限的次数的交换下使s变为t?…...
万字长文,小白新手怎么开始做YOLO实验,从零开始教!整体思路在这里,科研指南针!
最近专栏来了很多的新手小白,对科研实验的过程感到困惑和无从下手,这篇文章就来讲解一下整体的科研流程,从选择数据集到发表论文的各个步骤,并针对大家在实验中常犯的错误进行解答。并且为大家提供通向我其他相关博客的指引&#…...
MDR-1A用什么前端:深度解析与实用指南
MDR-1A用什么前端:深度解析与实用指南 索尼MDR-1A作为一款备受瞩目的音乐耳机,其音质表现与前端设备的搭配息息相关。那么,MDR-1A用什么前端才能达到最佳的音效体验呢?本文将从四个方面、五个方面、六个方面和七个方面进行深入探…...
01Linux以及操作系统概述
课程目标 1.了解现代操作系统的整体构成及发展历史 2.了解Linux操作系统及其分支版本 3.直观上理解服务器端与桌面端版本的区别 课程实验 1.通过对CentOS和Ubuntu的演示,直观理解Linux与Windows的异同 课堂引入 本章内容主要为大家详细讲解Linux操作系统(以下简…...
华为OD刷题C卷 - 每日刷题 1
1、(两数之和): 这段代码是针对力扣(LeetCode)上的“两数之和”问题。它提供了一个Java类Solution,其中包含一个方法twoSum,该方法接收一个整数数组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的使用方式(了解)2.1 索引操作2.1.1 创建索引…...
音视频开发—音频相关概念:数模转换、PCM数据与WAV文件详解
文章目录 前言1.模拟数字转换(ADC)1.1ADC的关键步骤: 2.数字模拟转换(DAC)2.1DAC 的基本流程包括: 3.PCM数据3.1PCM 数据的关键要素包括: 4.WAV文件4.1 WAV的构成4.2WAV文件的标准块结构4.3WAV的…...
Elasticsearch 8.1官网文档梳理 - 十三、Search your data(数据搜索)
Search your data 这里有两个比较有用的参数需要注意一下 Search timeout:设置每个分片的搜索超时时间。从集群级别可以通过 search.default_search_timeout 来设置超时时间。如果在 search.default_search_timeout 设置的时间段内未完成搜索请求,就会…...
笔墨挥毫如游龙 最是经典铁线篆——记著名书法家王子彬
真正的书法大家,必是经历了日积月累的求索磨炼,毕竟书法从来都不是一蹴而就的艺术,因此但凡是急功近利者,其人也是远远无法达到书入臻境的创作高度。而纵观当代书坛界内,其中王子彬先生的艺术声誉可谓是广为人知,作为一名深具传统功底的实力派书法大家,王子彬先生的取法历途无疑…...
智慧校园有哪些特征
随着科技的飞速进步,教育领域正经历着一场深刻的变革。智慧校园,作为这场变革的前沿代表,正在逐步重塑我们的教育理念和实践方式。它不仅仅是一个概念,而是一个集成了物联网、大数据、人工智能等先进技术的综合生态系统࿰…...
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) 让您能够全面地控制自己的虚拟网络环境,包括资源放置、连接性和安全性。首先在 AWS 服务控制台中设置 VPC。然后,向其中添加资源,例如 Amazon Elastic Compute Cloud (EC2) 和 Amazon …...
vscode设置编辑器文件自动保存
步骤 1.打开vscode的设置 2.在搜索栏输入关键字“保存”; 在 Files: Auto Save 设置项,选择自动保存的模式...
SJ705C安全帽高温预处理箱
一、仪器用途 安全帽高温预处理箱是我公司根据安全帽新国家标准检测试验要求而自主设计研发制造。是安全帽检测前做高温预处理的专用设备。 二、仪器特征 1、有PID自整定温度控制仪,控制准确。 2、数显计时、计温器。 3、石英灯管加热系统;。 …...
19c补丁后oracle属主变化,导致不能识别磁盘组
补丁后服务器重启,数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后,存在与用户组权限相关的问题。具体表现为,Oracle 实例的运行用户(oracle)和集…...
工业安全零事故的智能守护者:一体化AI智能安防平台
前言: 通过AI视觉技术,为船厂提供全面的安全监控解决方案,涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面,能够实现对应负责人反馈机制,并最终实现数据的统计报表。提升船厂…...
day52 ResNet18 CBAM
在深度学习的旅程中,我们不断探索如何提升模型的性能。今天,我将分享我在 ResNet18 模型中插入 CBAM(Convolutional Block Attention Module)模块,并采用分阶段微调策略的实践过程。通过这个过程,我不仅提升…...
Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...
条件运算符
C中的三目运算符(也称条件运算符,英文:ternary operator)是一种简洁的条件选择语句,语法如下: 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true,则整个表达式的结果为“表达式1”…...
电脑插入多块移动硬盘后经常出现卡顿和蓝屏
当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时,可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案: 1. 检查电源供电问题 问题原因:多块移动硬盘同时运行可能导致USB接口供电不足&#x…...
【项目实战】通过多模态+LangGraph实现PPT生成助手
PPT自动生成系统 基于LangGraph的PPT自动生成系统,可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析:自动解析Markdown文档结构PPT模板分析:分析PPT模板的布局和风格智能布局决策:匹配内容与合适的PPT布局自动…...
srs linux
下载编译运行 git clone https:///ossrs/srs.git ./configure --h265on make 编译完成后即可启动SRS # 启动 ./objs/srs -c conf/srs.conf # 查看日志 tail -n 30 -f ./objs/srs.log 开放端口 默认RTMP接收推流端口是1935,SRS管理页面端口是8080,可…...
C# SqlSugar:依赖注入与仓储模式实践
C# SqlSugar:依赖注入与仓储模式实践 在 C# 的应用开发中,数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护,许多开发者会选择成熟的 ORM(对象关系映射)框架,SqlSugar 就是其中备受…...
数据库分批入库
今天在工作中,遇到一个问题,就是分批查询的时候,由于批次过大导致出现了一些问题,一下是问题描述和解决方案: 示例: // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...
