目录
[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等技术将具体的业务逻辑(如Accepter、Recver)绑定到这些 Handler 上。
三、经典运行流程(生命周期)
Reactor 模式运转起来就像一个精密咬合的齿轮,通常遵循以下流水线:
注册阶段 :服务器启动,创建 Listen fd。将其封装为 Connection 对象,绑定好
Accepter回调,并将其挂载到epoll上,监听EPOLLIN事件。事件循环 :Reactor 启动
while(true)循环,调用epoll_wait,线程挂起等待。新连接到来 :客户端发起连接,Listen fd 触发可读事件。Reactor 被唤醒,识别到该 fd,调起其绑定的
Accepter回调。派生与绑定 :
Accepter接收新连接生成新的 fd,将其设置为非阻塞,封装为新的 Connection 对象,绑定好Recver和Sender回调,再次挂载到epoll上。数据读写 :客户端发送数据,普通 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,必须严守以下规则:
必须使用非阻塞 I/O (Non-blocking I/O)
特别是配合
epoll的边缘触发(ET)模式时。因为 ET 模式要求每次可读事件必须循环read直到返回EAGAIN或EWOULDBLOCK。如果使用阻塞 fd,最后一次read会直接把整个 Reactor 线程永久卡死。应用层缓冲区的绝妙配合
Reactor 绝不能直接对套接字进行业务级操作。必须为每个连接抽象出独立的
in_buffer和out_buffer。读事件触发时,只管往in_buffer里灌水,交由上层去解决粘包/半包问题;写数据时,也是将数据放入out_buffer,注册写事件,由 Reactor 在内核允许时一点点吐出去。彻底的解耦
Reactor 核心(如
Dispatcher)代码应当是完全死板的、固化的。它绝对不应该出现if (fd == listen_fd)这种业务级判断。所有的差异化行为,都必须通过函数指针、std::bind或 Lambda 表达式作为回调注入进去。
六、基于 C++ 与 epoll ET 模式的 Reactor 架构剖析
1. 架构映射
1.1 句柄 -> fd 与 Socket 类
在 Linux 中,一切皆文件。网络连接就是一个文件描述符(fd)。封装了
Sock类来管理基础的 socket 创建、绑定和监听。
1.2 同步事件多路复用器 -> Epoller 类
封装了
Epoller类,它是对 Linux底层epollAPI(epoll_create,epoll_ctl,epoll_wait)的面向对象封装。它负责在底层高效地阻塞等待事件的发生。
1.3 事件处理器 -> Connection 类
这是解耦的关键。不直接操作 fd,而是为每一个连接抽象出一个 Connection 类。
私有缓冲区 :每个连接都有独立的
inbuffer_和outbuffer_,用于解决 TCP 粘包/半包问题。回调函数 :内部存储了三个
std::function(recv_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;
}