Reactor模式

目录

一、核心思想:好莱坞原则与事件驱动

二、四大核心组件拆解

[1. Handle(句柄 / 资源)](#1. Handle(句柄 / 资源))

[2. Synchronous Event Demultiplexer(同步事件多路复用器)](#2. Synchronous Event Demultiplexer(同步事件多路复用器))

[3. Initiation Dispatcher(初始分发器 / 反应器核心)](#3. Initiation Dispatcher(初始分发器 / 反应器核心))

[4. Event Handler(事件处理器)](#4. Event Handler(事件处理器))

三、经典运行流程(生命周期)

[四、Reactor 的三大演进模型](#四、Reactor 的三大演进模型)

[1. 单 Reactor 单线程模型](#1. 单 Reactor 单线程模型)

[2. 单 Reactor 多线程模型](#2. 单 Reactor 多线程模型)

[3. 主从 Reactor 多线程模型 (Multi-Reactor)](#3. 主从 Reactor 多线程模型 (Multi-Reactor))

[五、Reactor 模式的规则](#五、Reactor 模式的规则)

[六、基于 C++ 与 epoll ET 模式的 Reactor 架构剖析](#六、基于 C++ 与 epoll ET 模式的 Reactor 架构剖析)

[1. 架构映射](#1. 架构映射)

[1.1 句柄 -> fd 与 Socket 类](#1.1 句柄 -> fd 与 Socket 类)

[1.2 同步事件多路复用器 -> Epoller 类](#1.2 同步事件多路复用器 -> Epoller 类)

[1.3 事件处理器 -> Connection 类](#1.3 事件处理器 -> Connection 类)

[1.4 核心分发器 -> TcpServer 类](#1.4 核心分发器 -> TcpServer 类)

[2. 代码展示](#2. 代码展示)

[2.1 事件处理器:连接的抽象 (Connection 类)](#2.1 事件处理器:连接的抽象 (Connection 类))

[2.2 多路复用器:底层事件监控 (Epoller 类)](#2.2 多路复用器:底层事件监控 (Epoller 类))

[2.3 Reactor 核心分发器 (TcpServer 类核心逻辑)](#2.3 Reactor 核心分发器 (TcpServer 类核心逻辑))

[2.4 业务层解耦 (main.cc)](#2.4 业务层解耦 (main.cc))


Reactor 模式是高性能网络编程领域最核心、最经典的架构模式,没有之一。现代几乎所有的高性能网络框架(如 Netty、Nginx、Redis、Node.js 等)底层都深深烙印着 Reactor 的思想。

一、核心思想:好莱坞原则与事件驱动

Reactor 模式的本质是"事件驱动""控制反转"。

在传统的阻塞式网络编程中,程序是主动去等待数据(例如调用 read,如果没有数据就一直卡在那里等)。

而在 Reactor 模式中,程序秉持的是"好莱坞原则:Don't call us, we'll call you.(不要打电话给我们,我们会打给你)"。

系统将所有的 I/O 操作(连接、读、写)转化为一个个事件。程序不再主动阻塞等待数据,而是把所有的 Socket 注册到一个"事件分发器"上。当有 Socket 真正发生了读写事件时,分发器才会"打电话"通知程序去处理。这使得一个线程可以轻松管理成千上万个并发连接。

二、四大核心组件拆解

一个标准的 Reactor 架构通常由以下四个核心角色构成:

1. Handle(句柄 / 资源)

操作系统提供的网络资源标识。在 Linux 下,它就是文件描述符(fd),比如监听套接字(Listen Socket)或普通通信套接字(Normal Socket)。

2. Synchronous Event Demultiplexer(同步事件多路复用器)

操作系统内核提供的机制,用于同时监听多个 Handle 上的事件。在 Linux 环境下,这通常是高效率的 epoll (或传统的 select/poll)。它通过 epoll_wait 阻塞等待,直到有一个或多个 Handle 就绪。

3. Initiation Dispatcher(初始分发器 / 反应器核心)

这就是 Reactor 本体。它提供注册、删除 Handle 的接口(如 AddConnection)。它内部维护着一个死循环(Event Loop),不断调用多路复用器获取就绪事件,然后将事件分发(Dispatch)给对应的处理器。

4. Event Handler(事件处理器)

定义了处理特定事件的具体逻辑。在 C++ 中,这通常被抽象为一个拥有读、写、异常回调函数(如 recv_cb_, send_cb_)的 Connection 对象。开发者通过 std::bind 等技术将具体的业务逻辑(如 AccepterRecver)绑定到这些 Handler 上。

三、经典运行流程(生命周期)

Reactor 模式运转起来就像一个精密咬合的齿轮,通常遵循以下流水线:

  1. 注册阶段 :服务器启动,创建 Listen fd。将其封装为 Connection 对象,绑定好 Accepter 回调,并将其挂载到 epoll 上,监听 EPOLLIN 事件。

  2. 事件循环 :Reactor 启动 while(true) 循环,调用 epoll_wait,线程挂起等待。

  3. 新连接到来 :客户端发起连接,Listen fd 触发可读事件。Reactor 被唤醒,识别到该 fd,调起其绑定的 Accepter 回调。

  4. 派生与绑定Accepter 接收新连接生成新的 fd,将其设置为非阻塞,封装为新的 Connection 对象,绑定好 RecverSender 回调,再次挂载到 epoll 上。

  5. 数据读写 :客户端发送数据,普通 fd 触发可读事件。Reactor 唤醒,调起 Recver 回调。回调函数中通过循环读取将数据读入应用层缓冲区,随后触发业务层的逻辑解析报文。

四、Reactor 的三大演进模型

根据业务场景的复杂度和对 CPU 的压榨程度,Reactor 演化出了三种经典形态:

1. 单 Reactor 单线程模型

  • 架构 :Reactor 里的事件分发(Dispatch)、新连接建立(Accept)、数据读写(Read/Write)以及业务逻辑处理(Process),全部在一个线程内完成

  • 优点:极简,没有锁竞争,没有线程上下文切换的开销,代码对缓存极其友好。

  • 缺点 :如果业务逻辑中包含耗时操作(如复杂的 JSON 解析、数据库查询),会卡住整个线程,导致 epoll_wait 无法被调用,瞬间造成其他所有客户端的连接和数据得不到响应。

  • 典型代表Redis(在 6.0 之前,其单机抗下十几万 QPS 的核心就是纯正的单 Reactor 单线程,因为它全是内存级别极快的业务操作)。

2. 单 Reactor 多线程模型

  • 架构 :Reactor 线程专职搞网络。它只负责监听事件、接收连接、把网络数据 read 出来。一旦数据读取完毕,Reactor 绝不自己处理业务,而是把数据打包扔给后端的工作线程池(Worker Thread Pool) 。线程池处理完后,再把结果交还给 Reactor 线程进行 write

  • 优点:充分利用了多核 CPU,业务阻塞不再影响网络的收发。

  • 缺点:在面对极其夸张的并发(如瞬间涌入几十万连接和高频发包)时,单线程的 Reactor 即使只做 I/O,也可能成为 CPU 瓶颈。

3. 主从 Reactor 多线程模型 (Multi-Reactor)

  • 架构:终极杀器。将 Reactor 拆分:

    • MainReactor :通常只有一个线程,只负责监听 Listen fd,专门处理新连接的接入。

    • SubReactor:通常有多个(数量等于 CPU 核心数)。MainReactor 接客完毕后,将新的 fd 直接扔给某个 SubReactor。SubReactor 负责这个连接后续所有的网络读写。

    • Worker Thread Pool:后端依然挂载线程池处理耗时业务。

  • 优点:职责极致分离,MainReactor 绝不被读写拖慢,并发连接接入能力极强;SubReactor 平摊了网络 I/O 压力,性能达到巅峰。

  • 典型代表Nginx (多进程架构的变种)、Java 高性能网络库 Netty

五、Reactor 模式的规则

如果想手写一个工业级的 Reactor,必须严守以下规则:

  1. 必须使用非阻塞 I/O (Non-blocking I/O)

    特别是配合 epoll 的边缘触发(ET)模式时。因为 ET 模式要求每次可读事件必须循环 read 直到返回 EAGAINEWOULDBLOCK。如果使用阻塞 fd,最后一次 read 会直接把整个 Reactor 线程永久卡死。

  2. 应用层缓冲区的绝妙配合

    Reactor 绝不能直接对套接字进行业务级操作。必须为每个连接抽象出独立的 in_bufferout_buffer。读事件触发时,只管往 in_buffer 里灌水,交由上层去解决粘包/半包问题;写数据时,也是将数据放入 out_buffer,注册写事件,由 Reactor 在内核允许时一点点吐出去。

  3. 彻底的解耦

    Reactor 核心(如 Dispatcher)代码应当是完全死板的、固化的。它绝对不应该出现 if (fd == listen_fd) 这种业务级判断。所有的差异化行为,都必须通过函数指针、std::bind 或 Lambda 表达式作为回调注入进去。

六、基于 C++ 与 epoll ET 模式的 Reactor 架构剖析

1. 架构映射

1.1 句柄 -> fdSocket

在 Linux 中,一切皆文件。网络连接就是一个文件描述符(fd)。封装了 Sock 类来管理基础的 socket 创建、绑定和监听。

1.2 同步事件多路复用器 -> Epoller

封装了 Epoller 类,它是对 Linux底层 epoll API(epoll_create, epoll_ctl, epoll_wait)的面向对象封装。它负责在底层高效地阻塞等待事件的发生。

1.3 事件处理器 -> Connection

这是解耦的关键。不直接操作 fd,而是为每一个连接抽象出一个 Connection 类。

  • 私有缓冲区 :每个连接都有独立的 inbuffer_outbuffer_,用于解决 TCP 粘包/半包问题。

  • 回调函数 :内部存储了三个 std::functionrecv_cb_, send_cb_, except_cb_),定义了该连接在可读、可写、异常时该执行的具体动作。

1.4 核心分发器 -> TcpServer

这是整个框架的"心脏"。它维护了一个哈希表 unordered_map<int, std::shared_ptr<Connection>> connections_,用于统一管理所有连接。它的 Dispatcher() 函数负责从 Epoller 拉取就绪事件,并无脑调用 Connection 中绑定的回调函数进行事件分发。

2. 代码展示

2.1 事件处理器:连接的抽象 (Connection 类)

cpp 复制代码
// 位于 TcpServer.hpp
using callback_t = std::function<void(std::shared_ptr<Connection>)>;

class Connection
{
public:
    Connection(int fd, TcpServer *tcp_server_ptr) 
        : fd_(fd), tcp_server_ptr_(tcp_server_ptr) {}

    ~Connection() {}

    // 注册特定事件发生时的回调函数(读、写、异常)
    void Register(callback_t recv_cb, callback_t send_cb, callback_t except_cb)
    {
        recv_cb_ = recv_cb;
        send_cb_ = send_cb;
        except_cb_ = except_cb;
    }

    int SockFd() { return fd_; }
    void AppendInBuffer(const std::string &info) { inbuffer_ += info; }
    void AppendOutBuffer(const std::string &info) { outbuffer_ += info; }
    std::string &Inbuffer() { return inbuffer_; }
    std::string &OutBuffer() { return outbuffer_; }

private:
    int fd_;
    std::string inbuffer_;  // 应用层接收缓冲区(解决粘包/半包)
    std::string outbuffer_; // 应用层发送缓冲区(配合 EPOLLOUT)

public:
    callback_t recv_cb_;
    callback_t send_cb_;
    callback_t except_cb_;

    TcpServer *tcp_server_ptr_; // 回指指针,方便在回调中操作 Server
};

2.2 多路复用器:底层事件监控 (Epoller 类)

cpp 复制代码
// 位于 Epoller.hpp
class Epoller
{
public:
    Epoller()
    {
        _epfd = epoll_create(704);
        if (_epfd == -1) lg(Error, "epoll_create error");
    }
    ~Epoller() { close(_epfd); }

    // 管理红黑树上的节点 (ADD, MOD, DEL)
    int EpollerUpdate(int fd, int op, uint32_t event)
    {
        int n = 0;
        if (op == EPOLL_CTL_DEL)
        {
            n = epoll_ctl(_epfd, op, fd, 0); // 删除操作不需要关心 event
            close(fd); // 从 epoll 移除后关闭底层文件描述符
        }
        else
        {
            struct epoll_event ev;
            ev.data.fd = fd;
            ev.events = event;
            n = epoll_ctl(_epfd, op, fd, &ev);
        }
        return n;
    }

    // 阻塞等待就绪事件
    int EpollerWait(struct epoll_event revents[], int num)
    {
        return epoll_wait(_epfd, revents, num, 1000);
    }
private:
    int _epfd;
};

2.3 Reactor 核心分发器 (TcpServer 类核心逻辑)

cpp 复制代码
// 位于 TcpServer.hpp
class TcpServer
{
public:
    // ... 构造函数与 Init() 等基础代码 ...

    // 【核心】向 Reactor 注册新的连接与事件
    void AddConnection(int fd, uint32_t events, callback_t recv_cb, callback_t send_cb, callback_t except_cb)
    {
        SetNonBlock(fd); // ET模式下的 fd 一定要设置为非阻塞
        std::shared_ptr<Connection> new_connection = std::make_shared<Connection>(fd, this);
        new_connection->Register(recv_cb, send_cb, except_cb);
        
        connections_[fd] = new_connection; // 加入统一管理
        epoller_ptr_->EpollerUpdate(fd, EPOLL_CTL_ADD, events); // 挂载到 epoll
    }

    // 【核心】事件分发器:完美消灭了 if(fd == listensock) 的硬编码
    void Dispatcher()
    {
        int n = epoller_ptr_->EpollerWait(rev, nums);
        for (int i = 0; i < n; i++)
        {
            uint32_t event = rev[i].events;
            int fd = rev[i].data.fd;

            if (event & EPOLLERR || event & EPOLLHUP) 
                event |= (EPOLLIN | EPOLLOUT); // 异常转化为读写事件处理

            // 触发多态回调,彻底解耦
            if ((event & EPOLLIN) && IsConnectionSafe(fd))
                if (connections_[fd]->recv_cb_) connections_[fd]->recv_cb_(connections_[fd]);
            
            if ((event & EPOLLOUT) && IsConnectionSafe(fd))
                if (connections_[fd]->send_cb_) connections_[fd]->send_cb_(connections_[fd]);
        }
    }

    // 【核心】连接接入(监听套接字绑定的回调)
    void Accept(std::shared_ptr<Connection> connection)
    {
        while (true) // ET 模式必须循环 accept 直到 EWOULDBLOCK
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int sockfd = ::accept(connection->SockFd(), (struct sockaddr *)&peer, &len);
            if (sockfd > 0)
            {
                // 为新客户端注册读写异常回调
                AddConnection(sockfd, EVENT_IN,
                              std::bind(&TcpServer::Recver, this, std::placeholders::_1),
                              std::bind(&TcpServer::Sender, this, std::placeholders::_1),
                              std::bind(&TcpServer::Excepter, this, std::placeholders::_1));
            }
            else
            {
                if (errno == EWOULDBLOCK) break;
                else if (errno == EINTR) continue;
                else break;
            }
        }
    }

    // 【核心】非阻塞读取
    void Recver(std::shared_ptr<Connection> connection)
    {
        int sockfd = connection->SockFd();
        while (true)
        {
            char buffer[128];
            memset(buffer, 0, sizeof(buffer));
            ssize_t n = recv(sockfd, buffer, sizeof(buffer) - 1, 0); 
            if (n > 0)
            {
                connection->AppendInBuffer(buffer); // 读取数据到应用层缓冲区
            }
            else if (n == 0) // 客户端断开
            {
                connection->except_cb_(connection);
                return;
            }
            else
            {
                if (errno == EWOULDBLOCK) break; // 读干净了
                else if (errno == EINTR) continue;
                else { connection->except_cb_(connection); return; }
            }
        }
        // 读取完毕,将组装好的数据交给上层业务逻辑(解耦网络与业务)
        OnMessage_(connection); 
    }

    // 【核心】非阻塞异步发送
    void Sender(std::shared_ptr<Connection> connection)
    {
        auto &outbuffer = connection->OutBuffer();
        while (true)
        {
            ssize_t n = send(connection->SockFd(), outbuffer.c_str(), outbuffer.size(), 0);
            if (n > 0)
            {
                outbuffer.erase(0, n);
                if (outbuffer.empty()) break; // 发完了
            }
            else if (n == 0) return;
            else
            {
                if (errno == EWOULDBLOCK) break; // 内核缓冲区满了,等待下一次 EPOLLOUT
                else if (errno == EINTR) continue;
                else { connection->except_cb_(connection); return; }
            }
        }
        // 动态管理写事件:还有数据未发送才开启 EPOLLOUT 关心,避免 CPU 空转
        if (!outbuffer.empty()) EnableEvent(connection->SockFd(), true, true);
        else EnableEvent(connection->SockFd(), true, false);
    }
};

2.4 业务层解耦 (main.cc)

cpp 复制代码
#include <iostream>
#include <memory>
#include "TcpServer.hpp"
#include "Calculator.hpp"

Calculator calculator;

// 用户的业务逻辑注入:网络层在收到完整数据后会回调此函数
void DefaultOnMessage(std::shared_ptr<Connection> connection_ptr)
{
    std::cout << "上层得到了数据: " << connection_ptr->Inbuffer() << std::endl;
    
    // 1. 调用业务计算逻辑(协议解析与计算)
    std::string response_str = calculator.Handler(connection_ptr->Inbuffer()); 
    if (response_str.empty()) return;

    // 2. 将响应数据放入连接的发送缓冲区
    connection_ptr->AppendOutBuffer(response_str);

    // 3. 通知 Reactor 框架进行发送处理
    auto tcpserver = connection_ptr->tcp_server_ptr_;
    tcpserver->Sender(connection_ptr);
}

int main()
{
    // 将业务逻辑函数指针注册进 Reactor 服务器
    std::unique_ptr<TcpServer> tcpsvr(new TcpServer(8080, DefaultOnMessage));
    tcpsvr->Init();
    tcpsvr->Start(); // 启动死循环事件监听

    return 0;
}
相关推荐
小糖学代码2 小时前
计算机网络理论:2.物理层
网络·计算机网络
小鸡吃米…2 小时前
Python 中的并发 —— 进程池
linux·服务器·开发语言·python
星辰引路-Lefan2 小时前
全平台 Docker 部署 CPA(CLIProxyAPI Plus) 灵活定制指南 (Linux/Windows)——接入Codex
linux·windows·docker·ai·ai编程
历程里程碑2 小时前
40 UDP - 2 C++实现英汉词典查询服务
linux·开发语言·数据结构·c++·ide·c#·vim
程序设计实验室2 小时前
别再手动复制SSH公钥了,Linux服务器一键从GitHub快速导入公钥
linux
雾岛听蓝2 小时前
动静态库原理与ELF文件详解
linux·经验分享·笔记
枕布响丸辣2 小时前
Nginx 安全防护与 HTTPS 部署实战
linux·运维·服务器·系统安全
Vallelonga2 小时前
认识 Linux 终端
linux
Yan-英杰2 小时前
远程控制软件哪个安全?2026 ToDesk/向日葵/RayLink加密、隐私与防护全面对比评测
网络·人工智能·网络协议·tcp/ip·http