基于多反应堆的高并发服务器【C/C++/Reactor】(中)Dispatcher模块的实现思路和定义

(四)Dispatcher模块的实现思路

关于dispatcher,它应该是反应堆模型里边的核心组成部分,因为如果说这个反应堆模型里边有事件需要处理,或者说有事件需要检测,那么是需要通过这个poll、epoll 或者 select来完成的。dispatcher有三个组成部分,它们并不是互相依存的,而是互斥的。就是我们在选择的时候,只能任选其一。不管使用哪一个,都可以往这个模型里边添加一个新的待检测事件,或者说把一个已经检测的事件从这个检测模型里边删掉还有一种情况,就是把一个已经被检测得到文件描述符它的事件进行修改,比如原来是读事件,现在改成读写也就是说这三种处理方式,每一种处理方式它们都对应一套处理函数,它们都对应一套处理函数。需要解决的问题:如果我们在程序中使用后,在调用这些接口的时候,是不是需要做一个判断?就是在程序中判断

if(使用的模型是poll){
    调用处理方式
}

else if(使用的模型是epoll){
    调用处理方式
}

else if(使用的模型是select){
    调用处理方式
}

因为这三种处理方式对应的是一套函数,所以在调用添加函数的时候需要做这样的一个的判断;在做删除的时候也需要做这样的一个判断,在做修改操作的时候,也需要做这样的判断。也就意味着咱们编写的程序是非常的冗余。

if() {
    ...
}
else if() {
    ...
}
else if() {
    ...
}

怎么去精简呢?有没有一种解决方案可以让代码写起来非常精简呢?

  • 对应的解决方案就是使用回调函数

Dispatcher提供了一系列的接口:

  1. init():做数据初始化
  2. add():添加一个事件节点
  3. remove():删除一个事件节点
  4. modify():修改一个事件节点

dispatch():用于事件检测的,对于poll来说,就是调用poll函数,对于epoll来说,就是调用epoll_wait函数,对于select来说,就是调用select函数。通过调用dispatch函数就能够知道检测的这一系列的文件描述符集合里边到底是哪一个文件描述符它所对应的事件被触发了,找到了这个被触发事件的文件描述符,就需要基于它的事件去调用文件描述符注册好的读函数或者是写函数了。

clear():内存释放。第一部分:对文件描述符的关闭,第二部分:对申请的堆内存的释放。可以把Dispatcher设计成是一个结构体,里边有六个成员,类型都是函数指针。函数指针指向的是函数的地址,它指向了这个函数的地址之后,就可以对地址对应的函数进行调用了。首先保存一个函数的地址,然后在适当的时机去调用这个地址对应的函数。因为函数名就是地址。

  • 假设说我们把这个函数指针已经做了初始化,什么时候进行调用呢?比如说客户端和服务器新建立了连接,那么就得到了一个用于通信的文件描述符。得到了通信的文件描述符,就需要调用add方法。这个add方法它是一个函数指针,它肯定指向一个对应的处理函数,那么这个任务函数动作是什么我就执行对应的那个动作。
  • 假设说某一个通信的文件描述符客户端断开了连接,那么就需要把这个文件描述符从检测的模型上删除(poll、epoll、select),remove也是一个函数指针,指向一个实际的函数,只要能够找到这个函数,就可以调用这个函数,把对应的文件描述符从检测的模型上删除。

  • 关于poll,也是一样的,分别是pollInit,pollAdd,pollDelete,pollModify,pollDispatch,pollClear这些函数它们还是函数指针吗?就不是了吧,这是实实在在的函数,但是这个函数的函数原型也就是它的返回值以及参数。需要和上边dispatch这个模型,里边定义的函数,指针的类型是相同的,这样的话,才能够让这个指针指向这个函数的地址。也就说,下边这一系列函数主要是给谁呢?给上边的这个dispatch结构体里边的函数指针进行实例化的,就是做初始化的。
  • 关于epoll,也是一样的,分别是epollInit,epollAdd,epollDelete,epollModify,epollDispatch,epollClear
  • select呢,也一样的,只不过是前缀不一样

当把下边的这三个模型里边的函数分别实现了之后,就看用户的选择了。

  1. 如果用户选择epoll,那么我们就使用epoll的这组函数去给上面的函数指针进行初始化。
  2. 如果用户选择select,那么就用这组函数的地址去给这个函数值呢?进行初始化、
  3. 如果用户选择poll,那么就用这组函数的函数名或者是函数地址

其实都是一样的。给上面的函数指针做初始化。初始化好了之后,在上层调用的时候,只需要使用dispatch这个结构体里边的这些函数指针的名字,就可以对下边这些已经实现了的函数进行调用了。处理思路说明白之后,再来看一个细节。对于poll这个模型来说,如果他要处理一系列的文件描述符, 前提条件是需要先把它们存储起来,要存储到一个结构体里边。在调用poll函数的时候,需要用到一个结构体类型

#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

struct pollfd {
    int fd; /* file descriptor */
    short events; /* requested events */
    short revents; /* returned events */
};

fdsstruct pollfd类型,这个参数是一个传入传出参数。在调用这个函数之前,需要先把结构体定义出来,然后对结构体进行初始化,告诉他我要检测的文件描述符的值是什么,以及要检测这个文件描述符的什么事件。当我们通过poll函数委托内核去检测这一系列的文件描述符集合的时候,内核检测到了某些文件,描述符对应的这个事件被触发了。那么,它就会把这个事件写入到revents里边。

那么为什么有一个events了,还有一个revents呢?是这个样子的,比如说这个events,它里边委托内核要检测文件描述符的读写事件

  • 现在只有读事件触发了,所以在revents里边,就只有读事件。
  • 如果对应的写事件触发了,那么这里边就只有写事件。
  • 如果读写事件都触发了,那么在这个revents里边,就是读写。

所以通过这个结构体的revents成员就能够非常清晰的知道这个文件描述符它的什么事件被触发了。知道什么事件被触发了,就可以做对应的动作处理了。

#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

epoll里边调用了epoll_wait就能够委托内核帮助我们去检测一系列的文件描述的集合,它所对应的事件是不是触发了?如果这些事件被触发了,那么他就会给我们返回数据,这个数据是保存到了第二个参数里边,第二个参数是一个epoll_event类型的结构体数组的地址。这个返回值是告诉我们epoll树上有多少个待检测的文件描述符,它对应的事件被激活了。

#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

看一下在调用select这个函数的时候用到的那些数据成员。在调用select函数的时候,有三个存储文件描述符集合的参数分别是readfdswritefds以及exceptfds第三个是异常的集合,关于异常的集合,可以不去检测。我们主要关心的是它的读集合和写集合类型,是fd_set,其实它也是传入传出参数。我们在传入的时候需要往fd_set里边设置一些合适的值告诉select,你需要委托内核帮助我们去检测哪些文件描述符的什么事件

  • 如果把这些文件描述符设置给了readfds,就是检测它的读事件
  • 如果把这些文件描述符设置给了writefds,那么就是检测这些文件描述符的写事件

关于这个fd_set,可以把它看成是一个整形的数组,它里边一共有1024个标志位。这个fd_set这种类型,它里边一共有1024个标志位。这1024个标志位,就对应select能够检测的那1024个文件描述符。

一个Dispatcher模型,它对应一个DispatcherData,它们都同时存在于另一个模块里边(EventLoop),是一个对应关系。我们如果想把这个Dispatcher对应的data取出来,那么就需要通过EventLoop来取了,所以要得到EventLoop的地址之后,也就能拿到这个Dispatcher对应的DispatcherData了。

(1)init函数

在我们要实现的这个多反应堆服务器模型里边,Dispatcher一共有多少个?是一个还是多个呢?来看一下在这个EventLoop里边, 其实就有Dispatcher,这个Dispatcher就是事件分发器,这个事件分发器其实就是要编写的那个poll、 epoll 或者select模块,我们在实现Dispatcher它底层的这三个模型里边,任意一个的时候都需要一个DispatcherData

现在再来思考,刚才提问的那个问题,在这个多反应堆模型里边需要多少个Dispatcher呢?一个还是n个呢?其实是n个吧,在这个项目里边有多少个反应堆模型,它就有多少个EventLoop,那么底层就有多少个Dispatcher。一个Dispatcher,它对应的有三块,一块是epoll ,一块是poll,一块是select。虽然有三块,前面也说了这三块并不是同时发挥作用,而是三选一。这个Dispatcher有多少个,那么这个DispatcherData就有多少个。所以,需要给底层的这个IO多路转接模型提供对应的数据块,有多少个多路lO转接模型,就需要提供多少个DispatcherData。

举一个例子,比如在我们项目中有三个EventLoop,那么就有三个epoll、三个poll、三个select。那么对应的DispatcherData有多少个呢?三三得九,是九个。但是对于每一组来说,我们只能从里边选择一个来使用,那么另外两个就用不到了。既然用不到,那么我们需要对它的DispatcherData进行初始化吗?也就不需要了吧,也就是说,虽然有九个,但是

  • 如果你选择了用这个epoll,那么我就给这个epolldata,做初始化;
  • 如果你选择了用poll,那么我就给这个polldata,做初始化;
  • 如果你选择了用select,那么我就给这个select对应的data做初始化

这是一个EventLoop。剩下的两个EventLoop也是做同样的选择。

所以,在这个项目中有三个EventLoop,那么实际被初始化的DispatcherData有多少个呢?三个,现在就能搞清楚在Dispatcher这个结构体里边对应的这个回调函数Init()它是用来干什么的?就是用来初始化epoll或者是select或者是poll对应的那个数据块。要通过这个函数去初始化一个数据块,最后要把这个数据块的内存地址给到函数的调用者。所以它的返回值肯定是一个指针,另外poll、 epoll 和select他们需要的数据块对应的内存类型一样吗?不一样,如果想要一种类型来兼容三种不同的类型,怎么做到呢?在C语言里就是使用泛型,故返回值类型为void*

void* (*init)();

 (2)add函数

  • EventLoop.h
#pragma once
#include "Dispatcher.h"
struct EventLoop{
    Dispatcher* dispatcher;
    void* dispatcherData;
};

add函数,这个add函数要把待检测的文件描述符添加到poll 、epoll 或者select上边。我们在添加一个待检测节点的时候,这个节点对应的肯定是一个文件描述符。在前面的文章中,已经介绍了把文件描述符封装成Channel类型。所以这个函数指针对应的参数肯定有一个是Channel类型。另外还有一个细节,就是我们通过add函数Channel里边的文件描述添加到IO检测模型上去的时候,都需要什么呢?

  • 如果是epoll,就需要epoll树的根结点。不管是什么类型的结点,都需要把它放到用于检测的这个epoll树上。关于这个根结点,肯定是需要保存的,可以在初始化的时候把epoll树的根结点和epoll_event结构体一起保存起来,也就是把这两部分数据做一个包装封装成一个结构体
  • 如果是poll,就需要pollfd对应的那个结构体
  • 如果是select,就需要它的读集合写集合

add函数还有一个EventLoop类型的evLoop参数,通过这个结构体,我们就能够取出当前的dispatcher它在工作的时候需要用到的那一系列的数据。前面说到,select用到的是文件描述符的集合(fd_set),epoll就是epoll_eventpoll就是pollfd类型的结构体

// 添加
int (*add)(struct Channel* channel,struct EventLoop* evLoop);

(3)remove函数

  • 如果要删除,用到的也是Channel类型和EventLoop类型的参数
// 删除
int (*remove)(struct Channel* channel,struct EventLoop* evLoop);

(4)modify函数

  • 如果要修改,用到的也是Channel类型和EventLoop类型的参数
// 修改
int (*modify)(struct Channel* channel,struct EventLoop* evLoop);

(5)dispatch函数

// 事件检测
int (*dispatch)(struct EventLoop* evLoop,int timeout); // 单位:s

这是一个函数指针声明。让我们分解这个声明以更好地理解它:

  • dispatch 是函数指针的名字
  • int 是函数的返回类型,表示该函数返回一个整数值
  • (*dispatch) 表示 dispatch 是一个指向函数的指针
  • struct EventLoop* evLoop是函数的第一个参数,它是一个指向 EventLoop 结构体的指针 
  • int timeout 是函数的第二个参数,它是一个整数

(6)clear函数

// 清除数据(关闭fd或者释放内存)
int (*clear)();
  • 综上所述,这个函数指针 dispatch 指向的函数接受一个指向 EventLoop 的指针和一个整数作为参数,并返回一个整数

Dispatcher结构体定义与初始化

在先前的介绍中,我们提到了dispatcher结构体的定义。这个结构体中包含六个成员,它们主要是通过函数指针来进行初始化的。这些函数指针对应于epollselect等使用的数据。

  • 对于select,需要使用fd_set类型的两个文件描述符集合;
  • 对于epoll,则是使用epoll_event类型的结构体数组
  • 对于poll,则是pollfd类型的结构体数组

不论使用哪种类型的lO多路转接模型,它们都需要一个或多个数据块进行工作。因此,在init的函数中,主要是用来初始化这些数据块的。在实现dispatcher的底层模型时(无论是哪一个),都需要一个DispatcherData。这个data是通过dispatcher结构体的回调函数init来初始化的。这个函数主要是用来初始化epoll、select或poll对应的数据块。关于这个函数的返回值,它是一个指针。这个设计是为了兼容epoll、select或poll的不同类型数据块。

EventLoop结构体定义EventLoop结构体中包含一个dispatcher实例。为了兼容epoll、poll和select,这个数据块通过void类型的指针来保存。这个EventLoop结构体的定义相对简单,主要目的是确保其存在。

回到先前提到的DispatcherData头文件中,通过这个结构体,我们可以获取当前dispatcher在工作时所需的一系列数据。

总结:通过以上分析,我们可以看到dispatcher结构体在系统中的核心作用。它不仅定义了lO多路转接模型所需的数据块,还提供了初始化这些数据的函数。而EventLoop结构体则为dispatcher提供了一个工作平台,确保了数据的正确使用和管理。这种模块化的设计使得代码更加清晰、易于维护,同时也为未来的扩展提供了便利。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【数据结构】查找与排序

要查询信息&#xff0c;涉及两个问题&#xff1a; 在哪里查&#xff1f;——查找表 怎么查&#xff1f;——查找方法 一.查找 1.查找表的定义&#xff1a; 查找表是由同类型的数据元素构成的集合 2.对查找表的基本操作&#xff1a; 1&#xff09;查询某个数据元素是否在查…

如何进行实例监控

目录 行实例监控 云监控 云监控核心功能 云监控ECS实例 安装插件 查看监控图表 云监控控制台 云服务器ECS控制台 设置报警规则 行实例监控 方式一&#xff1a;在服务器上自行编写并定时运行&#xff08;计划任务&#xff09;监控脚本&#xff08;shell、python&#x…

众和策略:12月新批国产网游版号数量过百

上星期五&#xff08;22日&#xff09;&#xff0c;A股冲高回落&#xff0c;三大股指挨近午盘拉升走高&#xff0c;午后再度回落走低&#xff0c;沪指尾盘跌幅收窄。到收盘&#xff0c;沪指跌0.13%报2914.78点&#xff0c;深成指跌0.39%报9221.31点&#xff0c;创业板指跌0.37%…

idea Springboot小区紧急事件上报系统VS开发mysql数据库web结构java编程计算机网页源码maven项目

一、源码特点 springboot 小区紧急事件上报系统是一套完善的信息系统&#xff0c;结合springboot框架和jsp完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用springboot框架&#xff08;MVC模式开发&#xff09;&#xff0c;系统具 有完整的源代码和数据库&am…

408数据结构错题知识点拾遗

408相关&#xff1a; 408数据结构错题知识点拾遗 408计算机网络错题知识点拾遗 对于数据结构的学习&#xff0c;个人认为要对概念性的东西进行理解&#xff0c;特别是树的性质、图的相关性质和考察的相应算法。应用题强化的话&#xff0c;对于每一章节尾的应用小节&#xff0c…

鸿蒙系列--组件介绍之其他基础组件(下)

​​​​​​鸿蒙系列--组件介绍之其他基础组件&#xff08;上&#xff09; 一、 ScrollBar 描述&#xff1a; 滚动条组件 功能&#xff1a; 用于配合可滚动组件使用&#xff0c;如List、Grid、Scroll 子组件&#xff1a;可以包含单个子组件 ScrollBar(value: { scroller…

自学SLAM(9)《第五讲:特征点法视觉里程计》作业

文章目录 1.ORB特征点1.1 ORB提取1.2 ORB描述1.3 暴力匹配1.4 最后&#xff0c;请结合实验&#xff0c;回答下⾯⼏个问题 2.从 E 恢复 R&#xff0c;t3.用 G-N 实现 Bundle Adjustment4.* 用 ICP 实现轨迹对齐 1.ORB特征点 1.1 ORB提取 ORB(Oriented FAST and BRIEF) 特征是 S…

SM2259XT Intel N18混贴3CH开卡经验分享,SM2259XT2、SM2258XT量产固件参考教程

收了条Intel的512G不认盘的ssd&#xff0c;拆出来两颗29F02T2AMCQH1&#xff0c;这个应该是正品&#xff0c;ID也没问题。然后&#xff0c;还有个山寨的256G SATA&#xff0c;主控2259XT&#xff0c;两个颗粒丝印29F1TB2ALCTH2&#xff0c;但是&#xff0c;ID与CQH1一样&#x…

2024年科技盛宴“上海智博会·上海软博会”招商工作接近尾声

2024年上海智博会和上海软博会即将于3月份在上海跨国采购会展中心盛大召开。作为全球科技和软件行业的盛会&#xff0c;这两大展会汇集了业界顶尖的企业、创新技术和前瞻思想&#xff0c;吸引了来自世界各地的专业人士和参展商。 今年的展会将一如既往地为大家呈现最前沿的科技…

[SWPUCTF 2021 新生赛]error

[SWPUCTF 2021 新生赛]error wp 信息搜集 查看页面&#xff1a; 输个单引号会报错&#xff1a; 显然是 SQL 注入。 提示看看有没有什么捷径&#xff0c;你要说捷径的话&#xff0c;sqlmap&#xff1f;你不说我也会用 sqlmap 先跑一下&#xff0c;哈哈。 sqlmap 的使用 先简…

阿里云S5服务器4核8G和轻量选哪个比较好?

腾讯云4核8G服务器优惠价格表&#xff0c;云服务器CVM标准型S5实例4核8G配置价格15个月1437.3元&#xff0c;5年6490.44元&#xff0c;轻量应用服务器4核8G12M带宽一年446元、529元15个月&#xff0c;阿腾云atengyun.com分享腾讯云4核8G服务器详细配置、优惠价格及限制条件&…

氢燃料电池商用车系统架构开发与集成技术

一、国家及不同地区对氢能发展支持政策 近三年国家对氢能及燃料电池产业的支持政策 近年来22个省市的发展规划中提到了大力支持氢能源产业发展 二、燃料电池客车架构分解及国内外已有车型 未来燃料电池客车发展方向 未来燃料电池客车新增加的燃料电池堆产业链及供应商 国内外差…

【三维重建】3D Gaussian Splatting:实时的神经场渲染

文章目录 摘要一、前言二、相关工作1.传统的场景重建与渲染2.神经渲染和辐射场3.基于点的渲染和辐射场4.*什么是 Tile-based rasterizer (快速光栅化) 三、OVERVIEW四、可微的三维高斯 Splatting五、三维高斯 自适应密度控制的优化1.优化2.高斯的自适应控制 六、高斯分布的快速…

a = a + b 与 a += b 的区别

隐式的将加操作的结果类型强制转换为持有结果的类型。如果两个整型相加&#xff0c;如 byte、short 或者 int&#xff0c;首先会将它们提升到 int 类型&#xff0c;然后在执行加法操作。 byte a 127; byte b 127; b a b; // error : cannot convert from int to byte b a…

c语言:去除最高分最低分,求平均值|练习题

一、题目 有10个裁判评分&#xff0c;去除最高分和最低分&#xff0c;求运动员的平均分。 如图&#xff1a; 二、思路分析 1、设置一个数组变量&#xff0c;用冒泡排序法排序 2、数组的首位和最后一位&#xff0c;就是最低分和最高分 3、数组的第二到n-1个&#xff0c;就是符合…

【力扣题解】P144-二叉树的前序遍历-Java题解

&#x1f468;‍&#x1f4bb;博客主页&#xff1a;花无缺 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 花无缺 原创 收录于专栏 【力扣题解】 文章目录 【力扣题解】P144-二叉树的前序遍历-Java题解&#x1f30f;题目描述&#x1f4a1;题解&#x1f30…

EEPROM

芯片地址 前四位固定为1010&#xff0c;A2~A0为由管脚电平。AT24CXX EEPROM Board模块中默认为接地。A2~A0为000&#xff0c;最后一位表示读写操作。所以AT24Cxx的读地址为0xA1,写地址为0xA0。 写24C02的时候&#xff0c;从器件地址为10100000&#xff08;0xA0&#xff09;&am…

C单片机数据类型

C语言数据类型 关键字位数表示范围stdint关键字ST关键字unsigned char80 ~ 255uint8_tu8char8-128 ~ 127int8_ts8unsigned short160 ~ 65535uint16_tu16short16-32768 ~ 32767int16_ts16unsigned int320 ~ 4294967295uint32_tu32int32-2147483648 ~ 2147483647int32_ts32unsig…

Linux的安装及管理程序

一、如何在linux安装卸载软件 1. 编译安装 灵活性较高 难度较大 可以安装较新的版本 2. rpm安装&#xff08;redhat&#xff09; linux 包安装 查软件信息&#xff1a;是否安装&#xff0c;文件列表 rpm 软件名 3. yum yum是RPM升级版本&#xff0c;解决rpm的弊端 安装软件 首…

引用jquery.js的html5基础页面模板

本专栏是汇集了一些HTML常常被遗忘的知识&#xff0c;这里算是温故而知新&#xff0c;往往这些零碎的知识点&#xff0c;在你开发中能起到炸惊效果。我们每个人都没有过目不忘&#xff0c;过久不忘的本事&#xff0c;就让这一点点知识慢慢渗透你的脑海。 本专栏的风格是力求简洁…