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

news2025/2/8 0:01:02

(四)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.coloradmin.cn/o/1336260.html

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

相关文章

C# 初识System.IO.Pipelines

写在前面 在进一步了解Socket粘包分包的过程中&#xff0c;了解到了.NET 中的 System.IO.Pipelines&#xff0c;可以更优雅高效的解决这个问题&#xff1b;先跟随官方的示例做个初步的认识。 System.IO.Pipelines 是一个库&#xff0c;旨在使在 .NET 中执行高性能 I/O 更加容…

56.0/DIV+CSS 布局(详细版)

目录 56.1 本章简介 56.2 实例讲解 56.2.1 菜单制作 56.2.2 美化滚动条 56.2.3 DIV+CSS 布局 56.3 综合示例 56.3.1 总体分析 56.3.2 Header 层 56.3.3 最终代码 56.1 本章简介 本章通过几个实例讲解 DIV+CSS 的应用。 采用表格布局的页面内,为了实现设计的布局,制作者往往…

SQL server 数据库练习题及答案(练习3)

一、编程题 公司部门表 department 字段名称 数据类型 约束等 字段描述 id int 主键&#xff0c;自增 部门ID name varchar(32) 非空&#xff0c;唯一 部门名称 description varchar(1024) …

数据库原理及应用·关系数据库标准语言SQL

4.1 SQL概述 4.1.1 SQL的产生和发展 1.产生 1974年&#xff0c;SQL语言的雏形最早由美国IBM公司的Raymond F. Boyce和Donald D. Chamberlin提出 1975-1979年&#xff0c;在System R上首次实现&#xff0c;由IBM的San Jose研究室研制&#xff0c;称为SEQUEL 2.发展 1986年推…

使用 pytest.ini 文件控制输出 log 日志

一、前置说明 pytest.ini 文件中可以配置参数来控制 pytest 的运行行为,其存放路径要求与 conftest.py 一样。 项目根目录project_root/ ├── pytest.ini ├── tests/ │ └── test_demo.py以test开头的测试子目录project_root/ ├── tests/ │ ├── pytest.in…

C# 学习网站

C# 文档 - 入门、教程、参考。 | Microsoft Learnhttps://learn.microsoft.com/zh-cn/dotnet/csharp/ Browse code samples | Microsoft LearnGet started with Microsoft developer tools and technologies. Explore our samples and discover the things you can build. htt…

STM32独立看门狗和窗口看门狗的区别

独立看门狗&#xff1a; 本质上是一个定时器&#xff0c;这个定时器有一个输出端&#xff0c;可以输出复位信号。 该定时器是一个 12 位的递减计数器&#xff0c;当计数器的值减到 0 的时候&#xff0c;就会产生一个复位信号。如果在计数没减到 0 之前&#xff0c;重置计数器的…

Go_defer详解

defer 1. 前言 defer语句用于延迟函数的调用&#xff0c;每次defer都会把一个函数压入栈中&#xff0c;函数返回前再把延迟的函数取出并执行。 为了方便描述&#xff0c;我们把创建defer的函数称为主函数&#xff0c;defer语句后面的函数称为延迟函数。 延迟函数可能有输入…

深入了解队列:探索FIFO数据结构及队列

之前介绍了栈&#xff1a;探索栈数据结构&#xff1a;深入了解其实用与实现&#xff08;c语言实现栈&#xff09; 那就快马加鞭来进行队列内容的梳理。队列和栈有着截然不同的工作方式&#xff0c;队列遵循先进先出&#xff08;FIFO&#xff09;的原则&#xff0c;在许多场景下…

案例149:基于微信小程序的家庭财务管理系统的设计与实现

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

怎么搭建实时渲染云传输服务器

实时渲染云传输技术方案&#xff0c;在数字孪生、虚拟仿真领域使用越来越多&#xff0c;可能很多想使用该技术方案项目还不知道具体该怎么搭建云传输服务器&#xff0c;具体怎么使用实时云渲染平台系统。点量云小芹将对这两个问题做集中分享。 一、实时渲染服务器怎么搭建&…

线性代数基础【3】向量

第一节 向量的概念与运算 一、基本概念 ①向量 ②向量的模(长度) ③向量的单位化 ④向量的三则运算 ⑤向量的内积 二、向量运算的性质 (一)向量三则运算的性质 α β β αα (β γ) (α β) γk (α β) kα kβ(k l) α kα lα (二)向量内积运…

C语言中函数调用和嵌套

函数是C语言的基本组成元素 函数调用 根据函数在程序中出现的位置有下列三种函数调用方式&#xff1a; 将函数作为表达式调用 将函数作为表达式调用时&#xff0c;函数的返回值参与表达式的运算&#xff0c;此时要求函数必须有返回值 int retmax(100,150); 将函数作为语句…

Jenkins——在流水线管道中使用指定的JDK

通过在tools下来指定JDK stage(Build) {tools {jdk "JDK8u231"}steps {sh /var/jenkins_home/tools/apache-maven-3.6.3/bin/mvn package} }JDK8u231是在全局配置下配置过的JDK

Stata回归结果该怎么分析呢?

回归分析是经典的数据分析方法之一&#xff0c;应用范围非常广泛&#xff0c;深受学者们的喜爱。它是研究分析某一变量受到其他变量影响的分析方法&#xff0c;基本思想是以被影响变量为因变量&#xff0c;以影响变量为自变量&#xff0c;研究因变量与自变量之间的因果关系。回…

中国信通院「星熠」案例公布,个推消息推送获评绿色SDK产品优秀案例

12月22日&#xff0c;由中国信息通信研究院安全研究所主办、大数据应用与安全创新实验室承办的“数据安全共同体计划成员大会&#xff08;2023&#xff09;”在京举行。每日互动&#xff08;个推&#xff09;作为“数据安全共同体计划”的联合发起单位及首批成员单位受邀出席大…

饥荒Mod 开发(二三):显示物品栏详细信息

饥荒Mod 开发(二二)&#xff1a;显示物品信息 源码 前一篇介绍了如何获取 鼠标悬浮物品的信息&#xff0c;这一片介绍如何获取 物品栏的详细信息。 拦截 inventorybar 和 itemtile等设置字符串方法 在modmain.lua 文件中放入下面代码即可实现鼠标悬浮到 物品栏显示物品详细信…

一开始我还不信!高德导航红绿灯竟然能读秒?

高德导航红绿灯为啥能读秒&#xff1f; 1 内部员工吐露 每天工作其实就是负责自己片区的红绿灯&#xff0c;一大早就去校对时间&#xff0c;然后发布到后台。是的&#xff0c;统计出来的&#xff0c;而且还是人工统计&#xff0c;有误差请见谅[害羞] 真的是很辛苦了&#xf…

云HIS源码 云HIS解决方案 支持医保功能

云HIS系统重建统一的信息架构体系&#xff0c;重构管理服务流程&#xff0c;重造病人服务环境&#xff0c;向不同类型的医疗机构提供SaaS化HIS服务解决方案。 云HIS作为基于云计算的B/S构架的HIS系统&#xff0c;为基层医疗机构&#xff08;包括诊所、社区卫生服务中心、乡镇卫…

element步骤条<el-steps>使用具名插槽自定义

element步骤条使用具名插槽自定义 步骤条使用具名插槽: <el-steps direction"vertical" :active"1"><el-step><template slot"description">//在此处可以写你的插槽内容</template>/el-step> </el-steps>步骤…