5.实现 Channel 类,Reactor 模式初步形成
目录
由联合体epoll_data引出类Channel
结构体epoll_data_t
Channel类
Channel类的使用
Epoll类的改变
由联合体epoll_data引出类Channel
在之前使用epoll时,有使用到一个结构体epoll_event
// 这是联合体,多个变量共用同一块内存
typedef union epoll_data { void *ptr; // 指针类型,可以指向任意数据类型,通常用于存储自定义数据结构的指针 int fd; // 文件描述符,常用于指代与 epoll_ctl 操作相关的文件描述符 uint32_t u32; // 32 位无符号整形,可用于存储无符号整数类型的用户数据 uint64_t u64; // 64 位无符号整形,适合存储较大的整数或数据
} epoll_data_t; // epoll_event 结构体用于描述要监控的事件及其相关的数据
struct epoll_event { uint32_t events; /* epoll 事件类型 */ epoll_data_t data; /* 用户数据变量 */
}; // 调用 epoll_ctl 函数来控制与 epoll 相关的事件
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
结构体epoll_data_t
epoll_event中有个成员变量data,这是一个联合体。初学情况下,我们会使用data的成员变量fd。
回想一下,我们使用epoll_event的时候
// 调用 epoll_wait,等待事件发生
// epfd: epoll 实例的文件描述符
// events: 用于存储被激活事件的数组
// MAX_SIZE: 事件数组的最大容量
// -1: 阻塞模式,直到有事件发生
int nums = epoll_wait(epfd, events, MAX_SIZE, -1); // 遍历所有返回的事件
for (int i = 0; i < nums; ++i) { // 检查当前事件的文件描述符是否为监听文件描述符 (lfd) if (events[i].data.fd == lfd) { handl1(); // 处理连接操作,例如接受新的客户端连接 } // 检查是否为可读事件 else if (events[i].events & EPOLLIN) { handl2(); // 处理通信操作,例如读取客户端发送的数据 }
}
这样我们只使用联合体中成员fd,只能得到文件描述符。
当时当我们想要获取关于该fd更多的信息时候,就不妥了。比如想获取该用户连接成功的时间。这时可能有朋友想到可以使用std::unordered_map来存储,但这就需要借助外部的变量。
那么,我们换一种思路,联合体epoll_data中还有个变量ptr,其是个void*指针。那就可以指向任何一块地址空间,也可以指向一个类对象,这就可以包含关于这个文件描述符的更多信息。
按照上面的思路,我们可以把firstTime和fd构成一个结构体,之后就使用联合体epoll_data的ptr,不使用fd。
// 用户自定义的数据结构,用于存储与文件描述符相关的详细信息
struct moreMsg { int fd; // 文件描述符,标识特定的套接字或文件 int64_t firstTime; // 连接成功的时间,通常用于记录客户端的连接时间 int events; // epoll_wait 返回的活跃事件,用于存储事件类型(如可读、可写等)
};
在新用户连接成功的时候,就创建moreMesg结构体,给fd和firstTime和events都赋值。这个epoll_data 会随着 epoll_wait 返回的 epoll_event 一并返回。那附带的自定义消息,就是ptr指向这个自定义消息。
那么我们可以把一个文件描述符封装成一个类,这个类里面有更多的关于这个文件描述符的信息,我们把他叫做Channel类,用这个void*指针指向Channel类对象。
Channel类
class Channel {
public: // 构造函数,初始化与 Epoll 对象的指针和文件描述符 Channel(Epoll* ep, int fd); // 设置关注的事件类型 void setEvent(uint32_t events); // 获取关注的事件类型 uint32_t Event() const; // 设置从 epoll_wait 返回的事件类型 void setRevent(uint32_t revents); // 获取从 epoll_wait 返回的事件类型 uint32_t Revent() const; // 检查通道是否在 epoll 中 bool isInepoll(); // 设置通道是否在 epoll 中的状态 void setInEpoll(bool in); // 获取该通道关联的文件描述符 int fd(); // 启用读取事件 void enableReading(); private: Epoll* ep_; // 指向关联的 Epoll 对象的指针,用于更新事件 int fd_; // 文件描述符,表示该通道关联的文件或 socket uint32_t events_; // 用户关心的事件类型,例如可读或可写 uint32_t revents_; // 从 epoll_wait 返回的活跃事件类型 bool isInEpoll_; // 标记该通道是否在 epoll 的红黑树中
};
成员变量:
- 那一定会有文件描述符fd,每个Channel对象自始至终只负责一个fd,但是它不拥有这个fd,即也不会在析构的时候关闭这个fd;
- 接着有Epoll类对象,我们要通过epoll把fd添加到epoll上,一个channel只跟着一个Epoll,通过enableReading()函数添加fd到该epoll上。
- events_是channel关心的IO事件,由用户设置(即用户想监听该fd的哪类事件,如读事件)
- revents_是通过epoll_wait()返回的目前该fd活动的事件(即我们要根据这个类型执行相对的操作,返回读事件就执行读操作)。
- isInEpoll_是用于判断该fd是否在epoll红黑树上,进而判断是使用EPOLL_CTL_ADD还是使用EPOLL_CTL_MOD。
一个channel就对应了一个客户端,或者服务器端。
Channel类的使用
// 创建一个 Socket 对象,用于网络通信
Socket serv_socket; // 创建一个 Epoll 对象,用于管理多个文件描述符的事件
Epoll ep; // 创建一个 Channel 对象,关联先前创建的 Epoll 对象和 Socket 的文件描述符
Channel* ch = new Channel(&ep, serv_socket.fd()); // 说明:Channel 的构造函数需要传入一个指向 Epoll 对象的指针
// 以及要监视的文件描述符(即 serv_socket.fd()),
// 这样 Channel 才能在事件被触发时有效地与 Epoll 进行交互。
那接下来我们需要把fd添加到epoll红黑树上,之前是使用ep.update(fd,EPOLLIN,EPOLL_CTL_ADD);
那现在是通过Channel去把fd添加到红黑树上
// 启用读取事件的成员函数
void enableReading() { // 设置关注的事件类型为可读事件(EPOLLIN) setEvent(EPOLLIN); // 更新 epoll 中的通道,将这个通道的新事件添加到 epoll 中 ep_->updateChannel(this);
} // 设置关注的事件类型
void setEvent(uint32_t events) { // 将传入的事件类型存储到成员变量 events_ 中 events_ = events;
}
其主要是做了两件事,将要监听的事件events设置为读事件,然后用成员变量ep_去更新channel。
Epoll类的改变
Channel::enableReading()函数内部调用了Epoll::updateChannel(Channel*)。updateChannel成员函数的参数类型是Channel*,猜想函数内部是对channel做了一些操作的。
EpoLL类的updateChannel()的实现如下:
// 更新与 epoll 关联的通道
void updateChannel(Channel* ch) { // 获取通道的文件描述符 int fd = ch->fd(); // 创建 epoll_event 结构体,用于描述要修改的事件 struct epoll_event ev; // 清空结构体 memset(&ev, 0, sizeof(ev)); // 将通道的指针存储在 epoll_event 结构中 ev.data.ptr = ch; // 这里很重要,我们使用指针而不是文件描述符 ev.events = ch->Event(); // 获取通道当前设置的事件类型 // 检查通道是否已在 epoll 树中 if (ch->isInepoll()) { // 如果通道已经在 epoll 树中,修改其事件 epoll_ctl(epfd_, EPOLL_CTL_MOD, fd, &ev); } else { // 如果通道不在 epoll 树中,添加该通道 epoll_ctl(epfd_, EPOLL_CTL_ADD, fd, &ev); ch->setInEpoll(true); // 设置通道已在 epoll 树上 }
} // 注意:原函数省略了错误判断,实际使用中应该检查
// epoll_ctl 的返回值,处理可能的错误情况
EPOLL的epoll_wait()函数代码也有修改,有了Channel之后,返回的就是vector<Channel*>。
// 使用 Channel 类的 Epoll_wait 函数
void Epoll_wait(vector<Channel*>& active, int timeout = 10) { // 调用 epoll_wait,等待事件发生 int nums = epoll_wait(epfd_, events_, SIZE, timeout); // 遍历返回的事件数量 for (int i = 0; i < nums; ++i) { // 从 events_ 中获取 Channel 对象的指针 Channel* ch = static_cast<Channel*>(events_[i].data.ptr); // 设置从 epoll_wait 返回的事件类型 ch->setRevents(events_[i].events); // 将活跃的通道添加到 active 列表中 active.emplace_back(ch); }
} // 以前的写法
/*
void Epoll_wait(vector<epoll_event>& active, int timeout = 10) { // 调用 epoll_wait,等待事件发生 int nums = epoll_wait(epfd_, events_, SIZE, timeout); // 遍历返回的事件数量 for (int i = 0; i < nums; ++i) { // 将 events_ 中的每个事件添加到 active 列表中 active.emplace_back(events_[i]); }
}
*/
那么在编写服务端程序的时候,我们不再用epoll_events结构体,而是使用Channel。
// 创建一个 Epoll 对象
Epoll ep; // 事件循环,持续等待和处理 I/O 事件
while (true) { // 创建一个用于存储活动通道的向量 vector<Channel*> activeChannel; // 调用 Epoll 的 Wait 函数,获取活跃的通道 ep.Epoll_Wait(activeChannel); // 获取活跃通道的数量 int nums = activeChannel.size(); // 遍历所有活跃通道 for (int i = 0; i < nums; ++i) { // 获取当前活跃通道关联的文件描述符 int fd = activeChannel[i]->fd(); // 如果文件描述符是监听文件描述符 (lfd),处理连接请求 if (fd == lfd) { handle1(); // 处理新连接 } // 检查当前通道是否有可读事件 else if (activeChannel[i]->Event() & EPOLLIN) { handle2(); // 处理通信操作 } }
}
这一节我们还没有使用回调函数,这不符合我们的要求的,这将在下一节中进行修改。
通过这一节的修改,我们可以获得关于epoll返回的活跃的文件描述符的更多信息,添加了Channel类。接下来也会逐渐实现Reator模式。
相关文章:
5.实现 Channel 类,Reactor 模式初步形成
目录 由联合体epoll_data引出类Channel 结构体epoll_data_t Channel类 Channel类的使用 Epoll类的改变 由联合体epoll_data引出类Channel 在之前使用epoll时,有使用到一个结构体epoll_event // 这是联合体,多个变量共用同一块内存 typedef union…...
深度剖析:U盘打不开难题与应对之策
一、引言 在数字化办公与数据存储的浪潮中,U盘凭借其小巧便携、大容量存储等优势,成为了人们日常数据传输与备份的得力助手。然而,当我们急需调用U盘中的关键数据时,却常常遭遇U盘打不开的棘手状况。U盘打不开不仅会影响工作进度&…...
洛谷题单3-P5721 【深基4.例6】数字直角三角形-python-流程图重构
题目描述 给出 n n n,请输出一个直角边长度是 n n n 的数字直角三角形。所有数字都是 2 2 2 位组成的,如果没有 2 2 2 位则加上前导 0 0 0。 输入格式 输入一个正整数 n n n。 输出格式 输出如题目要求的数字直角三角形。 输入输出样例 输入…...
一起学大语言模型-通过ollama搭建本地大语言模型服务
文章目录 Ollama的github地址链接安装下载需求配置更改安装目录安装更改下载的模型存储位置Ollama一些目录说明日志目录 运行一个模型测试下测试下更改服务监听地址和端口号 Ollama的github地址链接 https://github.com/ollama/ollama 安装 下载 mac安装包下载地址࿱…...
AllData数据中台商业版发布版本1.2.9相关白皮书发布
文章末尾网盘链接获取白皮书,本资源通过星球社群不定时更新,加入星球后,请联系市场同事获取相关知识星球社群信息。 一、总体介绍 主要介绍了AllData商业版产品的整体情况,包括产品定位、核心优势、灵活性和扩展性等,已有150个客户使用,社区发展良好。同时,详细解析了…...
uni-app 框架 调用蓝牙,获取 iBeacon 定位信标的数据,实现室内定位场景
背景:最近需要对接了一个 叫 iBeacon 定位信标 硬件设备,这个设备主要的作用是,在信号不好的地方,或者室内实现定位,准确的找到某个东西。就比如 地下停车场,商城里,我们想知道这个停车场的某个…...
leetcode-热题100(3)
leetcode-74-搜索二维矩阵 矩阵最后一列升序排序,在最后一列中查找第一个大于等于target的元素 然后在该元素所在行进行二分查找 bool searchMatrix(int** matrix, int matrixSize, int* matrixColSize, int target) {int n matrixSize;int m matrixColSize[0];in…...
汇编学习结语
一天之内挑战计划太乐观了, 不过还好,这次我总共用了三天完成了系列汇编指令的学习,有的指令也深入进行了验证,输出了系列文章,收获颇多。 接下来我将开启一个专栏,用于记录学习OllyDbg的使用。 OllyDbg使用…...
C++ I/O 流通俗指南
1. std::ostream 是什么? 定义:std::ostream 是 C 标准库中的输出流类,负责将数据输出到各种目标(如屏幕、文件、网络等)。你可以把 std::ostream 想象成一根“数据水管”: 数据从 C 代码流进 std::ostrea…...
基于python的电影数据分析及可视化系统
一、项目背景 随着电影行业的快速发展,电影数据日益丰富,如何有效地分析和可视化这些数据成为行业内的一个重要课题。本系统旨在利用Python编程语言,结合数据分析与可视化技术,为电影行业从业者、研究者及爱好者提供一个便捷的电…...
【NLP 面经 5】
难以承受的东西只会让我在下一次更平静的面对 —— 25.4.2 一、NER任务,CRF模型改进 命名实体识别(NER)任务中,你使用基于条件随机场(CRF)的模型,然而模型在识别嵌套实体和重叠实体时效果不佳&a…...
鸿蒙NEXT小游戏开发:猜小球
1. 引言 “猜小球”是一个经典的益智游戏,通常由一名表演者和多名参与者共同完成。表演者会将一个小球放在一个杯子下面,然后将三个杯子快速地交换位置,参与者则需要猜出最终哪个杯子下面有小球。本文将介绍如何使用HarmonyOS NEXT技术&…...
[NCTF2019]Fake XML cookbook [XXE注入]
题目源代码 function doLogin(){var username $("#username").val();var password $("#password").val();if(username "" || password ""){alert("Please enter the username and password!");return;}var data "…...
Android 防抖和节流
文章目录 Android 防抖和节流概述工具类使用源码下载 Android 防抖和节流 概述 防抖(Debounce): 防抖是指在事件被触发后,等待一段时间,如果在这段时间内没有再触发事件,才执行处理函数。如果在这段时间内…...
安徽京准:NTP时间同步服务器操作使用说明
安徽京准:NTP时间同步服务器操作使用说明 3.1 连接天线 天线连接到“ANT”口。 3.2 连接电源 将220V电源线连到AC220V座上或将电源适配器(7.5V~12V)接到DC口上。也可以同时接上,提高供电可靠性。 3.3 LAN网口 网线连接到NTP…...
【学习记录】pytorch载入模型的部分参数
需要从PointNet网络框架中提取encoder部分的参数,然后赋予自己的模型。因此,需要从一个已有的.pth文件读取部分参数,加载到自定义模型上面。做了一些尝试,记录如下。 关于模型保存与载入 torch.save(): 使用Python的pickle实用程…...
Ubuntu Wayland启动腾讯会议并实现原生屏幕共享
Intro 众所周知,长期以来,由于腾讯会议项目组的尸位素餐、极度不作为,在Wayland成为Ubuntu 24.04 LTS的默认窗口环境下,仍然选择摆烂,甚至还“贴心”地在启动脚本下增加检测Wayland退出的代码;并且即使使用…...
写Prompt的技巧和基本原则
一.基本原则 1.一定要描述清晰你需要大模型做的事情,不要模棱两可 2.告诉大模型需要它做什么,不需要做什么 改写前: 请帮我推荐一些电影 改写后: 请帮我推荐2025年新出的10部评分比较高的喜剧电影,不要问我个人喜好等其他问题ÿ…...
前端Material-UI面试题及参考答案
目录 Material-UI 的设计理念与 Material Design 规范的关系是什么? 如何通过 npm/yarn/pnpm 安装 Material-UI 的核心依赖? Material-UI 的默认主题系统如何实现全局样式管理? 如何在项目中配置自定义字体和颜色方案? 什么是 emotion 和 styled-components,它们在 Ma…...
29、web前端开发之CSS3(六)
13. 多列布局(Multi-column Layout) 多列布局(Multi-column Layout)是一种通过CSS实现的布局方式,允许将内容组织成多列,类似于报纸或杂志的排版方式。这种布局方法能够有效地利用页面空间,提升…...
Go 语言语法精讲:从 Java 开发者的视角全面掌握
《Go 语言语法精讲:从 Java 开发者的视角全面掌握》 一、引言1.1 为什么选择 Go?1.2 适合 Java 开发者的原因1.3 本文目标 二、Go 语言环境搭建2.1 安装 Go2.2 推荐 IDE2.3 第一个 Go 程序 三、Go 语言基础语法3.1 变量与常量3.1.1 声明变量3.1.2 常量定…...
MySQL 复制与主从架构(Master-Slave)
MySQL 复制与主从架构(Master-Slave) MySQL 复制与主从架构是数据库高可用和负载均衡的重要手段。通过复制数据到多个从服务器,既可以实现数据冗余备份,又能分担查询压力,提升系统整体性能与容错能力。本文将详细介绍…...
水下成像机理分析
一般情况下, 水下环境泛指浸入到人工水体 (如水库、人工湖等)或自然水体(如海洋、河流、湖 泊、含水层等)中的区域。在水下环境中所拍摄 的图像由于普遍受到光照、波长、水中悬浮颗粒物 等因素的影响,导致生成的水下图像出现模糊、退 化、偏色等现象,图像…...
腾讯云智测试开发面经
1、投递时间线 2.20投递简历,3.11第一轮面试,3.30第二轮面试,4.4第三轮面试,4.10第四轮面试,4.11offer意向书 2、第一轮面试 第一轮面试技术面,面试官是导师,面试时长40多分钟 1)自我介绍 2)数组和列表的区别 3)了解哪些数据库 4)进程和线程的区别 5)了解哪…...
JVM类加载器详解
文章目录 1.类与类加载器2.类加载器加载规则3.JVM 中内置的三个重要类加载器为什么 获取到 ClassLoader 为null就是 BootstrapClassLoader 加载的呢? 4.自定义类加载器什么时候需要自定义类加载器代码示例 5.双亲委派模式类与类加载器双亲委派模型双亲委派模型的执行…...
@ComponentScan注解详解:Spring组件扫描的核心机制
ComponentScan注解详解:Spring组件扫描的核心机制 一、ComponentScan注解概述 ComponentScan是Spring框架中的一个核心注解,用于自动扫描和注册指定包及其子包下的Spring组件。它是Spring实现依赖注入和自动装配的基础机制之一。 Retention(Retention…...
rust Send Sync 以及对象安全和对象不安全
开头:菜鸟小明的疑惑 小明: “李哥,我最近学 Rust,感觉它超级严谨,啥 Send、Sync、对象安全、静态分发、动态分发的,我都搞晕了!为啥 Rust 要设计得这么复杂啊?” 小李࿰…...
从一到无穷大 #44:AWS Glue: Data integration + Catalog
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。 本作品 (李兆龙 博文, 由 李兆龙 创作),由 李兆龙 确认,转载请注明版权。 文章目录 引言Glue的历史,设计原则与挑战Serverless ETL 功能设计Glue StudioGlue …...
【Redis】如何处理缓存穿透、击穿、雪崩
Redis 缓存穿透、击穿和雪崩是高并发场景下的典型问题,以下是详细解决方案和最佳实践: 一、缓存穿透(Cache Penetration) 问题:恶意请求不存在的数据(如不存在的ID),绕过缓存直接访…...
区块链技术如何重塑金融衍生品市场?
区块链技术如何重塑金融衍生品市场? 金融衍生品市场一直是全球金融体系的重要组成部分,其复杂性和风险性让许多投资者望而却步。然而,随着区块链技术的兴起,这一领域正在经历一场深刻的变革。区块链以其去中心化、透明和不可篡改…...
