基于多反应堆的高并发服务器【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提供了一个工作平台,确保了数据的正确使用和管理。这种模块化的设计使得代码更加清晰、易于维护,同时也为未来的扩展提供了便利。

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

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

相关文章

Spring Boot3 Web开发技术

前期回顾 springboot项目常见的配置文件类型有哪些&#xff1f;哪种类型的优先级最高 yml properties yaml 读取配置文件里的数据用什么注解&#xff1f; value restful风格 RESTful 风格与传统的 HTTP 请求方式相比&#xff0c;更加简洁&#xff0c;安全&#xff0c;能隐…

Kind创建k8s - JAVA操作控制

kind 简介kind 架构安装 Kind (必备工具)docker官网kubectl官网kind官网校验安装结果 关于kind 命令 安装一个集群查看当前 Kubernetes 集群中的节点信息。查看当前命名空间下中的Pod&#xff08;容器实例&#xff09;的信息。使用 kind create cluster 安装&#xff0c;关于安…

MYSQL一一函数一一流程函数

咱今天讲的是MySQL函数中的流程函数&#xff0c;会有3小题和一个综合案例帮助大家理解 流程函数是很常用的一类函数&#xff0c;可以在SQL语句中实现条件筛选&#xff0c;从而提高语句的效率 小题&#xff1a; ①if语句&#xff1a; select if(flash,ok,error); //如果…

Java之Atomic 原子类总结

Java之Atomic 原子类总结 Atomic 原子类介绍 Atomic 翻译成中文是原子的意思。在化学上&#xff0c;我们知道原子是构成一般物质的最小单位&#xff0c;在化学反应中是不可分割的。在我们这里 Atomic 是指一个操作是不可中断的。即使是在多个线程一起执行的时候&#xff0c;一…

【Java】你掌握了多线程吗?

【文末送书】今天推荐一本Java多线程编程领域新书《一本书讲透Java线程》 摘要 互联网的每一个角落&#xff0c;无论是大型电商平台的秒杀活动&#xff0c;社交平台的实时消息推送&#xff0c;还是在线视频平台的流量洪峰&#xff0c;背后都离不开多线程技术的支持。在数字化转…

FPGA-ZYNQ-7000 SoC在嵌入式系统中的优势

FPGA-ZYNQ-7000 SoC在嵌入式系统中的优势 本章节主要参考书籍《Xilinx Zynq-7000 嵌入式系统设计与实现 基于ARM Cortex-A9双核处理器和Vivado的设计方法 (何宾&#xff0c;张艳辉编著&#xff09;》 本章节主要讲述FPGA-ZYNQ-7000 SoC在嵌入式系统中的优势&#xff0c;学习笔…

LeetCode刷题--- 优美的排列

个人主页&#xff1a;元清加油_【C】,【C语言】,【数据结构与算法】-CSDN博客元清加油_【C】,【C语言】,【数据结构与算法】-CSDN博客 个人专栏 力扣递归算法题 http://t.csdnimg.cn/yUl2I 【C】 http://t.csdnimg.cn/6AbpV 数据结构与算法 ​​​​​​http://t.cs…

UGUI Panel的显示和隐藏优化

unity UI如何开启&#xff08;显示&#xff09;或者关闭&#xff08;隐藏&#xff09;Panel界面&#xff0c;相信大家都是知道的&#xff0c;但是如何做最好呢&#xff1f; 可能大家一般开启/关闭界面的方法就是直接SetActive吧。这样做通常是可以的&#xff0c;简答快速地解决…

排序算法--------计数排序

作者前言 &#x1f382; ✨✨✨✨✨✨&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; ​&#x1f382; 作者介绍&#xff1a; &#x1f382;&#x1f382; &#x1f382; &#x1f389;&#x1f389;&#x1f389…

【Unity】万人同屏高级篇, 自定义BRGdots合批渲染,海量物体目标搜索

博文介绍了最基本的实现原理&#xff0c;有些老板懒得折腾&#xff0c;所以特意熬了几个秃头的夜把RVO、BRG、GPU动画、海量物体目标搜索等高度封装成了开箱即用的插件。 划重点&#xff01;&#xff01;此方案是绕开Entities(ECS)&#xff0c;不用写一行ECS代码&#xff0c;现…

关于个人Git学习记录及相关

前言 可以看一下猴子都能懂的git入门&#xff0c;图文并茂不枯燥 猴子都能懂的git入门 学习东西还是建议尽可能的去看官方文档 权威且详细 官方文档 强烈建议看一下GitHub漫游指南及开源指北&#xff0c;可以对开源深入了解一下&#xff0c;打开新世界的大门&#xff01; …

【Jmeter、postman、python 三大主流技术如何操作数据库?】

前言 1、前言 只要是做测试工作的&#xff0c;必然会接触到数据库&#xff0c;数据库在工作中的主要应用场景包括但不限于以下&#xff1a; 功能测试中&#xff0c;涉及数据展示功能&#xff0c;需查库校验数据正确及完整性&#xff1b;例如商品搜索功能 自动化测试或性能测试…

【开源】基于JAVA的学校热点新闻推送系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 新闻类型模块2.2 新闻档案模块2.3 新闻留言模块2.4 新闻评论模块2.5 新闻收藏模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 新闻类型表3.2.2 新闻表3.2.3 新闻留言表3.2.4 新闻评论表3.2.5 新闻收藏表 四、系统展…

Ubuntu20.04-设置合上盖子电脑不熄屏,不休眠等

1.配置文件 /etc/systemd/logind.conf 1.1 配置文件解析 输入命令 sudo nano /etc/systemd/logind.conf打开的文件内容 # This file is part of systemd. # # systemd is free software; you can redistribute it and/or modify it # under the terms of the GNU Lesser G…

【深度学习目标检测】十一、基于深度学习的电网绝缘子缺陷识别(python,目标检测,yolov8)

YOLOv8是一种物体检测算法&#xff0c;是YOLO系列算法的最新版本。 YOLO&#xff08;You Only Look Once&#xff09;是一种实时物体检测算法&#xff0c;其优势在于快速且准确的检测结果。YOLOv8在之前的版本基础上进行了一系列改进和优化&#xff0c;提高了检测速度和准确性。…

【OAuth2】用户授权第三方应用,流程详解及模式

目录 一、讲述 1. 是什么 2. 工作流程 3. OAuth2的好处 二、协议流程 1. 应用场景 2. 实例 3. 安全体现 4. 角色 5. 认证流程 三、授权模式 1. 授权码模式 2. 简化(隐式)模式 3. 密码模式 4. 客户端模式 每篇一获 一、讲述 1. 是什么 OAuth&#xff08;开放授…

【开源】基于Vue+SpringBoot的新能源电池回收系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 用户档案模块2.2 电池品类模块2.3 回收机构模块2.4 电池订单模块2.5 客服咨询模块 三、系统设计3.1 用例设计3.2 业务流程设计3.3 E-R 图设计 四、系统展示五、核心代码5.1 增改电池类型5.2 查询电池品类5.3 查询电池回…

蓝桥杯备赛 day 1 —— 递归 、递归、枚举算法(C/C++,零基础,配图)

目录 &#x1f308;前言 &#x1f4c1; 枚举的概念 &#x1f4c1;递归的概念 例题&#xff1a; 1. 递归实现指数型枚举 2. 递归实现排列型枚举 3. 递归实现组合型枚举 &#x1f4c1; 递推的概念 例题&#xff1a; 斐波那契数列 &#x1f4c1;习题 1. 带分数 2. 反硬币 3. 费解的…

手把手教你安装Kali Linux

Kali Linux操作系统 Kali Linux&#xff0c;一种基于Debian的Linux发行版&#xff0c;是用于渗透测试和网络安全领域的专业工具。它包含了大量的安全测试工具和漏洞扫描器&#xff0c;用于评估网络的安全性和防御能力。Kali Linux有一个友好的界面和易于使用的工具&#xff0c…

数字调制学习总结

调制&#xff1a;将基带的信号的频谱搬移到指定的信道通带内的过程。 解调&#xff1a;把指定信号通带内的信号还原为基带的过程。 1、2ASK调制 原理如下图所示&#xff0c;基带信号为单极不归零码&#xff0c;与载波信号相乘&#xff0c;得到调制信号。 调制电路可以用开关…