主从Reactor高并发服务器

文章目录

  • Reactor模型的典型分类
    • 单Reactor单线程
    • 单Reactor多线程
    • 多Reactor多线程
    • 本项目中实现的主从Reactor One Thread One Loop
    • 各模型的优点与缺点
  • 项目分解
    • Reactor服务器模块
      • Buffer
      • Socket
      • Channel
      • Epoller
      • TimerWheel
      • EventLoop
      • Any
      • Connection
      • Acceptor
      • LoopThread
      • LoopThreadPool
      • TcpServer
    • HTTP服务器模块
      • Util
      • Request和Response
      • Response
      • Context
      • HttpServer

本篇博客是对自己实现的主从Reactor高并发服务器的总结。

Reactor模型的典型分类

单Reactor单线程

image-20231006205240973

单Reactor多线程

image-20231006205313039

多Reactor多线程

image-20231006205348577

本项目中实现的主从Reactor One Thread One Loop

image-20231006211351458

各模型的优点与缺点

单Reactor单线程

  • 优点:实现简单,不涉及到进程/线程间通信以及资源争抢;
  • 缺点:由于所有操作均在单线程中串行执行,一旦有任务处理较慢或者请求较多时,容易导致后面的任务处理或者请求得不到响应。并且由于是单线程,没有充分利用好CPU多核资源,最终非常容易达到性能瓶颈。

单Reactor多线程

  • 优点:利用了CPU多核资源;
  • 缺点:单个Reactor线程不仅处理了新建连接请求,而且还处理了数据通信请求,也就是管理了所有的fd上的一切事件,在高并发场景下也非常容易达到性能评价。

多Reactor多线程

  • 优点:充分利用了CPU多核资源,主Reactor只负责获取连接,副Reactor负责已获取的连接,各司其职,解决了前面两种模型的性能问题;
  • 缺点:实现复杂。

主从Reactor One Thread One Loop

由于也采用了主从Reactor模式,所以性能不差,但为了服务器的实现更简单,放弃了线程池的实现。

项目分解

本项目共分为两大模块:Reactor服务器模块基于Reactor服务器模块实现的HTTP服务器模块

下面的项目分解只是简单的说明了一下各模块的功能,项目源码中有详细的注释讲解,所以强烈建议搭配项目源码一起食用。

Reactor服务器模块

服务器模块共有以下子模块

image-20231007160642203

Buffer

recv并不能够保证读取到一个完整的协议数据,所以必须要将读取到的数据先暂存起来,然后上层检查数据完整性,若完整则拿走数据,不完整则一直等读取到一个完整的协议数据时再拿走数据,那么这时就需要一个缓冲区能暂时存放recv读取到的数据。并且写入数据时,也不能直接调用send,因为fd是要被epoll监控的,但用户又不知道什么时候调用,所以用户可以直接将数据写入缓冲区中,当fd上的写事件触发时,会自动将缓冲区中的数据send到fd中。

本模块就实现的是这样的一个缓冲区。

缓冲区结构如下:

image-20231006212557141

Socket

封装系统调用socket,使对于socket的各项操作更加方便。

Channel

Channel模块是对一个fd进行事件监控管理以及事件回调管理的!

功能大概有:

  • 开启/关闭fd的事件监控(读、写);

  • 关闭fd的所有事件监控;

  • 判断fd的事件监控是否被开启了;

  • 设置事件触发后的回调函数(读事件、写事件、错误事件、关闭事件、任意事件);

  • 调用已经触发的事件回调函数。

但要注意,关于fd的开启/关闭事件监控并不是真正在Channel模块执行的,而是在Epoller模块执行的。Channel模块只是将fd的相关监控操作和相关事件回调整合在了一起。

Epoller

Epoller模块是对epoll系列操作进行的封装,让对fd的事件监控操作更加简单。

通过传入一个Channel指针,获取到fd需要监控的事件,然后Epoller模块就把这些事件进行监控,而当有事件触发时,Epoller模块就把已经触发的事件通过Channel传出,再由Channel内部调用事件回调。

功能大概有:

  • 添加/更新事件监控;
  • 移除事件监控;
  • 开始事件监控。

TimerWheel

TimerWheel是一个定时任务管理模块。

大致思想就是:将任务封装到TimerTask的析构函数中,然后用shared_ptr管理起来放入TimerWheel中的vector里,每隔一秒就清空一下vector里的元素,此时调用析构函数,就会调用定时任务了。

image-20231007164736067

每隔一秒,step_就前进一步,step_走到哪里,就清空哪里,然后当最后一个shared_ptr调用析构函数时,就会调用定时任务。

step_的每秒移动是根据timerfd技术来实现的。

创建一个timerfd,让内核每隔一秒写入一次,然后用Channel管理timerfd,注册一个读事件,在读事件里++step_,这样内核每隔一秒写入一次,就触发一次读事件,就会++step_一次。

EventLoop

EventLoop模块就是副Reactor模块,封装了Epoller模块和TimerWheel模块,并且一个EventLoop就是一个线程。

大致功能有:

  • 更新/移除事件监控(调用Epoller接口);
  • 添加/刷新/取消/移除定时任务;
  • 添加任务到任务队列中;
  • 启动事件监控(调用Epoller接口),调用事件回调(调用Channel接口),执行任务队列中的任务。

关于任务队列,要详细说一下:

对于一个连接,用户所有关于连接的操作都是线程不安全的,比如在某个事件回调执行过程中,用户开辟了一个线程池,这个线程池都是共享这个连接的,那么假设有若干个线程同时对定时任务进行操作,就会出现线程安全问题。所以用户所有的对于连接的操作都是非线程安全的,但是又不能给每个连接的接口都添加锁,这样效率就太低了。于是就有了一个解决办法,在EventLoop模块里创建一个任务队列,所有的连接的接口在调用时都进行一下判断(接口内部判断),若是副Reactor线程就直接执行接口,若是其它线程,就将该任务压入队列中,由副Reactor线程统一执行。这样就避免了多线程对于连接访问的线程安全问题。

上面功能的第四点是在同一函数中执行的,那么就会出现一种情况,任务队列中有任务了,但此时没有事件触发,epoll_wait被阻塞,最终导致任务队列中的任务得不到及时执行。所以这里用了eventfd技术解决。eventfd用Channel管理起来,注册一个读事件,然后在将任务添加到任务队列后往eventfd里写入数据,此时就会触发读事件,epoll_wait不会被阻塞,任务队列中的任务也就能够被及时执行了。

Any

Any模块是模仿C++17中的any类实现的。

TCP服务器并不知道上层要运用什么协议,也就无法用一个特定类型保存上层的上下文信息,所以用一个Any类来保存上层的上下文信息。

实现思路

要实现一个类,能够存放任意类型的数据,那么该类必定不能是模版类,模版类不能自动推演类型,并且模版类在实例化之后就只能存放单一类型的数据了。但是函数模版可以自动推演类型,于是就想到将类的构造函数设置成模版函数,成员变量为void *指针,但是void *太不安全了。于是又想到,在Any类的内部创建一对父子类,子类是模版类,成员变量为父类指针,在Any的构造函数中new一个子类对象用父类指针管理,就能够实现简易的Any类。

Connection

Connection模块是子模块中最复杂的模块,是对Buffer、Socket、Channel、Any、模块的整合,还关联了EventLoop模块。

大致功能就是:

  • 设置任务回调函数(连接创建成功的回调,消息到来的回调,任意事件回调 . . . . . .);
  • 发送/读取数据;
  • 开启/关闭非活跃连接销毁;
  • 关闭连接;
  • 切换协议。

Connection模块所有的对外提供的接口在调用时都要判断是否和副Reactor线程是同一个线程,是则直接执行,不是则压入队列。但是对于关闭连接的操作,无需进行判断,应该直接压入队列,关闭连接必须要在所有的事件触发函数执行完之后,在队列中执行。

假设有一种场景:非活跃连接销毁时间是10s,1、2、3、4、5号都有事件触发,1号事件执行了20s,那么timerfd就超时了20次,假设2、3、4中有一个就是timerfd事件,然后指针走了20下,再然后后面还没来得及执行的事件的连接就被销毁了,此时再去执行触发事件就会发生错误。所以关闭连接的操作必须要在触发事件全部调用完之后,在任务队列中执行。

Acceptor

Acceptor模块也就是主Reactor模块,负责获取新连接,内部有一个EventLoop和一个Channel来管理监听套接字。

LoopThread

该模块将EventLoop和线程强绑定在了一起。为什么非要这么做呢?

因为EventLoop模块在初始化的时候获取当前线程ID,那么用户可能在一个线程内部创建好几个EventLoop,然后再将这几个EventLoop分配给其它线程,这时虽然一个EventLoop占一个线程,但此时EventLoop内部的线程ID和实际所处的线程ID是不一样的。

LoopThreadPool

将LoopThread模块封装成一个线程池,更加方便了服务器对于LoopThread数量的掌控。

TcpServer

是对所有模块的整合,但主要的成员也就是一个主Reactor(一个EventLoop和一个Acceptor)、一个LoopThreadPool。

主要功能有:

  • 设置任务回调函数(连接创建成功的回调,消息到来的回调,任意事件回调 . . . . . .);
  • 设置LoopThreadPool的线程数量;
  • 开启非活跃连接销毁;
  • 添加定时任务。

HTTP服务器模块

image-20231009202423756

Util

该模块提供了一些工具函数,比如字符串分割函数、读文件、写文件、编码、解码等。

Request和Response

该模块存放了解析后的Http请求报文数据,并且还提供了一些方法能够快速获取Request数据。

Response

该模块存放了解析后的Http响应报文数据,并且还提供了一些方法能够快速获取Response数据。

Context

该模块是接收Request的上下文模块,服务端接收到的数据有可能并不是一个一条完整的Http报文,所以需要该模块来记录下接收Http报文的过程(上下文)。

HttpServer

对上面所有模块的整合,并且设置了不同的Http请求与回调方法的映射。

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

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

相关文章

嵌入式Linux裸机开发(五)中断管理

系列文章目录 文章目录 系列文章目录前言STM32 中断系统IMX6U中断控制8个中断GIC中断控制器GIC介绍中断IDGIC逻辑分块GIC协处理器 中断使能中断优先级 重点代码分析官方SDK函数start.S文件自行编写中断驱动文件 前言 最近在学习中发现,学Linux嵌入式不仅是对Linux的…

c语言终点站--文件操作

前言: 为什么要学习文件操作呢?想要知道这个问题,我们就需要先了解什么是数据的可持久化。 那么什么是数据的可持久化呢?数据的可持久化就是把内存中的数据对象永久的保存在电脑的磁盘文件中,将程序数据在持久状态和…

Three.js如何计算3DObject的2D包围框?

推荐:用 NSDT编辑器 快速搭建可编程3D场景 在Three.js应用开发中,有时你可能需要为3D场景中的网格绘制2D的包围框,应该怎么做? 朴素的想法是把网格的3D包围框投影到屏幕空间,例如,下图中的绿色框 3D包围框…

LeetCode【84】柱状图中的最大矩形

题目: 思路: https://blog.csdn.net/qq_28468707/article/details/103682528 https://www.jianshu.com/p/2b9a36a548fa 清晰 代码: public int largestRectangleArea(int[] heights) {int[] heightadd new int[heights.length 1];for (i…

php+html+js+ajax实现文件上传

phphtmljsajax实现文件上传 目录 一、表单单文件上传 1、上传页面 2、接受文件上传php 二、表单多文件上传 1、上传页面 2、接受文件上传php 三、表单异步xhr文件上传 1、上传页面 2、接受文件上传php 四、表单异步ajax文件上传 1、上传页面 2、接受文件上传ph…

typora常用偏好设置

启用自动保存 关闭拼写检查 插入图片的设置 将图片保存在当前文件夹内 换行设置 关闭换行符的显示功能

ElementUI增删改的实现及表单验证

文章目录 一、准备二、添加功能2.1 新增添加按钮2.2 添加弹出框2.3 data中添加内容2.4 methods中添加相关方法 三、编辑功能3.1 表格中添加编辑和删除按钮3.2 methods中添加方法3.3 修改methods中clear方法3.4 修改methods中的handleSubmit方法 四、删除书籍功能4.1 往methods的…

【LeetCode刷题笔记】哈希查找

771. 宝石与石头 解题思路: 1. HashSet ,把所有 宝石 加入 set , 然后遍历检查 每一块石头是否包含在set中 ,若包含就是宝石。 2. 计数数组map, 把所有 宝石 进行 count 数组 计数 ,, 然后遍历检查 每一块石头是否 count[stone] …

算法错题簿(持续更新)

自用算法错题簿,按算法与数据结构分类 python1、二维矩阵:记忆化搜索dp2、图论:DFS3、回溯:129612964、二叉树:贪心算法5、字符串:记忆化搜索6、01字符串反转:结论题7、二进制数:逆向…

高效节能双冷源空调架构在某新建数据中心项目中的应用

随着互联网、通信、金融等行业的发展,数据中心产业迈入高质量发展新阶段,在国家“双碳”战略目标和“东数西算”工程的有力指引下,数据中心加快向创新技术、强大算力、超高能效为特征的方向演进。数据中心已经成为支撑经济社会数字化转型必不…

Linux系统管理:虚拟机Centos Stream 9安装

目录 一、理论 1.Centos Stream 9 二、实验 1.虚拟机Centos Stream 9安装准备阶段 2.安装Centos Stream 9 3.进入系统 一、理论 1.Centos Stream 9 (1) 简介 CentOS Stream 是一种 Linux 操作系统。安装此操作系统的难题在于,在安装此系统之前&#xff0c…

想要用Chat GPT写申请文书?先看各大名校招生官对它的态度是什么?

新的申请季已经正式开始,一些热门项目的ED截止日期也不再遥远,因此很多准留学生们都已经开始了关于文书的创作。 而随着科技的不断发展,以ChatGPT为首的一众AI工具也作为一种辅助手段愈发融入了我们的生活。 那么不免就会有一些同学在准备申…