基于主从Reactor模型的高性能网络通信框架

muduo库的简化仿写

知识要求

C++11特性:多线程,智能指针

Linux:网络通信,多路复用

为什么写这个项目

在学校学了这些知识,想要提高自己的能力,就在网上找的相关的开源项目,通过原码阅读、仿写的⽅式提升多线程⽹络编程能⼒。

整体计划

1.高性能服务器框架 10

2.支持应用层协议:HTTP 5

3.通过自定制协议实现RPC通信框架 (自定制协议:在网络通信中别人不知道我的格式) 5

框架设计

Reactor模型

别名:反应堆模型(直译),事件驱动模型(原理),事件循环模型(实现机制)。

就是epoll

核心特性

被动反应:不像主动轮询,而是等待事件发生然后反应

链式反应:⼀个事件可能触发⼀系列后续处理

能量集中:少量线程处理大量连接,像反应堆高效产⽣能量

控制中心:像核反应堆的控制系统,统⼀调度所有事件

传统阻塞模型:主动询问

每个客户端连接主动询问:"你有数据吗?"

Reactor模型:被动反应

事件发生时自动被唤醒

reactor模型的演化过程

单reactor模型

优点:极其简单

缺点:一个线程里既要获取新连接,又要处理数据

单reactor+线程池模型

把 业务处理 单独拿出来用 线程池 处理。

缺点:依旧存在单线程IO的性能瓶颈。只有一个线程 处理所有网络 IO 事件

主从reactor模型

主从 Reactor 模型将连接接收读写事件处理分离,主 Reactor 只负责 accept 新连接并分派,多个从 Reactor 分担网络 IO 与协议解析,耗时业务交由独立业务线程池,充分利用多核 CPU、消除单线程瓶颈与单点故障。

Proactor模型

别名:前摄器模型、主动器模型

核心思想

应用程序发起异步I/O操作,然后继续执行其他任务,当I/O操作完成时,操作系统会通知应用程序,应用程序再处理相应的业务逻辑。

Reactor:"当I/O就绪时通知我,我来做读写操作"

Proactor:"我告诉你我要读写什么,你帮我做完后通知我结果。

核心特点

异步I/O:应⽤程序调⽤异步I/O操作,由操作系统负责执⾏I/O,应⽤程序不阻塞等待。

完成通知:I/O操作完成后,操作系统主动通知应⽤程序。

分离关注点:应⽤程序的I/O操作和事件处理分离,由操作系统负责I/O操作,应⽤程序负责业务处 理。

I/O操作

1.发起IO操作

2.等待IO就绪

3.拷贝数据

4.IO调用返回

异步IO

进程发起IO调用,IO等待+数据拷贝过程由系统完成,IO完成后通知进程

概要设计

Reactor模型实现主要分为三⼤模块:reactor、handler,逻辑整合,在这三⼤模块中,⼜细分了其他 的⼦模块进⾏细节实现。

模块设计

reactor:负责事件循环监控及分发功能(事件监控)

Poller描述符监控模块:负责描述符的事件监控与事件分发。

Eventloop事件循环模块:负责实现Poller事件循环监控 + 任务池实现连接的线程安全操作。

  • Weakup事件循环唤醒模块:负责Poller的阻塞唤醒事件描述符的读事件处理(muduo中没 有)

LoopThread模块:负责实现Eventloop与对应线程thread封装在⼀起。

LoopThreadPool模块:负责实现池化LoopThread功能

handler:负责描述符的监控事件描述以及事件处理(事件处理)

Channel事件处理器模块:负责描述描述符的监控事件以及事件处理

TimerQueue定时器模块:负责实现定时器中定时器描述符的读事件处理

  • Timestamp时间操作模块:负责简单的系统⽇期时间操作
  • Timer定时任务模块:负责描述⼀个定时任务(过期时间,是否重复,过期回调,...)
  • TimerId模块:与定时任务Timer对应的定时任务ID(唯⼀序号,定时任务指针)

Acceptor监听对象模块:负责监听套接字的读事件处理(新连接)

Connection连接对象模块:负责I/O套接字的读写事件处理。(IO处理)

  • Buffer模块:负责实现发送/接收数据缓冲区
  • Sockets模块:负责套接字的各项基础操作

逻辑整合

TcpServer服务器模块:负责基于以上模块整合实现服务端

TcpClient客⼾端模块:负责基于以上模块整合实现客户端

模块关系图

详细设计

channel

fd:要监控的描述符

event:要监控的事件

revent:实际就绪的事件

readcallback:读事件回调函数

writecallback:写事件回调函数

errorcallback:错误时间回调函数

closecallback:连接关闭事件回调函数

功能:如果要对哪个描述符进行事件监控,就为其构造一个channel对象。这个channel对象就描述了监控了哪个描述符的哪个事件。然后将这些信息交给Poller(epoll)进行实际的事件监控。

poller

epoll封装

updataChannel

removeChannel

wait

eventLoop

epoll_wait:事件监控

执行跨线程任务

weakuoChannel(fd,this)构造一个channel对象,里面有fd,eventloop*loop。eventloop*loop指向的就是eventloop对象。enableReading开始监控,调用loop->updateChannel(this),指向eventloop的updateChannel。然后把updateChannel加到poller里面进行监控。

是事件循环类。

功能:

1.针对Poller进行二次封装实现channel的事件监控。

2.解决后期连接 线程安全问题 ------任务池。

3.定时任务的操作。

定时器的设计

Timestamp类:时间戳管理类

Timerld:定时任务ID。唯一标识一个定时任务对象

Timer类:定时任务类,封装了一个回调函数对象

TimerQueue类:定时任务管理类,

定时器实现原理:

1.能够让系统通知进程自身,现在超时了。(告诉系统一个固定的时间点,让系统在这个时间点通知进程)

timerfd本质原理:

本质是内核的一个8B计数器+计时功能,当我们给描述符设置了一个超时/间隔时间,内核在每隔一定的间隔时间后,给这个计数器+1,这时候,定时器描述符就会触发可读事件(这个事件就是超时通知),超时之后,我们需要将计数器中的计数归零。

2.能够快速的找出超时任务,针对这些任务执行一下

定时任务管理 实现思想:

1.时间轮思想

1.定义一个时间轮(环形数组/链表,每个结点都是一个链表,该链表中存放的都是定时任务)

2.定义一个刻度指针,刻度指针指向哪个结点,那个结点链表中的任务就都是过期任务

3.通过定时器,让刻度指针移动起来

优点:不用调整堆结构

缺点:需要不断触发事件

2.小根堆思想

小根堆:根顶总是最小节点

定义一个以时间为比较关键字的小根堆,堆顶任务就是最接近超时任务。

实现过程:

1.获取堆顶节点的时间戳,定时定时器超时时间

2.当定时器超时的时候,意味着堆顶节点必然超时

3.当超时时,不断从堆顶取出节点,直到堆顶节点时间大于当前系统时间(没有超时)

优点:不会出现空跑

muduo库中的定时器的实现:小根堆思想。

实现所用的数据结构:红黑树

节点结构:pair<timestamp , timer>

左叶子节点就是最接近超时的节点。

定时器的实现:

1.使用timerfd实现定时器超时通知

2.使用红黑树进行超市任务管理(map<pair<timestamp , timer*>>),在收到超时通知后快速获取过期任务。

3.实现细节:

1.定义一个定时任务池,保存所有的定时任务。

2.取出任务池中最小节点的超时时间,设置为定时器的国企通知时间

3.当收到通知,则从定时任务池中取出所有的过期任务,然后进行处理。

4.针对所有的过期任务,判断是否是循环任务,如果是且没有被标记为取消,则重新添加到定时任务池中;如果时但是被标记为取消,则直接释放任务;如果不是则直接释放任务。

取消定时任务的细节:

(1)如果任务在定时任务池中,意味着必然不是正在执行的过期任务。

(2)如果任务不在定时任务池中,这个任务必然在过期池中,正在被执行

为什么要有定时器

1.几乎在所有的网络通信库中,都会配备有定时器功能,因为很多时候我们搭建了服务器,都需要丢客户端连接设置超时断开时间。

2.任务池加入的任务是什么:任务就是一个回调函数对象+任务管理信息

3.循环任务都可能存放在哪些池子中:(1)任务都在红黑树定时任务池中(2)任务过期的时候,会从定时任务池中取出,放到临时的过期池中处理,处理完毕后重新放回定时任务池。

Acceptor

监听事件的处理

TcpConnection

通信事件的处理

readv分块接收操作

核心关键点:

为什么muduo库性能在大块数据的pingpong测试中比libevent高了近3倍?

因为muduo中使用readv分块接收,一次IO取出了socket缓冲区所有数据;而libevent是循环每次接收4096字节数据。

核心关键点:

muduo库中,epoll监控时事件触发模式使用的是水平触发,而不是边缘触发。

水平触发:只要socket接收缓冲区中的数据大于高水位标记(1B)就会触发事件

边缘触发:只有新数据到来的时候才会触发事件,强制要求程序员在一次事件触发中,处理完所有数据;减少了数据触发数量。

因为muduo数据接受操作使用分块接收,一次性就取出了所有数据,没必要使用边缘触发。水平出发逻辑也更加简单。

逻辑关系

fd挂载到epoll,找到Channel里的事件的回调函数,然后执行。

Acceptor(监听管理器)里有个handleRead来获取新连接。Channel里的回调函数就是handleRead。

获取到的新连接会创建一个TcpConnection,里面封装一个Channel对象,来设置一系列的回调函数。

TimerQueue里有个定时器描述符,也会封装一个Channel对象,执行定时任务。

请求流程

1.服务器启动,Main Reactor 开始监听端口。

2.客户端发起连接,Acceptor 读取事件,accept 得到新 fd。

3.Main Reactor 轮询选择一个 Sub Reactor。

4.创建 TcpConnection 对象,绑定到对应 Sub Reactor。

5.数据到达时,epoll 通知,Channel 调用读回调。

6.数据读到 Buffer,触发用户的 MessageCallback。

7.用户 send 数据时,先写入输出缓冲区,开启写事件,可写时自动发送。

8.连接关闭时,自动清理资源、回调、移除监听。

相关推荐
为何创造硅基生物2 小时前
C++ 类的 static 成员函数
c++
bubiyoushang8882 小时前
基于 C/C++ 的 MQTT 物联网通信协议实现
c语言·c++·物联网
牛油果子哥q2 小时前
【C++ const 】全场景深度精讲:修饰规则、底层常量折叠、指针 / 引用 / 成员函数实战、易错坑点与工程代码实现
开发语言·c++
郝学胜_神的一滴2 小时前
Qt 高级开发 025:打造优雅界面的艺术与高效重构之道
c++·qt
牛油果子哥q3 小时前
【C++指针与引用】C++指针与引用底层彻底精讲:本质区别、易错深坑、底层内存模型、工程选型、笔试面试满分解析
c++·面试
十五年专注C++开发3 小时前
CMake实践:VS2019控制台程序隐藏控制台方法
c++·windows·cmake·控制台隐藏
小欣加油3 小时前
leetcode3635 最早完成陆地和水上游乐设施的时间II
数据结构·c++·算法·leetcode
QT-Neal3 小时前
链接和库整理
c++
剑锋所指,所向披靡!3 小时前
C++多线程实现
开发语言·c++·chrome