Muduo网络库实现 [四] - Channel模块
设计思路
具体来说每一个套接字都会对应一个 Channel 对象,用于对它的事件进行管理。可以对于描述符的监控事件在用户态更容易维护,以及触发事件后的操作流程更加的清晰
Channel模块是用于对一个描述符所需要监控的事件以及事件触发之后要执行的回调函数进行管理的

一个连接触发的事件有哪些?
- 基本事件:EPOLLIN(可读)、EPOLLOUT(可写)。
- 异常事件:EPOLLHUP(挂起)、EPOLLERR(错误)。
- 特殊事件:EPOLLPRI(优先数据)、EPOLLRDHUP(半关闭)。
- 模式修饰:EPOLLET(边缘触发,需配合其他事件)。
对于EPOLLHUP(挂起)EPOLLPRI(优先数据)、EPOLLRDHUP(半关闭)。大家可能见得少
下面我来详细讲解下
EPOLLRDHUP:读关闭,接收缓冲区不会再有新数据到来了,内核不会再向接收缓冲区写数据,但是已经存在的数据还是可以被读取的
读关闭其实就是收到了对方的FIN请求,此时我们进入 CLOSE_WAIT 状态。 对方不会再发送数据给我们了,我们的做法应该是尽快将缓冲区中的数据读取并处理,然后将结果返回,不管这一轮处理之后是否还有不完整的报文,只要我们把发送缓冲区的数据发送完,我们就关闭连接。
EPOLLHUP:读写都关闭。意味着我们不会再收到新数据,同时无法再向内核缓冲区中写入数据或者说无法在将数据发送到对端。 这可能是因为网络等原因,多次重发报文不可达时引起的状态。此时可以理解为连接已经关闭,但是我们还是需要手动关闭文件描述符
当该事件触发时,我们的做法是关闭连接。因为就算还有数据待处理,我们的应答也无法发送到对端了,这时候连接已经无意义了,所以我们直接关闭。
EPOLLPRI:表示文件描述符上有优先或紧急数据(如 TCP 带外数据)可读,不影响普通数据流。
我们可以把 EPOLLRDHUP 看成是 读事件,触发之后调用读方法,但是需要尽快关闭连接。
而 EPOLLHUP 是挂断事件,他的做法其实就跟错误事件的做法类似,直接关闭连接。
一般情况下我们也无需太关心EPOLLRDHUP,就把它当读事件就行了,因为触发EPOLLRDHUP的时候,有两种情况,一种是没有触发读事件,那么说明没有新数据到来,那么缓冲区此时没有一个完整报文,我们只需要把输出缓冲区的数据发出去就可以直接关闭连接了。 另一种是读事件先触发,然后触发EPOLLRDHUP,这时候,由于读事件触发,我们会调用读事件的回调方法将缓冲区的所有完整报文都处理完,所以此时我们也是一样的,把输出缓冲区的数据全部发送出去就可以关闭连接了。
最后,我们还需要一个任意事件的回调方法。因为我们可能会启动超时连接的销毁机制,那么任意事件触发我们都需要刷新定时任务。同时,用户也可能会设置任意事件回调,那么任意事件到来时,我们也需要执行用户设置的任意回调(这一点我们在Channel模块不考虑,这是Connection模块该考虑的,Channel的回调方法全部都是Connection设置进来的,同时被Connection管理)。
在 epoll 模型中,我们对一个描述符的事件管理是使用一个 uint32_t events 来管理的,后续事件就绪时,我们也是通过一个 uint32_t revents 来获取出来的,所以我们的Channel模块中除了五个回调函数,还需要两个 uint32_t的变量。 一个是监控的事件 ,一个是就绪的事件
我们需要设置的功能性接口:
- 判断读/写事件是否已经监控
- 开启读/写事件监控
- 关闭读/写事件监控
- 关闭所有事件监控
- 移除监控
- 以及设置五个回调方法的接口
这个模块就好比是邮局里面有:快递盒子(channel),物品(fd),信息单(各种事件)。用户沟通(回调函数)
包括的步骤:
检查快递盒贴上物品信息了吗?(是否启动了读事件/写事件...)
查看信息单知道某个快递何时派发,需要怎么发送(启动读/写等事件监控)
快递已经被取走(关闭事件监控)
用户取消了某个快递派发(移除监控)
用户说发了这个快递请给我回个电话(设置事件回调)
注意:关闭所有事件监控是指在 epoll 模型中将所有的事件监控取消,不再监控。但是我们的文件描述符还仍在epoll模型的红黑树节点中!! 而移除监控则是要在epoll模型中移除我们的节点,在移除节点指点,所以要先取消所有事件监控,然后进行移除监控
还有需要注意的是,其实移除所有事件监控并不是真的就不监控任何事件了,我们的文件描述符在epoll中,默认是自动开启 EPOLLERR ,ERPOLLHUP 这两个事件的监控的,但是EPOLLRDHUP需要我们自己进行监控。
接下来还需要一个接口用于执行就绪事件,当我们要监控的事件就绪了,肯定要让去做别的事情。也就是按照一定的逻辑来调用回调方法。
同时,未来我们获取到就绪的事件是在EventLoop 模块中获取,那么我们也需要一个接口用于设置就绪事件。
而我们也需要一个私有接口,因为我们的Channel模块只是对事件进行管理,真正进行监控的还是Poller模块以及EventLoop模块,我们未来会通过调用EventLoop模块的接口来对EventLoop模块中的Poller模块内的epoll模型中监控的事件做调整。
我们所谓的启动和取消事件监控,其实就是对 epoll 模型中的文件描述符所监控的事件作更新,所以对事件的修改只需要一个接口就行了,因为未来调用的都是 epoll_ctl 这个接口。
类的设计
class Channel
{
private:using EventCallBack = std::function<void()>; // 回调函数别名int fd; // 文件描述符uint32_t _event; // 监控的事件uint32_t _revents; // 就绪的事件EventCallBack _read_cb; // 读事件回调EventCallBack _write_cb; // 写事件回调EventCallBack _error_cb; // 错误事件回调EventCallBack _close_cb; // 挂断事件回调EventCallBack _event_cb; // 任意事件回调
private:void UpdateEvents();public:bool HasRead(); // 判断是否启动了读事件bool HasWrite(); // 判断是否启动了写事件void EnableRead(); // 启动读事件监控void EnableWrite(); // 启动写事件监控void DisableRead(); // 取消读事件监控void DisableWrite(); // 取消写事件监控void DisableAll(); // 取消所有事件监控void SetRevents(); // 设置就绪事件void HandlerEvents(); // 处理就绪事件void SetReadCallBack(); // 设置读事件回调void SetWriteCallBack(); // 设置写事件回调void SetErrorCallBack(); // 设置错误事件回调void SetClosedCallBack(); // 设置挂断事件回调void SetEventCallBack(); // 设置任意事件回调void Remove(); // 移除监控
};
当然,后续我们实现了EventLoop模块之后,我们会在Channel模块中再添加一个成员,就是 EventLoop* loop ,因为我们对事件的实际操作是需要通过 EventLoop 的 接口来完成的。
模块实现
私有接口
//私有接口,用于真正和 Poller 模块和 EventLoop 模块联动,进行事件监控的调整void UpdateEvents() //op 就是未来传递给 epoll_ctl 的op参数{//后续调用EventLoop提供的接口//_loop->UpdateEvents(this);}
简单接口
public:bool HasRead() // 判断是否启动了读事件{return _events & EPOLLIN;}bool HasWrite() // 判断是否启动了写事件{return _events & EPOLLOUT;}void EnableRead() // 启动读事件监控{_events = _events | EPOLLIN;}void EnableWrite() // 启动写事件监控{_events = _events | EPOLLOUT;}void DisableRead() // 关闭读事件监控{_events = _events & (~EPOLLIN);}void DisableWrite() // 关闭写事件监控{_events = _events & (~EPOLLOUT);}void DisableAll() // 关闭所有事件监控{_events = 0;//调用EventLoop里面封装的poller模块函数}void SetRevents(uint32_t revents) // 设置就绪事件{_revents = revents;}void Remove(){//调用EventLoop里面封装的poller模块函数} // 移除监控
};
复杂接口
稍微复杂一点的接口就是处理就绪事件的接口。在处理就绪事件的时候,我们需要判断哪些事件就绪了。 同时,我们要注意一个点
读事件中间出错,我们是不会直接关闭连接的,因为可能对端不让读了,但是我们还能写入
例如一个服务器收到客户端的请求数据(触发 EPOLLIN),但读取时出错(例如客户端突然断开)。服务器可能还有响应数据(如 HTTP 响应)未发送,不能直接关闭连接,而是应尝试发送完数据。
但是如果写事件出错,那么我们是直接关闭连接了,因为写失败通常意味着连接已不可用(例如对端已关闭或网络中断),继续尝试发送数据没有意义,因为数据无法到达对端。
服务器尝试向客户端发送响应数据(触发 EPOLLOUT),但写操作失败(例如客户端已断开)。此时继续读取或处理数据无意义,直接关闭连接是合理的选择。
而如果挂断事件和错误事件,我们也是关闭连接。
而如果发生了挂断事件或者错误事件,那么一定会在写事件中体现出来,但是关闭连接的操作我们只需要进行一次,所以我们在处理事件的时候,写事件,挂断事件和错误事件只需要处理一种就行了,如果连接有问题,那么在任意一个事件的处理中就会关闭连接。
void HandlerEvents() // 处理就绪事件{if((_revents & EPOLLIN) | (_revents & EPOLLHUP) | (_revents & EPOLLPRI)){if(_read_callback)_read_callback();if(_event_callback)_event_callback();} if((_revents & EPOLLOUT)){if(_write_callback)_write_callback();if(_event_callback)_event_callback();} else if((_revents & EPOLLERR)){if(_error_callback)_error_callback();} else if((_revents & EPOLLHUP)){if(_close_callback)_close_callback();} }
回调函数接口
void SetReadCallBack(const EventCallBack &cb) // 设置读事件回调{_read_callback = cb;}void SetWriteCallBack(const EventCallBack &cb) // 设置写事件回调{_write_callback = cb;}void SetErrorCallBack(const EventCallBack &cb) // 设置错误事件回调{_error_callback = cb;}void SetClosedCallBack(const EventCallBack &cb) // 设置挂断事件回调{_close_callback = cb;}void SetEventCallBack(const EventCallBack &cb) // 设置任意事件回调{_event_callback = cb;}
疑惑点
using EventCallBack = std::function<void()>;前面的using是干嘛的?
using EventCallBack = std::function<void()>;
不是已经取消事件了嘛?为什么还要移除事件呢?
_events & EPOLLIN这个是咋计算的
都有才为真
_events |= EPOLLOUT怎么计算的?
有就为真
相关文章:
Muduo网络库实现 [四] - Channel模块
设计思路 具体来说每一个套接字都会对应一个 Channel 对象,用于对它的事件进行管理。可以对于描述符的监控事件在用户态更容易维护,以及触发事件后的操作流程更加的清晰 Channel模块是用于对一个描述符所需要监控的事件以及事件触发之后要执行的回调函…...
“自动驾驶背后的数学” 专栏导读
专栏链接: 自动驾驶背后的数学 专栏以“自动驾驶背后的数学”为主题,从基础到深入,再到实际应用和未来展望,全面解析自动驾驶技术中的数学原理。开篇用基础数学工具搭建自动驾驶的整体框架,吸引儿童培养兴趣࿰…...
XSS 攻击(详细)
目录 引言 一、XSS 攻击简介 二、XSS 攻击类型 1.反射型 XSS 2.存储型 XSS 3.基于 DOM 的 XSS 4.Self - XSS 三、XSS 攻击技巧 1.基本变形 2.事件处理程序 3.JS 伪协议 4.编码绕过 5.绕过长度限制 6.使用标签 四、XSS 攻击工具与平台 1.XSS 攻击平台 2.BEEF 五…...
K8s负载均衡全解析:从入门到实战的完整指南
Kubernetes(K8s)作为容器编排的标准,其负载均衡机制是构建高可用、高弹性应用的关键。本文将全面介绍K8s负载均衡的核心概念、实现方式及最佳实践,帮助开发者和运维人员构建稳定高效的云原生应用。 一、K8s负载均衡的基础概念 在Kubernetes生态系统中,负载均衡是指将工作负…...
《ZooKeeper Zab协议深度剖析:构建高可用分布式系统的基石》
《ZooKeeper Zab协议深度剖析:构建高可用分布式系统的基石》 一、分布式协调的挑战与ZooKeeper的解决方案 1.1 分布式系统一致性难题 #mermaid-svg-iigak7YlgEw7o6lq {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-sv…...
OpenCV 图形API(6)将一个矩阵(或图像)与一个标量值相加的函数addC()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 addC 函数将给定的标量值加到给定矩阵的每个元素上。该功能可以用矩阵表达式替换: dst src1 c \texttt{dst} \texttt{src1} \te…...
同步SVPWM调制策略的初步学习记录
最近项目需要用到一些同步调制SVPWM相关的内容(现在的我基本都是项目驱动了),因此对该内容进行一定的学习。 1 同步SVPWM调制的背景 我们熟知的一些知识是:SVPWM(空间矢量脉宽调制)是一种用于逆变器的调制…...
六十天Linux从0到项目搭建(第十五天)(程序替换、exec流程示意图、核心特性)
1 为什么要有程序替换? 程序替换(Process Replacement)是操作系统中一个关键机制,它的核心目的是:让一个正在运行的进程(通常是子进程)停止执行当前代码,转而加载并执行一个全新的程…...
排序算法3-交换排序
目录 1.常见排序算法 2.排序算法的预定函数 2.1交换函数 2.2测试算法运行时间的函数 2.3已经实现过的排序算法 3.交换排序的实现 3.1冒泡排序 3.2快速排序 3.2.1递归的快速排序 3.2.1.1hoare版本的排序 3.2.1.2挖坑法 3.2.1.3lomuto前后指针法 3.2.2非递归版本的快…...
【Qt】数据库管理
数据库查询工具开发学习笔记 一、项目背景与目标 背景:频繁编写数据库查询语句,希望通过工具简化操作,提升效率。 二、总体设计思路 1. 架构设计 MVC模式:通过Qt控件实现视图(UI),业务逻辑…...
Ant Design Vue 中的table表格高度塌陷,造成行与行不齐的问题
前言: Ant Design Vue: 1.7.2 Vue2 less 问题描述: 在通过下拉框选择之后,在获取接口数据,第一列使用了fixed:left,就碰到了高度塌陷,查看元素的样式结果高度不一致,如&#x…...
面经-项目
项目 项目(重点)问题1:描述在网页中题目点击提交后到题目结果出现的一系列后台反应【1】如何获取到用户提交的代码的?【2】_1. 题目细节都有哪些?【2】_2. 题目信息怎么存储的?【3】负载均衡算法的实现?【4】oj_server怎么连接对应的compile_server(编译主机)的?【5】oj_…...
Win10安装Linux的三种方法
通过 Windows 子系统 for Linux(WSL)安装 启用 “适用于 Linux 的 Windows 子系统” 可选功能: 图形界面方式:在【设置 -> 更新与安全 -> 开发者选项】中开启【开发人员模式】;在【程序和功能 -> 启用或关闭…...
【qt】文件类(QFile)
很高兴你能看到这篇文章,同时我的语雀文档也更新了许多嵌入式系列的学习笔记希望能帮到你 : https://www.yuque.com/alive-m4b9n 目录 QFile 主要功能QFile 操作步骤QFile 其他常用函数案例分析及实现功能一实现:打开文件并显示功能二实现:另…...
力扣hot100——最长连续序列(哈希unordered_set)
题目链接:最长连续序列 1、错解:数组做哈希表(内存超出限制) int longestConsecutive(vector<int>& nums) {vector<bool> hash(20000000010, false);for(int i0; i<nums.size();i){hash[1000000000nums[i]]t…...
3. 实战(一):Spring AI Trae ,助力开发微信小程序
1、前言 前面介绍了Spring boot快速集成Spring AI实现简单的Chat聊天模式。今天立马来实战一番,通过Trae这个火爆全网的工具,来写一个微信小程序。照理说,我们只是极少量的编码应该就可以完成这项工作。开撸~ 2、需求描述 微信小程序实现一…...
MySQL高级语句深度解析与应用实践
一、窗口函数:数据分析的利器 1. 窗口函数基础概念 窗口函数(Window Function)是MySQL 8.0引入的强大特性,它可以在不减少行数的情况下对数据进行聚合计算和分析 SELECT employee_name,department,salary,RANK() OVER (PARTITION BY department ORDER…...
SSE服务器主动推送至浏览器客户端,让你不再需要websocket
Server-Sent Events(SSE)是一种服务器向客户端推送实时更新的技术,基于HTTP协议。客户端通过EventSource API来接收事件流,而服务器则保持一个长连接,持续发送数据。这与传统的请求-响应模式不同,允许服务器…...
UE5新材质系统效果Demo展示
1、玉质材质,透明玻璃材质,不同透射和散射。 2、浅水地面,地面层,水层,地面湿度,水面高度,水下扰动,水下浇洒,水下折射 Substrate-Water Substrate-Water-CodeV2...
wps 怎么显示隐藏文字
wps 怎么显示隐藏文字 》文件》选项》视图》勾选“隐藏文字” wps怎么设置隐藏文字 wps怎么设置隐藏文字...
解决 macOS (M1 Pro) 上使用 Vite 进行 Build 打包时 Node 进程内存溢出的问题
解决 macOS (M1 Pro) 上使用 Vite 进行 Build 打包时 Node 进程内存溢出的问题 在搭载 M1 Pro 芯片的 macOS 系统上,使用 Vite 进行项目构建(build)时,您可能会遇到 Node 进程内存溢出的错误。特别是在使用较新版本的 Node.js&am…...
页面重构过程中如何保证良好的跨浏览器一致性?
在页面重构的过程中,为了确保网页能够在不同的浏览器中呈现一致的效果,我们需要采取一系列措施来提高跨浏览器的一致性。以下是几个关键步骤和技术要点: 使用标准化的HTML和CSS:始终遵循最新的Web标准编写代码,例如采用…...
CXL UIO Direct P2P学习
前言: 在CXL协议中,UIO(Unordered Input/Output) 是一种支持设备间直接通信(Peer-to-Peer, P2P)的机制,旨在绕过主机CPU或内存的干预,降低延迟并提升效率。以下是UIO的核心概念及UI…...
leetcode138.随即链表的复制
思路源于 【力扣hot100】【LeetCode 138】随机链表的复制|哈希表 采用一个哈希表,键值对为<原链表的结点,新链表的结点>,第一次遍历原链表结点时只创建新链表的结点,第二次遍历原链表结点时,通过键拿…...
03_MySQL工具介绍
文章目录 一、Navicat for MySQL1.1、安装 二、SQLyog2.1、安装 多数时候使用SQL语句对数据库进行操作不是很方便,特别是在查询操作时,显示的内容不够直观,此时我们需要借助图形化工具对数据库进行操作。 操作MySQL常用的图形工具如下&#x…...
《网络管理》实践环节01:OpenEuler22.03sp4安装zabbix6.2
兰生幽谷,不为莫服而不芳; 君子行义,不为莫知而止休。 1 环境 openEuler 22.03 LTSsp4PHP 8.0Apache 2Mysql 8.0zabbix6.2.4 表1-1 Zabbix网络规划(用你们自己的特征网段规划) 主机名 IP 功能 备注 zbx6svr 19…...
Qt Creator 中文 “error: C2001: 常量中有换行符“ 问题解决方法
Qt Creator 编译时出现中文 error: C2001: 常量中有换行符的问题,通常由文件编码与编译器字符集不兼容导致。 一、修改文件编码格式 添加 UTF-8 BOM 签名 在 Qt Creator 中设置:工具 -> 选项 -> 文本编辑器 -> 行为 -> UTF-8 BOM&a…...
Charles 抓包配置保姆教程(PC、IOS、Android)
抓包工具基础配置与使用指南 大家好,我是十一!今天给大家分享一篇关于抓包工具的基础配置与使用指南。无论是开发、测试还是安全分析,抓包工具都是不可或缺的利器。本文将详细介绍如何配置和使用抓包工具,并特别推荐一款功能强大…...
洛谷题单1-P1001 A+B Problem-python-流程图重构
题目描述 输入两个整数 a,b,输出它们的和(∣a∣,∣b∣≤109)。 输入格式 两个以空格分开的整数。 输出格式 一个整数。 输入输出样例 输入 20 30输出 50方式-print class Solution:staticmethoddef oi_input():"""从…...
el-table 动态给每行增加class属性
el-table 动态给每行增加class属性 html代码 row-class-name属性,绑定方法 :row-class-name“tableRowClassName”, <el-table :data"tableData" border :row-class-name"tableRowClassName"> </el-table>js代码 tableRowClassNam…...
