reactor模式的介绍

Last updated on 8 months ago

说Reactor模式前 我们看下epoll实现的服务器有什么改进的地方

在上几篇博客有介绍 io多用复用 其中主要介绍了epoll,最后 epoll实现了 io多路复用的服务器,我们再捋一捋
目前大致情况就是这样,现在是可以用,但是呢? 还可以优化下吗?因为每次都是要问三次情况,有点冗余了

image-20220312140629959

客户端有连接也是通过一个fd来管理,也是可读事件,那么讲 客户端连接也纳入 可读事件里

现在如下图所示:

可读事件分为两种类型

image-20220312140829460

确实是比较精简了,现在分为两部分,可读,可写,但实际上,可读里面还是得再分 是客户端连接还是有数据可读,还可不可再改进一点呢?

现在分为两大类,一类是 可读io,****一类是可写io,这些都是对文件描述符进行操作,而且文件描述符都是独一无二的,如果说每个文件描述符都对应一个函数的话,会不会方便很多呢?对应的函数也可以分为三类,可读,可写,建立连接;那可以建立一个结构体,用来保存fd和对应的操作函数,对应的操作函数,还有一个最为关键的是 结构体 epoll_event 不但可以保存fd,和io事件响应类型,其中还有个空指针可以灵活使用。

image-20220312142311053

每次加入 epoll_event 之前,将 *ptr执行一个结构体,该结构体包含一个 fd 和 一个函数指针
image-20220312142445086

现在呢 可是这样来处理

1
2
3
4
5
6
7
8
9
10
11
12
for (i = 0; i < nready; i++) {
if (events[i].events & EPOLLIN) { //可读
// printf("sockitem\n");
struct sockitem *si = (struct sockitem *)events[i].data.ptr; //抽出 events里的保存的结构体
si->callback(si->sockfd, events[i].events, si); // 通过结构体调用对应的函数(回调函数)
}

if (events[i].events & EPOLLOUT) {
struct sockitem *si = (struct sockitem *)events[i].data.ptr;
si->callback(si->sockfd, events[i].events, si);
}
}

到了这里 代码模式 转化为 事件驱动 : 有是三种事件 ,建立连接,读取数据,写入数据,有两种 fd ,listenfd 和 clientfd

reactor 是什么?

reactor 有反应堆的意思,这个堆则是 那些已经建立好的回调函数,当有 事件来了,ractor就有相对应的反应
这也是reactor中思想,本文中的代码也是朝着这种思想改进;reactor有三种模式

  • 单 Reactor 单进程 / 线程;

     目前现在就是处于这种模式
    

    image-20220312150155896
    单 Reactor 单进程的方案因为全部工作都在同一个进程内完成,所以实现起来比较简单,不需要考虑进程间通信,也不用担心多进程竞争。

    • 第一个缺点,因为只有一个进程,无法充分利用 多核 CPU 的性能
    • 第二个缺点,如果读写事件(业务)时间很长,而此时又突然有大量的连接接入,可能会成延迟

    所以,单 Reactor 单进程的方案不适用计算机密集型的场景,只适用于业务处理非常快速的场景

  • 单 Reactor 多线程 / 进程;

    • 单 Reactor 多线程

    开始时和 单 Reactor 单进程 一样,但在每个事件(业务)中,handle (fd对应的函数)用一个线程来处理这些业务,处理好了返回给handleimage-20220312151449241

    发挥了多核cpu的威力,线程直接通讯比较方便,但是使用多线程必然会产生资源竞争的问题,那要考虑线程锁的问题了

    • 单 Reactor 多进程

      单 Reactor 多进程 用的比较少,不过还是可以实现,用fork则是可以实现,进程间通讯比较麻烦(共享内存可以做到)…

    「单 Reactor」的模式还有个问题,因为一个 Reactor 对象承担所有事件的监听和响应,而且只在主线程中运行,在面对瞬间高并发的场景时,容易成为性能的瓶颈的地方

  • 多 Reactor 多进程 / 线程;

    该模式和前两者相比是把Reactor线程拆分了mainReactor和subReactor两个部分,mainReactor只处理连接事件,读写事件交给subReactor来处理。业务逻辑还是由线程池来处理。

image-20220312152848304

通俗点就是说 : 一个线程用于接收客户端连接,处理业务的用一个线程池来处理,池里有多少个线程按照多少个cpu的核心数而定。

待更新…