Muduo网络库实现 [四] - Channel模块

news2025/4/8 11:32:51

设计思路

具体来说每一个套接字都会对应一个 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怎么计算的?

有就为真 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2325680.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

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 五…

《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()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 addC 函数将给定的标量值加到给定矩阵的每个元素上。该功能可以用矩阵表达式替换&#xff1a; dst src1 c \texttt{dst} \texttt{src1} \te…

同步SVPWM调制策略的初步学习记录

最近项目需要用到一些同步调制SVPWM相关的内容&#xff08;现在的我基本都是项目驱动了&#xff09;&#xff0c;因此对该内容进行一定的学习。 1 同步SVPWM调制的背景 我们熟知的一些知识是&#xff1a;SVPWM&#xff08;空间矢量脉宽调制&#xff09;是一种用于逆变器的调制…

排序算法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】数据库管理

数据库查询工具开发学习笔记 一、项目背景与目标 背景&#xff1a;频繁编写数据库查询语句&#xff0c;希望通过工具简化操作&#xff0c;提升效率。 二、总体设计思路 1. 架构设计 MVC模式&#xff1a;通过Qt控件实现视图&#xff08;UI&#xff09;&#xff0c;业务逻辑…

Ant Design Vue 中的table表格高度塌陷,造成行与行不齐的问题

前言&#xff1a; Ant Design Vue: 1.7.2 Vue2 less 问题描述&#xff1a; 在通过下拉框选择之后&#xff0c;在获取接口数据&#xff0c;第一列使用了fixed:left&#xff0c;就碰到了高度塌陷&#xff0c;查看元素的样式结果高度不一致&#xff0c;如&#x…

【qt】文件类(QFile)

很高兴你能看到这篇文章&#xff0c;同时我的语雀文档也更新了许多嵌入式系列的学习笔记希望能帮到你 &#xff1a; https://www.yuque.com/alive-m4b9n 目录 QFile 主要功能QFile 操作步骤QFile 其他常用函数案例分析及实现功能一实现&#xff1a;打开文件并显示功能二实现:另…

3. 实战(一):Spring AI Trae ,助力开发微信小程序

1、前言 前面介绍了Spring boot快速集成Spring AI实现简单的Chat聊天模式。今天立马来实战一番&#xff0c;通过Trae这个火爆全网的工具&#xff0c;来写一个微信小程序。照理说&#xff0c;我们只是极少量的编码应该就可以完成这项工作。开撸~ 2、需求描述 微信小程序实现一…

UE5新材质系统效果Demo展示

1、玉质材质&#xff0c;透明玻璃材质&#xff0c;不同透射和散射。 2、浅水地面&#xff0c;地面层&#xff0c;水层&#xff0c;地面湿度&#xff0c;水面高度&#xff0c;水下扰动&#xff0c;水下浇洒&#xff0c;水下折射 Substrate-Water Substrate-Water-CodeV2

wps 怎么显示隐藏文字

wps 怎么显示隐藏文字 》文件》选项》视图》勾选“隐藏文字” wps怎么设置隐藏文字 wps怎么设置隐藏文字

CXL UIO Direct P2P学习

前言&#xff1a; 在CXL协议中&#xff0c;UIO&#xff08;Unordered Input/Output&#xff09; 是一种支持设备间直接通信&#xff08;Peer-to-Peer, P2P&#xff09;的机制&#xff0c;旨在绕过主机CPU或内存的干预&#xff0c;降低延迟并提升效率。以下是UIO的核心概念及UI…

leetcode138.随即链表的复制

思路源于 【力扣hot100】【LeetCode 138】随机链表的复制&#xff5c;哈希表 采用一个哈希表&#xff0c;键值对为<原链表的结点&#xff0c;新链表的结点>&#xff0c;第一次遍历原链表结点时只创建新链表的结点&#xff0c;第二次遍历原链表结点时&#xff0c;通过键拿…

《网络管理》实践环节01:OpenEuler22.03sp4安装zabbix6.2

兰生幽谷&#xff0c;不为莫服而不芳&#xff1b; 君子行义&#xff0c;不为莫知而止休。 1 环境 openEuler 22.03 LTSsp4PHP 8.0Apache 2Mysql 8.0zabbix6.2.4 表1-1 Zabbix网络规划&#xff08;用你们自己的特征网段规划&#xff09; 主机名 IP 功能 备注 zbx6svr 19…

Opencv计算机视觉编程攻略-第四节 图直方图统计像素

Opencv计算机视觉编程攻略-第四节 图直方图统计像素 1.计算图像直方图2.基于查找表修改图像3.直方图均衡化4.直方图反向投影进行内容查找5.用均值平移法查找目标6.比较直方图搜索相似图像7.用积分图统计图像 1.计算图像直方图 图像统计直方图的概念 图像统计直方图是一种用于描…

深度学习处理时间序列(5)

Keras中的循环层 上面的NumPy简单实现对应一个实际的Keras层—SimpleRNN层。不过&#xff0c;二者有一点小区别&#xff1a;SimpleRNN层能够像其他Keras层一样处理序列批量&#xff0c;而不是像NumPy示例中的那样只能处理单个序列。也就是说&#xff0c;它接收形状为(batch_si…

Mysql 索引性能分析

1.查看CRUD次数 show global status like Com_______&#xff08;7个下划线&#xff09; show global status like Com_______ 2.慢SQL分析 SET GLOBAL slow_query_log ON;-- 设置慢SQL日志记录开启 SET GLOBAL long_query_time 2; -- 设置执行超过 2 秒的查询为慢查询 开…

win11+ubuntu双系统安装

操作步骤&#xff1a; 官网下载ubuntu 最新镜像文件 准备U盘 准备一个容量不小于 8GB 的 U 盘&#xff0c;用于制作系统安装盘。制作过程会格式化 U 盘&#xff0c;请注意提前备份数据。 制作U盘启动盘 使用rufus工具&#xff0c;或者 balenaEtcher工具&#xff08;官网安…

linux-5.10.110内核源码分析 - 写磁盘(从VFS系统调用到I/O调度及AHCI写磁盘)

1、VFS写文件到page缓存(vfs_write) 1.1、写裸盘(dd) 使用如下命令写裸盘&#xff1a; dd if/dev/zero of/dev/sda bs4096 count1 seek1 1.2、系统调用(vfs_write) 系统调用栈如下&#xff1a; 对于调用栈的new_sync_write函数&#xff0c;buf为写磁盘的内容的内存地址&…

arinc818 fpga单色图像传输ip

arinc818协议支持的常用线速率如下图 随着图像分辨率的提高&#xff0c;单lane的速率无法满足特定需求&#xff0c;一种方式是通过多个LANE交叉的去传输图像&#xff0c;另外一种是通过降低图像的带宽&#xff0c;即通过只传单色图像达到对应的效果 程序架构如下图所示&#x…