【项目篇】高并发服务器 - Reactor模型详解

Reactor模式

Reactor模式也叫事件驱动处理模式

Reactor模型:

概念

指的是有一个或者多个客户端同时连接到我们的服务器上面,然后去请求我们的服务器。我们的服务器如何对这些请求进行处理呢?

它处理的过程是:哪一个客户端给我发送了数据,触发了我的事件,那么我就去处理谁。你要是没触发我的事件,没有给我发送数据我就不处理你,这就叫做事件驱动处理模式

谁触发了事件就去处理谁,那么我怎么知道谁触发了事件呢?

在这个Reactor模式里面使用到了一个非常关键的技术,叫做 I/O多路复用,也叫做 I/O多路转接。

分类:

单Reactor单线程:单I/O多路复用 + 业务处理

单Reactor单线程:指的是在一个线程里面对我们的事件进行监控并处理。

处理思想是:谁给我发送了数据,触发了我的事件,就处理谁。

优缺点:

  • 优点:因为是单线程操作,操作都是串行化的,思想比较简单,编码流程也比较简单(不用考虑进程或者线程之间的通信,以及安全问题)
  • 缺点: 因为所有的事件监控以及业务处理都是在一个线程中完成的,因此很容易造成性能瓶颈。

适用场景:

适用于客户端数量较少,且业务处理比较简单快速的场景。


单Reactor多线程:单I/O多路复用 + 线程池(业务处理)

单Reactor多线程:有一个线程,它进行我们Reactor模式事件监控,所有的客户端它请求到来之后,我们的服务器这边有一个线程专门进行事件监控,你有事件触发了,我进行I/O处理,把你的事件读取上来,读取上来我并不进行业务处理。我们有一个专门处理业务的线程池,读取线程把接收上来的数据抛入到工作线程池中,让工作线程池里面的线程进行业务处理。这样的话在我们的事件监控线程里面只需要做两件事情:事件监控 以及 客户端的I/O操作。对cpu资源能得到一个充分的利用。

优缺点:

  • 优点:充分利用了CPU的多核资源,处理效率可以更高,降低了代码的耦合度。
  • 缺点:在单个Reactor线程中,包含了对所有客户端的事件监控,以及所有客户端的I/O操作,不利于高并发场景(每一个时刻都有很多客户端来连接),来不及进行新的客户端连接处理。

多Reactor多线程:多I/O多路复用 + 线程池(业务处理)

多Reactor多线程:基于单Reactor多线程的缺点考虑,如果I/O的时候有连接的到来,那么连接将无法处理,因此将连接处理单独拎出来。

因此让一个Reactor线程仅仅进行新连接的处理,让其他的Reactor线程进行I/O处理,IO Reactor线程拿到数据分发给业务线程池进行业务处理。

因此多Reactor多线程模式,也叫主从Reactor模型。

  • 主Reactor线程:进行新连接事件监控
  • 从属Reactor线程: 进行IO事件监控
  • 业务线程池:进行业务处理

优缺点:

  • 优点:充分利用CPU多核资源,并且可以进行合理分配。
    理解:执行流也不是越多越好,执行流多了,反而会增加CPU切换调度的成本。
    因此在有些主从Reactor模式里面,它就不设置业务线程了,业务也在我们的从属Reactor线程里面来完成。

目标定位:One Loop Per Thread 主从Reactor 模型高并发服务器

我们要实现的是主从Reactor模型服务器,也就是主Reactor线程负责监控监听文件描述符,获取新建连接,保证获取新连接的高效性,提高服务器的高并发性能。

主Reactor获取到新连接后分发给子Reactor进行通信事件监控。而子Reactor线程监控各自的描述符的读写事件进行数据读写以及业务处理。

One Thread One Loop的思想就是把所有的操作都放到以个线程中进行,一个线程对应一个事件处理的循环。

功能模块划分:

基于上面的理解,我们要实现的是一个带有协议支持的Reactor高性能服务器,因此将整个项目的实现划分为两个大的模块:

  • SERVER模块:实现Reactor模型的TCP服务器。
  • 协议模块:对当前的Reactor模型服务器提供应用层协议支持。

SERVER模块:

SERVER模块是对我们所有的连接以及线程的管理,让他们各司其职,在格式的时候做合适的事,最终完成高性能服务器组件的实现。

具体的管理分为三部分:

  • 监听链接管理:对监听连接进行管理
  • 通信连接管理:对通信连接进行管理
  • 超时连接管理:对超时连接进行管理

基于以上的管理思想,将这个模块又可以细分为一下模块:

Buffer模块:

Buffer模块是一个缓冲区模块,用于实现通信中用户态的接收缓冲区和发送缓冲区功能。

Socket模块:

Socket模块是对套接字操作封装的一个模块,主要实现socket各项操作。

Channel模块:

Channel模块是对一个描述符需要进行的IO事件管理的模块,实现对描述符可读,可写,错误等事件的管理操作,以及Poller模块对描述符进行IO事件监控就绪后,根据不同的事件,回调不同的处理函数功能。

Connection模块:

Connection是对Buffer模块,Socket模块,Channel模块的一个整体封装,实现了对一个通信套接字的整体管理,每一个进行数据通信的套接字都会使用Connection(也就是accept获取到的新连接)进行管理。

  • Connection模块内部包含有三个由组件使用者传入的回调函数:连接建立完成回调,事件回调,新数据回调,关闭回调。
  • Connection模块内部包含有两个组件使用者提供的接口:数据发送接口,连接关闭接口。
  • Connection模块内部包含有两个用户态缓冲区:用户态接收缓冲区,用户态发送缓冲区。
  • Connection模块内部包含有一个Socket对象:完成描述符面向系统的IO操作。
  • Connection模块内部包含一个Channel对象:完成描述符IO事件的就绪处理。

具体的处理流程如下:

  1. 实现向Channel提供可读,可写,错误等不同事件的IO事件回调函数,然后将Channel和对应的描述符添加到Poller事件监控中。
  2. 当描述符在Poller模块中就绪了IO可读事件,则调用描述符对应Channel中保存的读事件处理函数,进行数据读取,将socket接收缓冲区全部读到Connection管理的用户态缓冲区中,然后调用由组件使用者传入的新数据到来回调函数进行处理。
  3. 组件使用者进行数据的业务处理完毕后,通过Connection向使用者提供的数据发送接口,将数据写入Connection的发送缓冲区中。
  4. 启动描述符在Poller模块中的IO写事件监控,就绪后,调用Channel中保存的写事件处理函数,将发送缓冲区中的数据通过Socket进行面向系统的实时数据发送。
  • Acceptor模块:
    Acceptor模块是对Socket模块和Channel模块的整体封装,实现了对一个监听套接字的整体管理。

Acceptor模块内部包含有一个Socket对象:实现监听套接字的工作

Acceptor模块内部包含有一个Channel对象:实现监听套接字IO事件就绪的处理

具体处理流程如下:

  1. 实现向Channel提供可读事件的IO事件处理回调函数,函数的功能也就是获取新连接。
  2. 为新连接构建一个Connection对象出来。

TimerQueue模块 :

TimerQueue模块是实现固定时间,定时任务的模块,可以理解为就是一个定时任务管理器,向定时任务管理器中添加一个任务,任务将在固定时间后被执行,同时也可以通过刷新定时任务来延迟任务的执行。

这个模块主要对Connection对象的生命周期做管理,对非活跃连接进行超时释放功能。

TimerQueue模块内部包含一个timefd:Linux系统提供的定时器。

TimeQueue模块内包含一个Channel对象:实现对timefd的IO时间就绪回调处理

Poller模块

Poller模块是对epoll模块的一个封装,主要实现对epoll模块IO事件的添加,修改,移除,获取活跃连接功能。

EventLoop模块

EventLoop模块我们可以理解成为上面的Reactor模块,它是对Poller模块,TimeQueue模块,Socket模块的一个整体封装,进行所有描述符的事件监控。

EventLoop模块必然是一个对象对应一个线程模块,线程内部的目的就是运行EventPool的启动函数。

EventLoop模块为了保证整体的线程安全问题,因此要求使用者对于Conection的所有操作,一定要在其对应的EventLoop线程内完成,不能再其他线程完成(比如组件使用者使用Connection发送数据以及关闭连接这种操作)。

EventLoop模块保证在自己内部所监控的所由描述符,都是活跃连接,非活跃连接就要即使释放,避免资源浪费。

EventLoop模块内部包含一个eventfd,evnetfd其实是Linux内核提供的一个处理事件的fd,专门用于事件通知。

EventLoop模块内部包含一个Poller对象:用于进行描述符的IO事件监控。

EventLoop模块内部包含一个TimerQueue对象:用于对定时任务的管理。

EventLoop模块内部包含一个PendingTask队列:组件使用者将对Connection的所有操作,都要添加到任务队列中,由EventLoop模块进行管理,并在EventLoop对应的线程中进行执行。

每一个connection对象都会绑定在一个EventLoop上,这样就能保证对这个连接的所有操作都是在一个线程中进行的。

具体的操作流程:

  1. 通过Poller模块对当前模块管理内的所有描述符进行IO事件监控,有描述符事件就绪后,通过描述符对应的Channel进行事件处理。
  2. 所有就绪的描述符IO事件处理完毕后,对任务队列中所有操作,顺序进行执行。
  3. 由于epoll的事件监控,有可能会因为没有事件的到来而持续的阻塞,导致任务队列中的任务不能即使得到执行,因此创建了eventfd,添加到Poller的事件监控中,用于实现每次向任务队列添加任务时候,通过向evnetfd写入数据来唤醒epoll的阻塞。

TcpServer模块:

这个模块是⼀个整体Tcp服务器模块的封装,内部封装了Acceptor模块,EventLoopThreadPool模块。

• TcpServer中包含有⼀个EventLoop对象:以备在超轻量使用场景中不需要EventLoop线程池,只需要在主线程中完成所有操作的情况。

• TcpServer模块内部包含有⼀个EventLoopThreadPool对象:其实就是EventLoop线程池,也就是子Reactor线程池

• TcpServer模块内部包含有⼀个Acceptor对象:⼀个TcpServer服务器,必然对应有⼀个监听套接字,能够完成获取客户端新连接,并处理的任务。

• TcpServer模块内部包含有⼀个std::shared_ptr的hash表:保存了所有的新建连接对应的Connection。

注意,所有的Connection使用shared_ptr进行管理,这样能够保证在hash表中删除了Connection信息后,在shared_ptr计数器为0的情况下完成对Connection资源的释放操作。


HTTP协议模块:

HTTP协议模块用于对高并发服务器模块进行协议支持,基于提供的协议支持能够更方便的完成指定协议服务器的搭建。

而HTTP协议支持模块的实现,可以细分为以下几个模块。

Util模块:

这个模块是⼀个工具模块,主要提供HTTP协议模块所用到的⼀些⼯具函数,比如url编解码,文件读写...等。

HttpRequest模块:

这个模块是HTTP请求数据模块,用于保存HTTP请求数据被解析后的各项请求元素信息。

HttpResponse模块:

这个模块是HTTP响应数据模块,用于业务处理后设置并保存HTTP响应数据的的各项元素信息,最终会被按照HTTP协议响应格式组织成为响应信息发送给客户端。

HttpContext模块:

这个模块是一个HTTP请求接收的上下文模块,主要是为了防止在一次接收的数据中,不是一个完整的HTTP请求,则解析过程并未完成,无法进行完整的请求处理,需要在下次接收到新数据后继续根据上下文进行解析,最终得到⼀个HttpRequest请求信息对象,因此在请求数据的接收以及解析部分需要⼀个上下文来进行控制接收和处理节奏。

HttpServer模块:

这个模块是最终给组件使用者提供的HTTP服务器模块了,用于以简单的接口实现HTTP服务器的搭建。

HttpServer模块内部包含有一个TcpServer对象:TcpServer对象实现服务器的搭建

HttpServer模块内部包含有两个提供给TcpServer对象的接口:连接建立成功设置上下文接文,数据处理接口。

HttpServer模块内部包含有⼀个hash-map表存储请求与处理函数的映射表:组件使用者向HttpServer设置哪些请求应该使用哪些函数进行处理,等TcpServer收到对应的请求就会使用对应的函数进行处理。

相关推荐
WangJunXiang62 小时前
keepalived高可用与负载均衡
运维·负载均衡
枳实-叶2 小时前
音视频 Linux 指令速查
linux·运维·音视频
凤年徐2 小时前
Linux常用命令详解
java·linux·服务器
SilentSamsara2 小时前
Linux 管道与重定向:命令行精髓的结构性解析
linux·运维·服务器·c++·云原生
the sun342 小时前
NFS 配置全指南 —— 从踩坑到手动挂载的完整落地
linux·运维·服务器·ubuntu
SilentSamsara2 小时前
Shell 脚本进阶:从能跑到写得优雅
linux·运维·服务器·自动化·ssh·bash
xiaoshuaishuai82 小时前
C# 实现“superpowers进化
运维·服务器·windows·c#
孙同学_3 小时前
【项目篇】高并发服务器 - 从 Buffer 到 TcpServer 构建高并发服务器引擎
运维·服务器