Reactor 模式详解
Reactor 是一种事件驱动的设计模式,特别适用于处理大量并发 I/O 操作的场景。在 Linux 系统中,Reactor 通常基于 epoll、select 或 poll 等 I/O 多路复用机制实现。下面从概念、组件、工作流程、实现示例、优缺点以及与 Proactor 的对比等方面进行详细说明。
1. 什么是 Reactor 模式
Reactor 模式的核心思想是:将服务端对请求的处理过程分为"事件等待"和"事件处理"两个阶段。一个或多个输入源(如 socket 连接)同时将事件(可读、可写等)注册到一个中心调度器(Reactor)上,Reactor 使用 I/O 多路复用技术监听这些事件,当事件发生时,将事件分发给预先注册的事件处理器(Handler)执行。
典型应用场景
- 网络服务器(如 Nginx、Redis、Netty 等)
- 图形用户界面事件循环
- 任何需要高并发 I/O 处理的系统
2. Reactor 在 Linux 中的实现基础
Linux 提供了多种 I/O 多路复用机制:
- select:最古老,有最大文件描述符限制(通常 1024),效率随描述符数量线性下降。
- poll:无固定上限,但仍需遍历所有描述符。
- epoll:Linux 特有的高效机制,采用事件驱动,仅返回就绪的描述符,支持边缘触发(ET)和水平触发(LT),适合高并发场景。
现代 Linux 下的 Reactor 实现几乎都基于 epoll,因此下文重点以 epoll 为例。
3. Reactor 的核心组件
|----------------------------------|-------------------------------------------------------|
| 组件 | 说明 |
| 事件源(Event Source) | 通常是文件描述符(fd),如监听 socket、已连接 socket、定时器 fd 等。 |
| 事件多路分发器(Event Demultiplexer) | 即 I/O 多路复用函数(epoll_wait),负责等待事件发生。 |
| 反应器(Reactor) | 事件循环的调度器,负责注册/注销事件,并调用 demultiplexer 等待事件,然后分发到对应处理器。 |
| 事件处理器(EventHandler) | 定义处理事件的方法(如 handle_input、handle_output),通常由用户实现。 |
4. Reactor 的工作流程
- 初始化 Reactor:创建 epoll 实例,初始化事件循环。
- 注册事件:将需要监听的 fd(如 listen socket)及其对应的处理器注册到 Reactor。
- 事件循环:
-
- 调用
epoll_wait等待事件就绪。 - 对于每个就绪的事件,根据 fd 找到对应的 EventHandler。
- 调用该 Handler 的特定方法(如
handle_read、handle_write)处理事件。
- 调用
- 事件处理:在 Handler 中执行具体业务逻辑(如接受新连接、读取数据、发送响应等),处理过程中可能向 Reactor 注册新的事件(例如读完后注册写事件)。
5. Reactor 的变体
根据事件处理线程模型的不同,Reactor 通常有三种变体:
5.1 单 Reactor 单线程
- 所有事件(accept、read、write、业务处理)都在一个线程中完成。
- 实现简单,但无法利用多核 CPU,且业务处理可能阻塞 I/O 循环。
5.2 单 Reactor 多线程
- Reactor 线程负责事件分发,具体业务处理(如解码、计算)交给线程池。
- 需要注意线程安全,适合计算密集型场景。
5.3 多 Reactor 多线程(主从 Reactor)
- 主 Reactor 只负责处理新连接的 accept,将已连接 socket 分配给子 Reactor。
- 子 Reactor 负责处理该连接上的读写事件,每个子 Reactor 可运行在独立线程中。
- 这是 Nginx、Netty 等高性能服务器的典型架构。
6. Reactor 的优缺点
优点
- 高并发:单线程可处理成千上万连接,避免了多线程切换开销。
- 可扩展性:可以通过增加 Reactor 线程数(多 Reactor)或工作线程池来提升处理能力。
- 模块化:事件与处理分离,易于维护和测试。
- 非阻塞:配合非阻塞 I/O,不会因为某个连接的慢速 I/O 阻塞整个系统。
缺点
- 编程复杂:需要将业务逻辑拆分为异步回调,容易陷入"回调地狱"。
- 调试困难:异步流程难以用传统单步调试方式跟踪。
- 不适合 CPU 密集型任务:如果业务处理耗时,会阻塞事件循环,此时应使用线程池或专用线程。
代码
Mutex.hpp
#pragma once
#include <iostream>
#include <mutex>
#include <pthread.h>
class Mutex
{
public:
Mutex()
{
pthread_mutex_init(&_lock, nullptr);
}
void Lock()
{
pthread_mutex_lock(&_lock);
}
void Unlock()
{
pthread_mutex_unlock(&_lock);
}
pthread_mutex_t *Get()
{
return &_lock;
}
~Mutex()
{
pthread_mutex_destroy(&_lock);
}
private:
pthread_mutex_t _lock;
};
class LockGuard
{
public:
LockGuard(Mutex *_mutex):_mutexp(_mutex)
{
_mutexp->Lock();
}
~LockGuard()
{
_mutexp->Unlock();
}
private:
Mutex *_mutexp;
};
Logger.hpp
#pragma once
#include <iostream>
#include <string>
#include <filesystem> // C++17 文件操作
#include <fstream>
#include <ctime>
#include <unistd.h>
#include <memory>
#include <sstream>
#include "Mutex.hpp"
// 规定出场景的日志等级
enum class LogLevel
{
DEBUG,
INFO,
WARNING,
ERROR,
FATAL
};
// 日志转换成为字符串
std::string Level2String(LogLevel level)
{
switch (level)
{
case LogLevel::DEBUG:
return "Debug";
case LogLevel::INFO:
return "Info";
case LogLevel::WARNING:
return "Warning";
case LogLevel::ERROR:
return "Error";
case LogLevel::FATAL:
return "Fatal";
default:
return "Unknown";
}
}
// 根据时间戳,获取可读性较强的时间信息
// 20XX-08-04 12:27:03
std::string GetCurrentTime()
{
// 1. 获取时间戳
time_t currtime = time(nullptr);
// 2. 如何把时间戳转换成为20XX-08-04 12:27:03
struct tm currtm;
localtime_r(&currtime, &currtm);
// 3. 转换成为字符串
char timebuffer[64];
snprintf(timebuffer, sizeof(timebuffer), "%4d-%02d-%02d %02d:%02d:%02d",
currtm.tm_year + 1900,
currtm.tm_mon + 1,
currtm.tm_mday,
currtm.tm_hour,
currtm.tm_min,
currtm.tm_sec);
return timebuffer;
}
// 策略模式,策略接口
// 1. 刷新的问题 -- 假设我们已经有了一条完整的日志,string->设备(显示器,文件)
// 基类方法
class LogStrategy
{
public:
// 不同模式核心是刷新方式的不同
virtual ~LogStrategy() = default;
virtual void SyncLog(const std::string &logmessage) = 0;
};
// 控制台日志策略,就是日志只向显示器打印,方便我们debug
// 显示器刷新
class ConsoleLogStrategy : public LogStrategy
{
public:
~ConsoleLogStrategy()
{
}
void SyncLog(const std::string &logmessage) override
{
{
LockGuard lockguard(&_lock);
std::cout << logmessage << std::endl;
}
}
private:
// 显示器也是临界资源,保证输出线程安全
Mutex _lock;
};
// 默认路径和日志名称
const std::string logdefaultdir = "log";
const static std::string logfilename = "test.log";
// 文件日志策略
// 文件刷新
class FileLogStrategy : public LogStrategy
{
public:
// 构造函数,建立出来指定的目录结构和文件结构
FileLogStrategy(const std::string &dir = logdefaultdir,
const std::string filename = logfilename)
: _dir_path_name(dir), _filename(filename)
{
LockGuard lockguard(&_lock);
if (std::filesystem::exists(_dir_path_name))
{
return;
}
try
{
std::filesystem::create_directories(_dir_path_name);
}
catch (const std::filesystem::filesystem_error &e)
{
std::cerr << e.what() << "\r\n";
}
}
// 将一条日志信息写入到文件中
void SyncLog(const std::string &logmessage) override
{
{
LockGuard lockguard(&_lock);
std::string target = _dir_path_name;
target += "/";
target += _filename;
// 追加方式
std::ofstream out(target.c_str(), std::ios::app); // append
if (!out.is_open())
{
return;
}
out << logmessage << "\n"; // out.write
out.close();
}
}
~FileLogStrategy()
{
}
private:
std::string _dir_path_name; // log
std::string _filename; // hello.log => log/hello.log
Mutex _lock;
};
// 具体的日志类
// 1. 定制刷新策略
// 2. 构建完整的日志
class Logger
{
public:
Logger()
{
}
void EnableConsoleLogStrategy()
{
_strategy = std::make_unique<ConsoleLogStrategy>();
}
void EnableFileLogStrategy()
{
_strategy = std::make_unique<FileLogStrategy>();
}
// 内部类,实现RAII风格的日志格式化和刷新
// 这个LogMessage,表示一条完整的日志对象
class LogMessage
{
public:
// RAII风格,构造的时候构建好日志头部信息
LogMessage(LogLevel level, std::string &filename, int line, Logger &logger)
: _curr_time(GetCurrentTime()),
_level(level),
_pid(getpid()),
_filename(filename),
_line(line),
_logger(logger)
{
// stringstream不允许拷贝,所以这里就当做格式化功能使用
std::stringstream ss;
ss << "[" << _curr_time << "] "
<< "[" << Level2String(_level) << "] "
<< "[" << _pid << "] "
<< "[" << _filename << "] "
<< "[" << _line << "]"
<< " - ";
_loginfo = ss.str();
}
// 重载 << 支持C++风格的日志输入,使用模版,表示支持任意类型
template <typename T>
LogMessage &operator<<(const T &info)
{
std::stringstream ss;
ss << info;
_loginfo += ss.str();
return *this;
}
// RAII风格,析构的时候进行日志持久化,采用指定的策略
~LogMessage()
{
if (_logger._strategy)
{
_logger._strategy->SyncLog(_loginfo);
}
}
private:
std::string _curr_time; // 日志时间
LogLevel _level; // 日志等级
pid_t _pid; // 进程pid
std::string _filename;
int _line;
std::string _loginfo; // 一条合并完成的,完整的日志信息
Logger &_logger; // 引用外部logger类, 方便使用策略进行刷新
};
// 故意拷贝,形成LogMessage临时对象,后续在被<<时,会被持续引用,
// 直到完成输入,才会自动析构临时LogMessage,至此也完成了日志的显示或者刷新
// 同时,形成的临时对象内包含独立日志数据
// 未来采用宏替换,进行文件名和代码行数的获取
LogMessage operator()(LogLevel level, std::string filename, int line)
{
return LogMessage(level, filename, line, *this);
}
~Logger()
{
}
private:
// 写入日志的策略
std::unique_ptr<LogStrategy> _strategy;
};
// 定义全局的logger对象
Logger logger;
// 使用宏,可以进行代码插入,方便随时获取文件名和行号
#define LOG(level) logger(level, __FILE__, __LINE__)
// 提供选择使用何种日志策略的方法
#define EnableConsoleLogStrategy() logger.EnableConsoleLogStrategy()
#define EnableFileLogStrategy() logger.EnableFileLogStrategy()
InetAddr.hpp
#pragma once
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#include <string>
#include "Logger.hpp"
using namespace std;
#define Conv(addr) ((struct sockaddr *)&addr)
class InetAddr
{
private:
void Net2Host()
{
_port = ntohs(_addr.sin_port);
// _ip = inet_ntoa(_addr.sin_addr);
char ipbuffer[64];
inet_ntop(AF_INET, &(_addr.sin_addr.s_addr), ipbuffer, sizeof(ipbuffer));
_ip = ipbuffer;
}
void Host2Net()
{
memset(&_addr, 0, sizeof(_addr));
_addr.sin_family = AF_INET;
_addr.sin_port = htons(_port);
// _addr.sin_addr.s_addr = inet_addr(_ip.c_str());
inet_pton(AF_INET, _ip.c_str(), &(_addr.sin_addr.s_addr));
}
public:
InetAddr(){}
// 默认ip为INADDR_ANY(0.0.0.0)
InetAddr(uint16_t port, const string ip = "0.0.0.0")
: _ip(ip),
_port(port)
{
Host2Net();
}
InetAddr(struct sockaddr_in &addr)
{
_addr = addr;
Net2Host();
}
void Init(const struct sockaddr_in peer)
{
_addr = peer;
Net2Host();
}
struct sockaddr *Addr()
{
return Conv(_addr);
}
string IP()
{
return _ip;
}
uint16_t Port()
{
return _port;
}
bool operator==(const InetAddr &addr)
{
return _ip == addr._ip && _port == addr._port;
}
socklen_t Length()
{
return sizeof(_addr);
}
string ToString()
{
return _ip + "-" + to_string(_port);
}
~InetAddr()
{
}
private:
// 网络风格地址
struct sockaddr_in _addr;
// 主机风格地址
string _ip;
uint16_t _port;
};
Socket.hpp
#pragma once
#include <iostream>
#include<stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <memory>
#include <functional>
#include "Logger.hpp"
#include "InetAddr.hpp"
#include"Utils.hpp"
using namespace std;
static int gbacklog = 16; // 设置默认的backlog
static const int gsockfd = -1;
// 设置错误码
enum
{
OK,
CREATE_ERR,
BIND_ERR,
LISTEN_ERR,
CONNECT_ERR
};
// 定义一个抽象类,父类中定义算法的骨架,将某些步骤的具体实现延迟到子类中
class Socket
{
public:
// 编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor,
// 所以基类的析构函数加了vialtual修饰,派生类的析构函数就构成重写
// 基类必须有虚析构函数
virtual ~Socket() {}
// 纯虚函数强制派生类重写虚函数
virtual void CreatSocket() = 0;
virtual void BindSocket(int port) = 0;
virtual void ListenSocket() = 0;
//virtual shared_ptr<Socket> Accept(InetAddr *clientaddr) = 0;
virtual int Accept(InetAddr *clientaddr) = 0;
virtual bool Connect(InetAddr &peer) = 0;
virtual int SockFd() = 0;
virtual void Close() = 0;
virtual ssize_t Recv(std::string *out) = 0;
virtual ssize_t Send(const std::string &in) = 0;
public:
void BuildListenSocketMethod(int port)
{
CreatSocket();
BindSocket(port);
ListenSocket();
}
void BuildClientSocketMethod()
{
CreatSocket();
}
};
class TcpSocket : public Socket
{
public:
TcpSocket() : _sockfd(gsockfd)
{
}
TcpSocket(int sockfd) : _sockfd(sockfd)
{
}
// override帮助用户检测是否重写
void CreatSocket() override
{
_sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (_sockfd < 0)
{
LOG(LogLevel::FATAL) << "creat socket error";
exit(CREATE_ERR);
}
//注意!!! Reactor(ET)模式下listensocket必须设为非阻塞
SetNonBlock(_sockfd);
LOG(LogLevel::DEBUG) << "creat socket success"<<" _sockfd:"<<_sockfd;
}
void BindSocket(int port) override
{
InetAddr local(port);
if (bind(_sockfd, local.Addr(), local.Length()) != 0)
{
LOG(LogLevel::FATAL) << "bind socket error";
exit(BIND_ERR);
}
LOG(LogLevel::DEBUG) << "bind socket success";
}
void ListenSocket() override
{
if (listen(_sockfd, gbacklog) != 0)
{
LOG(LogLevel::FATAL) << "listen socket error";
exit(LISTEN_ERR);
}
LOG(LogLevel::DEBUG) << "listen socket success";
}
int Accept(InetAddr *clientaddr) override
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int clientfd = accept(_sockfd, (sockaddr *)&peer, &len);
if (clientfd < 0)
{
LOG(LogLevel::FATAL) << "accept error";
perror("accept error");
return -1;
}
LOG(LogLevel::DEBUG)<<"accept success";
clientaddr->Init(peer);
return clientfd;
}
bool Connect(InetAddr &peer) override
{
if (connect(_sockfd, peer.Addr(), peer.Length()) != 0)
{
LOG(LogLevel::FATAL) << "connect error";
return false;
}
LOG(LogLevel::DEBUG) << "connect " << peer.ToString() << " success";
return true;
}
int SockFd() override
{
return _sockfd;
}
void Close() override
{
if (_sockfd >= 0)
close(_sockfd);
}
// 今天重点放在读,通过读,理解序列和反序列和自定义协议的过程
ssize_t Recv(std::string *out) override
{
// 只读一次
char buffer[1024];
ssize_t n = recv(_sockfd, buffer, sizeof(buffer) - 1, 0);
if (n > 0)
{
buffer[n] = 0;
*out += buffer; // 故意+=
}
return n;
}
ssize_t Send(const std::string &in)
{
// send 用于 已建立连接 的套接字(典型如 TCP),数据将自动发送给连接的对端。
// sendto 主要用于 无连接 的套接字(典型如 UDP),每次发送都需要明确指定目标地址。
return send(_sockfd, in.c_str(), in.size(), 0);
}
~TcpSocket() {}
private:
int _sockfd;
};
Connection.hpp
#pragma once
#include <string>
#include "InetAddr.hpp"
//头文件需要交叉包含时:只能一个头文件A包含B,如果B要用A里面的,只能声明
class Reactor;
using callback_t = std::function<std::string(std::string &inbuffer)>;
class Connection
{
public:
Connection() : _events(0), _owner(nullptr)
{
}
virtual void Recver() = 0;
virtual void Sender() = 0;
virtual void Excepter() = 0;
virtual ~Connection() {}
int Sockfd() { return _sockfd; }
void SetSocketfd(int sockfd) { _sockfd = sockfd; }
void SetEvents(uint32_t events) { _events = events; }
uint32_t Events() { return _events; }
void SetAddr(const InetAddr &addr) { _peer = addr; }
Reactor *Owner() { return _owner; }
void SetOwner(Reactor *r) { _owner = r; }
void SetCallback(callback_t cb) { _cb = cb; }
protected:
int _sockfd;
uint32_t _events;
string _inbuffer;
string _outbuffer;
InetAddr _peer;
Reactor *_owner; // 回指指针,方便我们添加或则使用Reactor中的方法 !
callback_t _cb;
};
Reactor.hpp
#pragma once
#include "Socket.hpp"
#include "Epoller.hpp"
#include "Connection.hpp"
static const int gsize = 128;
class Reactor
{
private:
bool IsExist(std::shared_ptr<Connection> &connection)
{
auto iter = _connections.find(connection->Sockfd());
return iter != _connections.end();
}
bool IsExist(int sockfd)
{
auto iter = _connections.find(sockfd);
return iter != _connections.end();
}
public:
Reactor() : _epoller(make_unique<Epoller>())
{
}
void AddConnection(shared_ptr<Connection> connection)
{
if (IsExist(connection))
{
LOG(LogLevel::DEBUG) << "This connection is already in the reactor";
return;
}
// 1.让connection回指当前reactor
connection->SetOwner(this);
// 2.添加到_connections中
_connections[connection->Sockfd()] = connection;
// 3.写到内核中,epoller
_epoller->AddEvent(connection->Sockfd(), connection->Events());
LOG(LogLevel::DEBUG) << "this connection add to reactor";
}
void DelConnection(int sockfd)
{
if (!IsExist(sockfd))
{
LOG(LogLevel::WARNING) << sockfd << " conn not in Reactor[delete]!";
return;
}
// 1. 从内核中移除对该sockfd的关心
_epoller->DelEvent(sockfd);
// 2. _connections移除到kv
_connections.erase(sockfd);
// 3. close关闭fd
close(sockfd);
LOG(LogLevel::INFO) << "remove conn " << sockfd << " from reactor success";
}
// 使能读写
void EnableReadWrite(int sockfd, bool enableread, bool enablewrite)
{
if (!IsExist(sockfd))
{
LOG(LogLevel::WARNING) << sockfd << " conn not in Reactor, bug!";
return;
}
// 1. 修改connection对象
uint32_t events = (enableread ? EPOLLIN : 0) | (enablewrite ? EPOLLOUT : 0) | EPOLLET;
_connections[sockfd]->SetEvents(events);
// 2. 写透到内核
// _epoller->ModEvent(_connections[sockfd]->Sockfd(), _connections[sockfd]->Events());
_epoller->ModEvent(sockfd, events);
}
//LoopOnce处理一批就绪的事件
void LoopOnce(int timeout)
{
int n = _epoller->WaitEvent(_ready, gsize, timeout);
for (int i = 0; i < n; i++)
{
int sockfd = _ready[i].data.fd; // 就绪的sockfd
uint32_t events = _ready[i].events;
// 统一报错的处理方法!
//EPOLLHUP:文件描述符被挂断->对端关闭连接、管道写端关闭、终端挂断
if (events & EPOLLHUP)
events = (EPOLLIN | EPOLLOUT);
//EPOLLERR:文件描述符发生错误
if (events & EPOLLERR)
events = (EPOLLIN | EPOLLOUT);
// 增加代码的鲁棒性!
if ((events & EPOLLIN) && IsExist(sockfd))
{
_connections[sockfd]->Recver();
}
if ((events & EPOLLOUT) && IsExist(sockfd))
{
_connections[sockfd]->Sender();
}
}
}
void Dispatcher()
{
int timeout = 3000;
while (true)
{
// 处理事件
LoopOnce(timeout);
// ShowConnection();
// CheckTimeOut();
// 连接管理
// CheckTimeOut();
}
}
~Reactor() {}
private:
unordered_map<int, shared_ptr<Connection>> _connections;
unique_ptr<Epoller> _epoller;
struct epoll_event _ready[gsize];
};
Epoller.hpp
#pragma once
#include<stdio.h>
#include <iostream>
#include <sys/epoll.h>
#include "Utils.hpp"
#include "Logger.hpp"
#include"Utils.hpp"
using namespace std;
class Epoller
{
private:
int OperEvent(int op,int fd,uint32_t events)
{
struct epoll_event ev;
ev.data.fd=fd;
ev.events=events;
//LOG(LogLevel::DEBUG)<<"Oper _epfd:"<<_epfd<<" fd:"<<fd;
return epoll_ctl(_epfd,op,fd,&ev);
}
public:
Epoller()
{
_epfd=epoll_create(1);
if(_epfd<0)
{
LOG(LogLevel::ERROR)<<"epoll_create error";
exit(-1);
}
LOG(LogLevel::DEBUG)<<"_epfd:"<<_epfd<<" epoll_create success";
}
void AddEvent(int fd,uint32_t events)
{
int n=OperEvent(EPOLL_CTL_ADD,fd,events);
if(n<0)
{
LOG(LogLevel::ERROR)<<fd<<":add "<<Event2String(events)<<"to eopller fail";
perror("add error");
return;
}
LOG(LogLevel::DEBUG)<<fd<<":add "<<Event2String(events)<<"to eopller success";
}
void DelEvent(int fd)
{
int n=epoll_ctl(_epfd,EPOLL_CTL_DEL,fd,nullptr);
if(n<0)
{
LOG(LogLevel::ERROR)<<fd<<":del "<<"from eopller fail";
return;
}
LOG(LogLevel::DEBUG)<<fd<<":del "<<"from eopller success";
}
void ModEvent(int fd,uint32_t events)
{
int n=OperEvent(EPOLL_CTL_MOD,fd,events);
if(n<0)
{
LOG(LogLevel::ERROR)<<fd<<":mod "<<Event2String(events)<<"from eopller fail";
return;
}
LOG(LogLevel::DEBUG)<<fd<<":mod "<<Event2String(events)<<"from eopller success";
}
int WaitEvent(struct epoll_event ready[],int maxevents,int timeout)
{
int n=epoll_wait(_epfd,ready,maxevents,timeout);
if(n>0)
{
LOG(LogLevel::DEBUG)<<n<<"个fd就绪";
}
else if(n==0)
{
LOG(LogLevel::DEBUG)<<"timeout";
}
else
{
LOG(LogLevel::ERROR)<<"epoll wait fail";
}
return n;
}
~Epoller()
{
if(_epfd>=0)
close(_epfd);
}
private:
int _epfd;
};
Channel.hpp
#pragma once
#include"Epoller.hpp"
#include"Connection.hpp"
static const int buffersize = 1024;
class Channel:public Connection
{
public:
Channel(int sockfd, InetAddr &addr)
{
SetSocketfd(sockfd);
SetAddr(addr);
SetEvents(EPOLLIN | EPOLLET);
}
void Recver() override
{
while (true)
{
char buffer[buffersize];
ssize_t n = recv(_sockfd, buffer, sizeof(buffer) - 1, 0); // 非阻塞的
if (n > 0)
{
buffer[n] = 0;
_inbuffer += buffer;
}
else if (n == 0)
{
LOG(LogLevel::INFO) << "client quit, client is : " << _peer.ToString();
Excepter();
return;// 注意这里是return
}
else
{
if (errno == EAGAIN)
{
break;
}
else if (errno == EINTR)
{
continue;
}
else
{
LOG(LogLevel::INFO) << "recv error, client is : " << _peer.ToString();
Excepter();
return; // 注意这里是return
}
}
}
//我一定把本轮数据全部读完了!
// 缓冲区内部有没有完整的报文??不知道,不应该知道!
// 谁知道,谁应该知道??协议知道!
_outbuffer += _cb(_inbuffer);
std::cout << "_outbuffer: " << _outbuffer << std::endl;
// 如何发送??方案二:使能写关心!
if(!_outbuffer.empty())
Owner()->EnableReadWrite(_sockfd, true, true);
// 如何发送??方案一:最佳实践,直接发!
// if(!_outbuffer.empty())
// Sender();
}
void Sender() override
{
while(true)
{
ssize_t n = send(_sockfd, _outbuffer.c_str(), _outbuffer.size(), 0);
if(n > 0)
{
_outbuffer.erase(0, n);
if(_outbuffer.size() == 0)
break;
}
else if(n == 0)
{
break;
}
else if( n < 0 )
{
if(errno == EAGAIN)
{
break;
}
else if(errno == EINTR)
{
continue;
}
else{
Excepter();
return;
}
}
}
if(!_outbuffer.empty())
{
//2. 缓冲区被写满了,需要对端读取一部分数据后才能继续发
// 开启sockfd对写事件的关心!
Owner()->EnableReadWrite(_sockfd, true, true);
}
else
{
//1. 发完了
// 关闭对sockfd写事件的关心
Owner()->EnableReadWrite(_sockfd, true, false);
}
}
void Excepter() override
{
// 统一错误处理!
Owner()->DelConnection(_sockfd);
}
~Channel(){}
};
Listener.hpp
#pragma once
#include "Socket.hpp"
#include"Reactor.hpp"
#include "Connection.hpp"
#include"Channel.hpp"
#include"Utils.hpp"
class Listener : public Connection
{
public:
Listener(int port) : _listensocket(make_unique<TcpSocket>()), _port(port)
{
_listensocket->BuildListenSocketMethod(_port);
SetSocketfd(_listensocket->SockFd());
SetEvents(EPOLLET | EPOLLIN);
}
void Recver()override
{
// 获取新连接,非阻塞循环获取,因为我们是ET模式,要保证本次获取连接,都必须处理完
while (true)
{
InetAddr clientaddr;
int error = 0;
//因为listensocket设置为非阻塞,而accept又一直在循环调用
//所以出现accept success后在出现accept error很正常因为没有新的连接获取了
//"Resource temporarily unavailable" 是操作系统返回的错误,
//对应错误码 EAGAIN(或 EWOULDBLOCK,在许多系统上值相同)。它表示当前资源暂时不可用,请稍后重试
int sockfd = _listensocket->Accept(&clientaddr);
if(sockfd < 0)
{
//EAGAIN:资源暂时不可用,请稍后重试,系统资源不足
if(errno == EAGAIN)
break;
//EINTR:系统调用被信号中断
else if(errno == EINTR)
continue;
else
break;
}
// 获取新链接成功, 能直接读取吗?不能!应该干什么?添加到Reactor中!
// 1. 构建新的连接
SetNonBlock(sockfd);//设为非阻塞
std::shared_ptr<Connection> conn = std::make_shared<Channel>(sockfd, clientaddr);
conn->SetCallback(_cb);
// 2. 添加到Reactor中?
Owner()->AddConnection(conn);
}
}
void Sender()override
{
//.....
}
void Excepter()override
{
//....
}
~Listener() {}
private:
unique_ptr<Socket> _listensocket;
uint16_t _port;
};
Protocol.hpp
#pragma once
#include <iostream>
#include <jsoncpp/json/json.h>
#include <string>
using namespace std;
// 协议就是双方约定好的结构化的数据
// Requst:客户端发送的请求
class Request
{
public:
Request()
{
_x = _y = _oper = 0;
}
// 对要发送的数据进行序列化
bool Serialize(string *out)
{
// 1. 构建 Json::Value 对象
Json::Value root;
root["x"] = _x;
root["y"] = _y;
root["oper"] = _oper;
// 2. 使用 StreamWriterBuilder
Json::StreamWriterBuilder writer; // StreamWriter 的工厂
writer["emitUTF8"] = true; // 允许输出UTF-8
*out = Json::writeString(writer, root);
if (out->empty())
return false;
return true;
}
// 对要接受的数据进行反序列化
bool Deserialize(string &s)
{
// 解析JSON 字符串
Json::Reader reader;
Json::Value root;
// 从字符串中读取JSON 数据
bool ret = reader.parse(s, root);
if (!ret)
return false;
_x = root["x"].asInt();
_y = root["y"].asInt();
_oper = root["oper"].asInt();
return true;
}
int X()
{
return _x;
}
int Y()
{
return _y;
}
char Oper()
{
return _oper;
}
~Request() {}
//不做private:,方便客户端访问
//private:
int _x;
int _y;
char _oper;
};
// Response:服务器的应答
class Response
{
public:
Response()
{
_result = _code = 0;
}
bool Serialize(string *out)
{
// 1. 构建 Json::Value 对象
Json::Value root;
root["result"] = _result;
root["code"] = _code;
// 2. 使用 StreamWriterBuilder
Json::StreamWriterBuilder writer; // StreamWriter 的工厂
writer["emitUTF8"] = true; // 允许输出UTF-8
*out = Json::writeString(writer, root);
if (out->empty())
return false;
return true;
}
// 对要接受的数据进行反序列化
bool Deserialize(string &s)
{
// 解析JSON 字符串
Json::Reader reader;
Json::Value root;
// 从字符串中读取JSON 数据
bool ret = reader.parse(s, root);
if (!ret)
return false;
_result = root["result"].asInt();
_code = root["code"].asInt();
return true;
}
void SetResult(int r)
{
_result = r;
}
void SetCode(int c)
{
_code = c;
}
void Print()
{
cout << _result << "[" << _code << "]" << endl;
}
~Response() {}
private:
int _result;
int _code; // 判断_result是否可信
};
// 定义json中的分隔符
static const string sep = "\r\n";
class Protocol
{
public:
// 包装jsonstr便于后续解析
// jsonstr -> len\r\njsonstr\r\n
static string Package(const string &jsonstr)
{
if (jsonstr.empty())
return string();
int jsonstr_len = jsonstr.size();
return to_string(jsonstr_len) + sep + jsonstr + sep;
}
// 检查长度字符串是否符合标准
static bool DigitSafeCheck(const std::string str)
{
for (int i = 0; i < str.size(); i++)
{
if (!(str[i] >= '0' && str[i] <= '9'))
return false;
}
return true;
}
// 收到的报文有任意种可能
// len\r\njsonstr\r\n
// len\r\njsonstr\r\nlen\r\njsonstr\r\n
// len\r\njsonstr\r\nlen\r
// len\r\n
// len\r\njs
// len\r
// len
// origin_str: 从网络中读取上来的字符串,输入输出
// package: 输出参数,如果有完整的json报文,就返回
// UnPack对收到的报文进行解析以获得完整的json字符串
static int UnPack(string &origin_str, string *package)
{
//解析工作完成时返回jsonstr的长度,没有完成时返回0 解析出错返回-1
if (!package)
return -1;
auto pos=origin_str.find(sep);// 从左向右
if(pos==string::npos)
return 0;
//此时,我们至少获得了jsonstr的长度
string json_len=origin_str.substr(0,pos);
if(!DigitSafeCheck(json_len))
return -1;
int length=stoi(json_len);
// 如果我得到了当前报文的长度
// 根据协议,我可以推测出,一个完整报文的长度是多少
// len\r\njsonstr\r\n
int target_len=json_len.size()+sep.size()*2+length;
//如果没有一个完整的报文,就返回继续读,直至获得完整的的报文
if(origin_str.size()<target_len)
return 0;
////////////////////////////////////////////////////////////
//截止目前,可以确定origin_str内部,一定有一个完整的报文请求!且我们没有对origin_str做任何修改
*package=origin_str.substr(pos+sep.size(),length);
origin_str.erase(0, target_len);
return package->size();
}
};
Parse.hpp
#pragma once
#include <functional>
#include "Protocol.hpp"
#include "Logger.hpp"
using handler_t = function<Response(Request &req)>;
// 只负责对报文进行各种解析工作
class Parser
{
public:
Parser(handler_t handler)
: _handler(handler)
{
}
// 解析报文
string Parse(string &inbuffer)
{
LOG(LogLevel::DEBUG) << "inbuffer: \r\n"
<< inbuffer;
string send_str;
// 接受到的字符串可能包含多条报文请求
while (true)
{
std::string jsonstr;
// 1. 解析报文
int n = Protocol::UnPack(inbuffer, &jsonstr);
// 解析出错,直接退出
if (n < 0)
break;
// 未获得一条完整的报文或已经将inbuffer内所有完整报文处理完毕,退出循环
else if (n == 0)
break;
else
{
// 解析出的一条完整jsonstr
LOG(LogLevel::DEBUG) << "jsonstr: \r\n"
<< jsonstr;
Request req;
// 2. 反序列化
if (!req.Deserialize(jsonstr))
{
return string();
}
// 3. 根据response, 处理具体的业务
Response resp = _handler(req);
// 4. 对resp在进行序列化
string resp_json;
if (!resp.Serialize(&resp_json))
{
return string();
}
// 5. 打包
send_str += Protocol::Package(resp_json);
}
}
// 若未获得一条完整的报文,返回的是空串
// 若已经将inbuffer内所有完整报文处理完毕,返回的是打包好的全部答复
return send_str;
}
~Parser() {}
private:
handler_t _handler;
};
Utils.hpp
#pragma once
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <sys/epoll.h>
void SetNonBlock(int fd)
{
int fl = fcntl(fd, F_GETFL);
if (fl < 0)
{
return;
}
fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}
// EPOLLIN EPOLLOUT EPOLLET
std::string Event2String(uint32_t events)
{
std::string s;
if (events & EPOLLIN)
{
s = "EPOLLIN";
}
if (events & EPOLLOUT)
{
s += "|EPOLLOUT";
}
if (events & EPOLLET)
{
s += "|EPOLLET";
}
return s;
}
Calculator.hpp
#pragma once
#include "Protocol.hpp"
#include <iostream>
#include <string>
class Calculator
{
public:
Calculator() {}
//Request进,Response出
Response Exec(Request &req)
{
Response resp;
switch (req.Oper())
{
case '+':
resp.SetResult(req.X() + req.Y());
break;
case '-':
resp.SetResult(req.X() - req.Y());
break;
case '*':
resp.SetResult(req.X() * req.Y());
break;
case '/':
{
if (req.Y() == 0)
{
resp.SetCode(1); // 1:div 0
}
else
{
resp.SetResult(req.X() / req.Y());
}
}
break;
case '%':
{
if (req.Y() == 0)
{
resp.SetCode(2); // 2:mod 0
}
else
{
resp.SetResult(req.X() % req.Y());
}
}
break;
default:
resp.SetCode(3); // 3: 非法操作
break;
}
return resp;
}
~Calculator() {}
};
ReactorServer.cc
#include <iostream>
#include "Calculator.hpp"
#include "Protocol.hpp"
#include "Parser.hpp"
#include "Logger.hpp"
#include "Reactor.hpp"
#include "Listener.hpp"
void Usage(std::string proc)
{
std::cerr << "Usage: " << proc << " localport" << std::endl;
}
int main(int argc, char *argv[])
{
if (argc != 2)
{
Usage(argv[0]);
exit(0);
}
uint16_t serverport = std::stoi(argv[1]);
// 日志服务
EnableConsoleLogStrategy();
// 1. 业务对象
std::unique_ptr<Calculator> cal = std::make_unique<Calculator>();
// 2. 协议和解析协议对象
std::unique_ptr<Parser> parse_protocol = std::make_unique<Parser>(
[&cal](Request &req) -> Response
{
return cal->Exec(req);
});
// 3. 连接管理器 - Listener
std::shared_ptr<Connection> listener = std::make_shared<Listener>(serverport);
listener->SetCallback([&parse_protocol](std::string &inbuffer) -> std::string
{ return parse_protocol->Parse(inbuffer); });
// 4. 构建一个Reactor容器
std::unique_ptr<Reactor> R = std::make_unique<Reactor>();
// 给reactor中,把连接管理器添加到Reactor
R->AddConnection(listener);
// 启动Reactor
R->Dispatcher();
return 0;
}
Client.cpp
#include <iostream>
#include <string>
#include <memory>
#include "Socket.hpp"
#include "InetAddr.hpp"
#include "Protocol.hpp"
using namespace std;
void Usage(std::string proc)
{
std::cerr << "Usage: " << proc << " serverip serverport" << std::endl;
}
// ./Client serverip serverport
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
exit(0);
}
EnableConsoleLogStrategy();
string serverip = argv[1];
uint16_t serverport = stoi(argv[2]);
// 创建socket
unique_ptr<Socket> sockptr = make_unique<TcpSocket>();
sockptr->BuildClientSocketMethod();
InetAddr server(serverport, serverip);
// 连接
if (sockptr->Connect(server))
{
string inbuffer;
while (true)
{
// 1. 构建请求
Request req;
cout << "Please Enter X: ";
cin >> req._x;
cout << "Please Enter Y: ";
cin >> req._y;
cout << "Please Enter Operator: ";
cin >> req._oper;
// 2. 序列化
string jsonstr;
req.Serialize(&jsonstr);
cout << "jsonstr: \r\n"
<< jsonstr << endl;
// 3. 打包
// len\r\njsonstr\r\n
string sendstr = Protocol::Package(jsonstr);
cout << "sendstr:\r\n"
<< sendstr << endl;
// 4. 发送
sockptr->Send(sendstr);
// 5. 接收
sockptr->Recv(&inbuffer);
// 6. 报文解析
string package;
int n = Protocol::UnPack(inbuffer, &package);
if (n > 0)
{
Response resp;
// 7. 反序列化
bool r = resp.Deserialize(package);
if (r)
{
resp.Print();
}
}
}
}
return 0;
}


Reactor模式下为什么listen套接字没设置为非阻塞时服务器收不到消息,它设置里非阻塞和不设置非阻塞有什么区别?
在 Linux 的 Reactor 模式(基于 epoll 或 select)中,将监听套接字设置为非阻塞,并不是为了"让接收消息更快",而是为了防止事件循环被意外阻塞,导致服务器"假死"或"收不到后续的消息"。
要理解为什么"不设置非阻塞"可能导致"收不到消息",需要深入分析 Reactor 模式的工作机制。
1. 核心原因:Reactor 是单线程/单事件循环驱动的
在典型的 Reactor 模式中(如 Redis、Nginx 的某些阶段,或你自己编写的 epoll 服务器),通常只有一个线程在运行事件循环:
- 调用
epoll_wait等待事件。 - 当监听套接字(Listen Socket)有事件时,说明有新的客户端连接到达。
- 调用
accept()接受连接。 - 将新连接的客户端套接字加入事件监听。
问题的关键出在第 3 步 **accept()**上。
2. 阻塞(默认)模式下发生了什么?
如果监听套接字是阻塞 的,当 epoll_wait 返回说"监听套接字可读"时,你调用 accept()。
- 正常情况(99.9%): 内核的
accept队列里确实有一个连接。accept()立即取出连接,返回新的文件描述符,耗时极短,一切正常。 - 极端情况(边缘触发 + 多连接风暴):
如果你使用的是 epoll****的 EPOLLET**(边缘触发)模式** ,或者系统发生了特殊的竞争条件(例如:客户端在accept之前发起了RST包,或者使用了SO_ATTACH_FILTER等过滤机制),就会出现一种情况:内核通知你有连接,但当你调用 **accept()**时,队列里已经没有连接了。
这时,阻塞的 **accept()**会做什么?
它会将当前线程挂起(阻塞),直到下一个客户端连接进来。在 Reactor 模式下,这个线程就是整个服务器的"心脏"。一旦这个线程被挂起:
-
- 它无法再调用
epoll_wait。 - 它无法处理已经连接上的客户端发送的数据(收不到消息)。
- 它无法接收新的连接。
- 服务器完全卡死。
- 它无法再调用
这就是你遇到的"收不到消息"的根本原因。
3. 非阻塞模式下有什么区别?
当你将监听套接字设置为非阻塞 (fcntl(sock, F_SETFL, O_NONBLOCK)):
- 即使内核通知了可读事件,当你调用
accept()时,如果队列里没有连接(由于竞争条件或边缘触发漏处理),accept()不会阻塞 ,而是立即返回-1,并设置errno为EAGAIN或EWOULDBLOCK。 - Reactor 的应对: 代码检测到
EAGAIN,不会继续死等,而是直接返回主循环,继续调用epoll_wait。
-
- 这样,其他已连接客户端的读写事件就能被正常处理。
- 服务器虽然暂时没拿到新连接,但功能依然是正常的。
4. 为什么很多文章说"listen 可以不设置非阻塞"?
这是一个需要区分的点:
- 对于 select/poll****或者 epoll****的 水平触发(LT )模式:即使
accept返回了EAGAIN,下次epoll_wait还会立刻返回该事件。如果你忘了处理EAGAIN,可能会造成 busy loop (CPU 100%),但不会 阻塞主线程。因此,很多新手代码在LT模式下省略了设置非阻塞,也能跑通。 - 对于 epoll****的 边缘触发(ET )模式:必须 设置非阻塞。因为
ET只通知一次,你必须循环调用accept直到返回EAGAIN为止。如果accept是阻塞的,在循环最后一次调用时,队列已空,线程就会永久阻塞在accept上,导致灾难。
5. 总结对比表
|----------------|----------------------------------------------------|------------------------------------|
| 特性 | 阻塞 Listen Socket | 非阻塞 Listen Socket |
| 常规连接 | 正常工作 | 正常工作 |
| 高并发/ET模式 | 致命风险:当 accept 队列为空时,线程挂起,导致 Reactor 无法处理任何 I/O | 安全:返回 EAGAIN,事件循环继续运行 |
| 多线程 Accept | 惊群效应下,多个线程可能有一个被阻塞在内核 | 配合 SO_REUSEPORT或互斥锁,表现更稳健 |
| 编程复杂度 | 简单,无需处理 EAGAIN | 需要循环 accept 直到 EAGAIN,且需要正确设置非阻塞标志 |
结论
"收不到消息"的原因是:在 Reactor 模式下,如果监听套接字是阻塞的,一旦触发了罕见的竞争条件(边缘触发漏处理或惊群后的空队列), **accept()**会永久阻塞事件循环线程。事件循环停了,不仅收不到新消息,已有的连接消息也无法处理。
在 Reactor 模式中,"永不阻塞"是铁律 。因此,监听套接字必须设置为非阻塞 ,并配合循环调用 accept() 直到返回 EAGAIN,这样才能保证事件循环的高可用性。