
🔥承渊政道: 个人主页
❄️个人专栏: 《C语言基础语法知识》 《数据结构与算法》 《C++知识内容》 《Linux系统与网络知识》 《算法刷题指南》 《测评文章活动推广》 《大模型语言路线学习》 《MySQL数据库学习》 《Python知识内容》 《cpolar知识学习》
✨逆境不吐心中苦,顺境不忘来时路!✨ 🎬 博主简介:

在学习
select、poll和epoll之后,我们已经能够通过就绪通知管理多个连接.但从一个能够收发数据的事件循环,到结构清晰、便于扩展的网络服务器,还需要解决新的问题:新连接、读写和异常事件应该如何分发?不同连接的状态与缓冲区由谁管理?网络通信与业务逻辑又该如何分离? Reactor 反应堆模式,正是组织这些职责的一种设计思路.Reactor 以 "等待就绪事件 → 分发事件 → 执行对应处理逻辑" 为核心,将事件监听与具体处理分离,使连接接收、数据读写和异常处理能够围绕统一的事件循环展开.需要明确的是,Reactor 是一种事件驱动的设计模式,并不是某个系统调用,也不等同于异步 IO .就绪通知之后,程序仍需执行实际读写;如果在事件循环中执行耗时或阻塞操作,其他连接的响应仍会受到影响.因此,理解Reactor不仅要看事件如何流转,还要关注任务分工、连接生命周期与资源管理.本文将以 "事件驱动思想 → 核心组件 → 线程模型 → 代码实践" 为主线,梳理事件源、多路复用器、事件分发器与处理器之间的协作关系,对比单Reactor单线程、单Reactor多线程及主从 Reactor 的组织方式.随后结合epoll与非阻塞读写,逐步分析事件注册、回调分发、缓冲区管理和连接清理,将分散的网络接口组织成完整的服务器结构,为后续学习高并发网络服务与事件驱动框架打下基础.

目录
一、Reactor反应堆模式代码
Reactor模式是⼀种事件驱动的编程模式,⽤于⾼效地处理并发I/O操作.它通过⼀个或多个事件循环(Event Loop)来监听和处理各种事件(如⽹络请求、定时器事件等),从⽽实现⾼效的并发处
理,⽽⽆需为每个连接创建⼀个线程或进程.

cpp
//Accepter.hpp
#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <cerrno>
#include "Common.hpp"
#include "Log.hpp"
#include "Connection.hpp"
#include "HandlerConnection.hpp"
class Accepter // 连接管理器
{
public:
Accepter()
{
}
// ET模式,你怎么知道,只有一条连接到来了呢?
void AccepterConnection(/*this, */ Connection *conn) // conn: listensocket对应的conn
{
errno = 0;
while (true)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int sockfd = ::accept(conn->SockFd(), (struct sockaddr *)&peer, &len);
if (sockfd > 0)
{
lg.LogMessage(Info, "get a new link, sockfd is: %d\n", sockfd);
SetNonBlock(sockfd);
auto recver = std::bind(&HandlerConnection::Recver, std::placeholders::_1);
auto sender = std::bind(&HandlerConnection::Sender, std::placeholders::_1);
auto excepter = std::bind(&HandlerConnection::Excepter, std::placeholders::_1);
Connection *normal_conn = ConnectionFactory::BuildNormalConnection(sockfd,
recver, sender, excepter, EPOLLIN|EPOLLET, conn->_R);
conn->_R->AddConnection(normal_conn);
}
else
{
if (errno == EAGAIN)
break;
else if (errno == EINTR)
continue;
else
{
lg.LogMessage(Warning, "get a new link error\n");
break;
}
}
}
}
~Accepter()
{
}
};
cpp
//Calculate.hpp
#pragma once
#include <iostream>
#include <memory>
#include "Protocol.hpp"
// 业务
namespace CalCulateNS
{
enum
{
Success = 0,
DivZeroErr,
ModZeroErr,
UnKnowOper
};
class Calculate
{
public:
Calculate() {}
std::shared_ptr<Protocol::Response> Cal(std::shared_ptr<Protocol::Request> req)
{
std::shared_ptr<Protocol::Response> resp = factory.BuildResponse();
resp->SetCode(Success);
switch (req->GetOper())
{
case '+':
resp->SetResult(req->GetX() + req->GetY());
break;
case '-':
resp->SetResult(req->GetX() - req->GetY());
break;
case '*':
resp->SetResult(req->GetX() * req->GetY());
break;
case '/':
{
if (req->GetY() == 0)
{
resp->SetCode(DivZeroErr);
}
else
{
resp->SetResult(req->GetX() / req->GetY());
}
}
break;
case '%':
{
if (req->GetY() == 0)
{
resp->SetCode(ModZeroErr);
}
else
{
resp->SetResult(req->GetX() % req->GetY());
}
}
break;
default:
resp->SetCode(UnKnowOper);
break;
}
return resp;
}
~Calculate() {}
private:
Protocol::Factory factory;
};
} // namespace CalCulate
cpp
//Common.hpp
#pragma once
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <cstdlib>
#include <cerrno>
#include "Log.hpp"
// 对指定的fd设置非阻塞
void SetNonBlock(int fd)
{
int fl = fcntl(fd, F_GETFL);
if(fl < 0)
{
exit(0);
}
lg.LogMessage(Info, "set %d been non block\n", fd);
fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}
cpp
//Connection.hpp
#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
class Connection;
class TcpServer;
using func_t = std::function<void(Connection *)>;
class Connection
{
public:
Connection(int sockfd, uint32_t events, TcpServer *R)
: _sockfd(sockfd), _events(events), _R(R)
{
}
void RegisterCallback(func_t recver, func_t sender, func_t excepter)
{
_recver = recver;
_sender = sender;
_excepter = excepter;
}
void AddInBuffer(std::string buffer)
{
_inbuffer += buffer; // 追加到inbuffer中
}
void AddOutBuffer(const std::string &buffer)
{
_outbuffer += buffer;
}
bool OutBufferEmpty()
{
return _outbuffer.empty();
}
int SockFd()
{
return _sockfd;
}
uint32_t Events()
{
return _events;
}
void SetEvents(uint32_t events)
{
_events = events;
}
void SetClient(const struct sockaddr_in c)
{
_client = c;
}
std::string &InBuffer()
{
return _inbuffer;
}
std::string &OutBuffer()
{
return _outbuffer;
}
void Close()
{
::close(_sockfd);
}
~Connection()
{
}
private:
// 对应的sockfd
int _sockfd;
// 对应的缓冲区
std::string _inbuffer; // _sockfd 接受缓冲区, 暂时用string代替
std::string _outbuffer; // _sockfd 发送缓冲区
// 关心的事件
uint32_t _events;
// 维护一下client的ip和port信息
struct sockaddr_in _client;
public:
// 对特定connection进行处理的回调函数
func_t _recver;
func_t _sender;
func_t _excepter;
// TcpServer的回指指针 - TODO
TcpServer *_R;
};
class ConnectionFactory
{
public:
static Connection *BuildListenConnection(int listensock, func_t recver, uint32_t events, TcpServer *R)
{
Connection *conn = new Connection(listensock, events, R);
conn->RegisterCallback(recver, nullptr, nullptr);
return conn;
}
static Connection *BuildNormalConnection(int sockfd,
func_t recver,
func_t sender,
func_t excepter,
uint32_t events,
TcpServer *R)
{
Connection *conn = new Connection(sockfd, events, R);
conn->RegisterCallback(recver, sender, excepter);
return conn;
}
};
cpp
//Epoller.hpp
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <set>
#include <unistd.h>
#include <sys/epoll.h>
#include "Log.hpp"
namespace EpollerModule
{
const static int defualtepfd = -1;
const static int size = 128;
class Epoller
{
public:
Epoller() : _epfd(defualtepfd)
{
}
void InitEpoller()
{
_epfd = ::epoll_create(size);
if (defualtepfd == _epfd)
{
lg.LogMessage(Fatal, "epoll_create error, %s : %d\n", strerror(errno), errno);
exit(4);
}
lg.LogMessage(Info, "epoll_create success, epfd: %d\n", _epfd);
}
void AddEvent(int sockfd, uint32_t events)
{
// for test
fd_list.insert(sockfd);
struct epoll_event ev;
ev.events = events;
ev.data.fd = sockfd; // ??, 不是给内核的, 给用户的
int n = ::epoll_ctl(_epfd, EPOLL_CTL_ADD, sockfd, &ev);
if (n < 0)
{
lg.LogMessage(Error, "epoll_ctl add error, %s : %d\n", strerror(errno), errno);
}
}
void ModEvent(int sockfd, uint32_t events)
{
struct epoll_event ev;
ev.events = events;
ev.data.fd = sockfd;
int n = ::epoll_ctl(_epfd, EPOLL_CTL_MOD, sockfd, &ev);
if (n < 0)
{
lg.LogMessage(Error, "epoll_ctl mod error, %s : %d\n", strerror(errno), errno);
}
}
void DelEvent(int sockfd)
{
// for test
fd_list.erase(sockfd);
int n = ::epoll_ctl(_epfd, EPOLL_CTL_DEL, sockfd, nullptr);
if (n < 0)
{
lg.LogMessage(Error, "epoll_ctl del error, %s : %d\n", strerror(errno), errno);
}
}
void DebugFdList()
{
std::cout << "fd list is : ";
for (auto &fd : fd_list)
{
std::cout << fd << " ";
}
std::cout << std::endl;
}
int Wait(struct epoll_event *revs, int maxevents, int timeout)
{
int n = ::epoll_wait(_epfd, revs, maxevents, timeout);
return n;
}
~Epoller()
{
if (_epfd >= 0)
{
close(_epfd);
}
lg.LogMessage(Info, "epoll close success\n");
}
private:
int _epfd;
std::set<int> fd_list;
};
}
cpp
//HandlerConnection.hpp
#pragma once
#include <iostream>
#include <cerrno>
#include "Connection.hpp"
#include "Protocol.hpp"
#include "Calculate.hpp"
#include "Log.hpp"
using namespace Protocol;
using namespace CalCulateNS;
const static int buffer_size = 1024;
class HandlerConnection
{
public:
static void HandlerRequest(Connection *conn)
{
std::string &inbuffer = conn->InBuffer();
std::string message; // 表示一个符合协议的一个完整的报文
Calculate calulate; // 负责业务处理
Factory factory;
auto req = factory.BuildRequest();
// 1. 明确报文边界,解决粘报问题
while (Decode(inbuffer, &message))
{
// message 一定是一个完整的报文,符合协议的!
// 2. 反序列化
if (!req->Deserialize(message))
continue;
// 3. 业务处理
auto resp = calulate.Cal(req);
// 4. 对相应进行序列化
std::string responseStr;
resp->Serialize(&responseStr);
// 5. 封装完整报文
responseStr = Encode(responseStr);
// 6. 将应答全部追加到outbuffer中
conn->AddOutBuffer(responseStr);
}
// 考虑发送的问题了
if (!conn->OutBufferEmpty())
{
conn->_sender(conn); // 对写事件,直接发!!!--- 不代表能全部发完!
}
}
// 在这里读取的时候,我们关系数据是什么格式?协议是什么样子的吗?
// 不关心!!!我们只负责把本轮属于完全读取完毕 --- 把读到的字节流数据,交给上层 --- 由上层进行分析处理
static void Recver(Connection *conn)
{
errno = 0;
// 读取流程
char buffer[1024];
while (true)
{
ssize_t n = recv(conn->SockFd(), buffer, sizeof(buffer) - 1, 0); // 非阻塞读取
if (n > 0)
{
buffer[n] = 0;
conn->AddInBuffer(buffer);
}
else
{
// std::cout << "................... errno:" << errno << std::endl;
if (errno == EAGAIN)
break;
else if (errno == EINTR)
continue;
else
{
// 真正的读取错误
conn->_excepter(conn); // 直接回调自己的异常处理就可以了!
return;
}
}
}
std::cout << "sockfd# " << conn->SockFd() << ":\n"
<< conn->InBuffer() << std::endl;
// 尝试分析处理报文 -- 半个,一个半, 10个,11个半
HandlerRequest(conn);
}
static void Sender(Connection *conn)
{
errno = 0;
std::string &outbuffer = conn->OutBuffer();
while (true)
{
ssize_t n = send(conn->SockFd(), outbuffer.c_str(), outbuffer.size(), 0);
if (n >= 0)
{
outbuffer.erase(0, n); // 已经发给OS的,就直接移除了 // conn->remove(n);
if (outbuffer.empty())
break;
}
else
{
if (errno == EAGAIN)
break; // 只有这里,才会正常退出
else if (errno == EINTR)
continue;
else
{
conn->_excepter(conn);
return;
}
}
}
// 走到这里,意味着什么?我们本轮发满了,但是数据可能没发完,为什么没发完呢?
// 开启对conn->SockFd() EPOLLOUT的关心!!!!!, 如何开启对于特定一个connection对应的写事件关心呢???
if (!conn->OutBufferEmpty())
{
conn->_R->EnableReadWrite(conn->SockFd(), true, true);
}
else
{
conn->_R->EnableReadWrite(conn->SockFd(), true, false);
}
}
static void Excepter(Connection *conn)
{
lg.LogMessage(Info, "connection erase done, who: %d\n", conn->SockFd());
errno = 0;
// 从epoll中移除对conn->Sockfd的关心
// unordered_map 移除conn
conn->_R->RemoveConnection(conn->SockFd());
// 关闭conn->Sockfd
conn->Close();
// delete conn
delete conn;
}
};
cpp
//Log.hpp
#pragma once
#include <iostream>
#include <fstream>
#include <string>
#include <cstdarg>
#include <ctime>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
enum
{
Debug = 0,
Info,
Warning,
Error,
Fatal
};
enum
{
Screen = 10,
OneFile,
ClassFile
};
std::string LevelToString(int level)
{
switch (level)
{
case Debug:
return "Debug";
case Info:
return "Info";
case Warning:
return "Warning";
case Error:
return "Error";
case Fatal:
return "Fatal";
default:
return "Unknown";
}
}
const int defaultstyle = Screen;
const std::string default_filename = "log.";
const std::string logdir = "log";
class Log
{
public:
Log() : style(defaultstyle), filename(default_filename)
{
mkdir(logdir.c_str(), 0775);
}
void Enable(int sty) //
{
style = sty;
}
std::string TimeStampExLocalTime()
{
time_t currtime = time(nullptr);
struct tm *curr = localtime(&currtime);
char time_buffer[128];
snprintf(time_buffer, sizeof(time_buffer), "%d-%d-%d %d:%d:%d",
curr->tm_year + 1900, curr->tm_mon + 1, curr->tm_mday,
curr->tm_hour, curr->tm_min, curr->tm_sec);
return time_buffer;
}
void WriteLogToOneFile(const std::string &logname, const std::string &message)
{
umask(0);
int fd = open(logname.c_str(), O_CREAT | O_WRONLY | O_APPEND, 0666);
if(fd < 0) return;
write(fd, message.c_str(), message.size());
close(fd);
// std::ofstream out(logname);
// if (!out.is_open())
// return;
// out.write(message.c_str(), message.size());
// out.close();
}
void WriteLogToClassFile(const std::string &levelstr, const std::string &message)
{
std::string logname = logdir;
logname += "/";
logname += filename;
logname += levelstr;
WriteLogToOneFile(logname, message);
}
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
void WriteLog(const std::string &levelstr, const std::string &message)
{
pthread_mutex_lock(&lock);
switch (style)
{
case Screen:
std::cout << message;
break;
case OneFile:
WriteLogToClassFile("all", message);
break;
case ClassFile:
WriteLogToClassFile(levelstr, message);
break;
default:
break;
}
pthread_mutex_unlock(&lock);
}
void LogMessage(int level, const char *format, ...) // 类C的一个日志接口
{
char leftbuffer[1024];
std::string levelstr = LevelToString(level);
std::string currtime = TimeStampExLocalTime();
std::string idstr = std::to_string(getpid());
char rightbuffer[1024];
va_list args; // char *, void *
va_start(args, format);
// args 指向了可变参数部分
vsnprintf(rightbuffer, sizeof(rightbuffer), format, args);
va_end(args); // args = nullptr;
snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%s][%s] ",
levelstr.c_str(), currtime.c_str(), idstr.c_str());
std::string loginfo = leftbuffer;
loginfo += rightbuffer;
WriteLog(levelstr, loginfo);
}
// void operator()(int level, const char *format, ...)
// {
// LogMessage(int level, const char *format, ...)
// }
~Log() {}
private:
int style;
std::string filename;
};
Log lg;
class Conf
{
public:
Conf()
{
lg.Enable(Screen);
}
~Conf()
{}
};
Conf conf;
cpp
//Main.cc
#include "TcpServer.hpp"
#include "Socket.hpp"
#include "Accepter.hpp"
#include <memory>
#include <iostream>
using namespace NetWorkModule;
static const int gbacklog = 32;
void Usage(std::string process)
{
std::cerr << "Usage: " << process << " port" << std::endl;
}
void Test(Connection *conn)
{
std::cout << "get event, sockfd is : " << conn->SockFd() << std::endl;
}
// 这个代码是什么鬼???Reator : 反应堆模式,半同步半异步模式,Linux高并发服务器最常见的模式(没有之一)
// ./epoll_server 8888
int main(int argc, char *argv[])
{
if (argc != 2)
{
Usage(argv[0]);
return 1;
}
int port = std::stoi(argv[1]);
// 1. 创建listensock
std::unique_ptr<Socket> listensock = std::make_unique<TcpSocket>(); // 3
listensock->BuildListenSocketMethod(port, gbacklog);
// 2. 创建tcpserver
std::unique_ptr<TcpServer> svr = std::make_unique<TcpServer>();
std::unique_ptr<Accepter> accepter = std::make_unique<Accepter>();
auto listen_recver = std::bind(&Accepter::AccepterConnection, accepter.get(), std::placeholders::_1);
// 4. 构建listen对应的connection,添加到tcpserver中
Connection *conn = ConnectionFactory::BuildListenConnection(listensock->GetSockFd(),
listen_recver,
EPOLLIN | EPOLLET,
svr.get());
svr->AddConnection(conn);
// 5. 开始事件派发
svr->Dispatcher(); // 开始事件派发啦
return 0;
}
cpp
//Makefile
epoll_server:Main.cc
g++ -o $@ $^ -std=c++14 #-ljsoncpp
.PHONY:clean
clean:
rm -f epoll_server
cpp
//Protocol.hpp
#pragma once
#include <iostream>
#include <memory>
#include <jsoncpp/json/json.h>
#define SelfDefine 1
namespace Protocol
{
// 问题
// 1. 结构化数据的序列和反序列化
// 2. 还要解决用户区分报文边界 --- 数据包粘报问题
// 讲法
// 1. 自定义协议
// 2. 成熟方案序列和反序列化
// 总结:
// 我们今天定义了几组协议呢??我们可以同时存在多个协议吗???可以
// "protocol_code\nlen\nx op y\n" : \n不属于报文的一部分,约定
const std::string ProtSep = " ";
const std::string LineBreakSep = "\r\n"; // telnet 敲击回车:\n->\r\n
// "len\nx op y\n" : \n不属于报文的一部分,约定
std::string Encode(const std::string &message)
{
std::string len = std::to_string(message.size());
std::string package = len + LineBreakSep + message + LineBreakSep;
return package;
}
// "len\nx op y\n" : \n不属于报文的一部分,约定
// 我无法保证package就是一个独立的完整的报文
// "l
// "len
// "len\n
// "len\nx
// "len\nx op
// "len\nx op y
// "len\nx op y\n"
// "len\nx op y\n""len
// "len\nx op y\n""len\n
// "len\nx op
// "len\nx op y\n""len\nx op y\n"
// "len\nresult code\n""len\nresult code\n"
bool Decode(std::string &package, std::string *message)
{
// 除了解包,我还想判断报文的完整性, 能否正确处理具有"边界"的报文
auto pos = package.find(LineBreakSep); // 5\r\n -> 5\r
if (pos == std::string::npos)
return false;
std::string lens = package.substr(0, pos);
int messagelen = std::stoi(lens);
int total = lens.size() + messagelen + 2 * LineBreakSep.size();
if (package.size() < total)
return false;
// 至少package内部一定有一个完整的报文了!
*message = package.substr(pos + LineBreakSep.size(), messagelen);
package.erase(0, total);
return true;
}
class Request
{
public:
Request() : _data_x(0), _data_y(0), _oper(0)
{
}
Request(int x, int y, char op) : _data_x(x), _data_y(y), _oper(op)
{
}
void Debug()
{
std::cout << "_data_x: " << _data_x << std::endl;
std::cout << "_data_y: " << _data_y << std::endl;
std::cout << "_oper: " << _oper << std::endl;
}
void Inc()
{
_data_x++;
_data_y++;
}
// 结构化数据->字符串
bool Serialize(std::string *out)
{
#ifdef SelfDefine // 条件编译
*out = std::to_string(_data_x) + ProtSep + _oper + ProtSep + std::to_string(_data_y);
return true;
#else
Json::Value root;
root["datax"] = _data_x;
root["datay"] = _data_y;
root["oper"] = _oper;
Json::FastWriter writer;
*out = writer.write(root);
return true;
#endif
}
bool Deserialize(std::string &in) // "x op y" [)
{
#ifdef SelfDefine
auto left = in.find(ProtSep);
if (left == std::string::npos)
return false;
auto right = in.rfind(ProtSep);
if (right == std::string::npos)
return false;
_data_x = std::stoi(in.substr(0, left));
_data_y = std::stoi(in.substr(right + ProtSep.size()));
std::string oper = in.substr(left + ProtSep.size(), right - (left + ProtSep.size()));
if (oper.size() != 1)
return false;
_oper = oper[0];
return true;
#else
Json::Value root;
Json::Reader reader;
bool res = reader.parse(in, root);
if(res)
{
_data_x = root["datax"].asInt();
_data_y = root["datay"].asInt();
_oper = root["oper"].asInt();
}
return res;
#endif
}
int GetX() { return _data_x; }
int GetY() { return _data_y; }
char GetOper() { return _oper; }
private:
// _data_x _oper _data_y
// 报文的自描述字段
// "len\nx op y\n" : \n不属于报文的一部分,约定
// 很多工作都是在做字符串处理!
int _data_x; // 第一个参数
int _data_y; // 第二个参数
char _oper; // + - * / %
};
class Response
{
public:
Response() : _result(0), _code(0)
{
}
Response(int result, int code) : _result(result), _code(code)
{
}
bool Serialize(std::string *out)
{
#ifdef SelfDefine
*out = std::to_string(_result) + ProtSep + std::to_string(_code);
return true;
#else
Json::Value root;
root["result"] = _result;
root["code"] = _code;
Json::FastWriter writer;
*out = writer.write(root);
return true;
#endif
}
bool Deserialize(std::string &in) // "_result _code" [)
{
#ifdef SelfDefine
auto pos = in.find(ProtSep);
if (pos == std::string::npos)
return false;
_result = std::stoi(in.substr(0, pos));
_code = std::stoi(in.substr(pos + ProtSep.size()));
return true;
#else
Json::Value root;
Json::Reader reader;
bool res = reader.parse(in, root);
if(res)
{
_result = root["result"].asInt();
_code = root["code"].asInt();
}
return res;
#endif
}
void SetResult(int res) { _result = res; }
void SetCode(int code) { _code = code; }
int GetResult() { return _result; }
int GetCode() { return _code; }
private:
// "len\n_result _code\n"
int _result; // 运算结果
int _code; // 运算状态
};
// 简单的工厂模式,建造类设计模式
class Factory
{
public:
std::shared_ptr<Request> BuildRequest()
{
std::shared_ptr<Request> req = std::make_shared<Request>();
return req;
}
std::shared_ptr<Request> BuildRequest(int x, int y, char op)
{
std::shared_ptr<Request> req = std::make_shared<Request>(x, y, op);
return req;
}
std::shared_ptr<Response> BuildResponse()
{
std::shared_ptr<Response> resp = std::make_shared<Response>();
return resp;
}
std::shared_ptr<Response> BuildResponse(int result, int code)
{
std::shared_ptr<Response> req = std::make_shared<Response>(result, code);
return req;
}
};
}
cpp
//Socket.hpp
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "Common.hpp"
#define Convert(addrptr) ((struct sockaddr *)addrptr)
namespace NetWorkModule
{
const static int defaultsockfd = -1;
const int backlog = 5;
enum
{
SocketError = 1,
BindError,
ListenError,
};
// 封装一个基类,Socket接口类
// 设计模式:模版方法类
class Socket
{
public:
virtual ~Socket() {}
virtual void CreateSocketOrDie() = 0;
virtual void BindSocketOrDie(uint16_t port) = 0;
virtual void ListenSocketOrDie(int backlog) = 0;
virtual int AcceptConnection(std::string *peerip, uint16_t *peerport) = 0;
virtual bool ConnectServer(std::string &serverip, uint16_t serverport) = 0;
virtual int GetSockFd() = 0;
virtual void SetSockFd(int sockfd) = 0;
virtual void CloseSocket() = 0;
virtual bool Recv(std::string *buffer, int size) = 0;
virtual void Send(std::string &send_str) = 0;
virtual void ReUseAddr() = 0;
// TODO
public:
void BuildListenSocketMethod(uint16_t port, int backlog)
{
CreateSocketOrDie();
ReUseAddr();
BindSocketOrDie(port);
ListenSocketOrDie(backlog);
}
bool BuildConnectSocketMethod(std::string &serverip, uint16_t serverport)
{
CreateSocketOrDie();
return ConnectServer(serverip, serverport);
}
void BuildNormalSocketMethod(int sockfd)
{
SetSockFd(sockfd);
}
};
class TcpSocket : public Socket
{
public:
TcpSocket(int sockfd = defaultsockfd) : _sockfd(sockfd)
{
}
~TcpSocket()
{
}
void CreateSocketOrDie() override
{
_sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
if (_sockfd < 0)
exit(SocketError);
SetNonBlock(_sockfd); // 将listensock设置成为非阻塞
}
void BindSocketOrDie(uint16_t port) override
{
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_addr.s_addr = INADDR_ANY;
local.sin_port = htons(port);
int n = ::bind(_sockfd, Convert(&local), sizeof(local));
if (n < 0)
exit(BindError);
}
void ListenSocketOrDie(int backlog) override
{
int n = ::listen(_sockfd, backlog);
if (n < 0)
exit(ListenError);
}
int AcceptConnection(std::string *peerip, uint16_t *peerport) override
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int newsockfd = ::accept(_sockfd, Convert(&peer), &len);
if (newsockfd < 0)
return -1;
*peerport = ntohs(peer.sin_port);
*peerip = inet_ntoa(peer.sin_addr);
return newsockfd;
}
bool ConnectServer(std::string &serverip, uint16_t serverport) override
{
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(serverip.c_str());
server.sin_port = htons(serverport);
int n = ::connect(_sockfd, Convert(&server), sizeof(server));
if (n == 0)
return true;
else
return false;
}
int GetSockFd() override
{
return _sockfd;
}
void SetSockFd(int sockfd) override
{
_sockfd = sockfd;
}
void CloseSocket() override
{
if (_sockfd > defaultsockfd)
::close(_sockfd);
}
bool Recv(std::string *buffer, int size) override
{
char inbuffer[size];
ssize_t n = recv(_sockfd, inbuffer, size - 1, 0);
if (n > 0)
{
inbuffer[n] = 0;
*buffer += inbuffer; // 故意拼接的
return true;
}
else if (n == 0)
return false;
else
return false;
}
void Send(std::string &send_str) override
{
// 多路转接我们在统一说
send(_sockfd, send_str.c_str(), send_str.size(), 0);
}
void ReUseAddr() override
{
int opt = 1;
setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
}
private:
int _sockfd;
};
}
cpp
//TcpServer.hpp
#pragma once
#include <iostream>
#include <string>
#include <unordered_map>
#include "Socket.hpp"
#include "Connection.hpp"
#include "Epoller.hpp"
using namespace EpollerModule;
#define EPOLL_BLOCK -1
class TcpServer;
// 只要管理一堆的Connection, 它就是Reactor!
// class Reactor --- 核心的部分
class TcpServer
{
static const int gmaxevents = 64;
public:
TcpServer() : _isrunning(false), _timeout(EPOLL_BLOCK)
{
_epoller.InitEpoller();
}
bool IsConnectionExists(int sockfd)
{
auto iter = _connections.find(sockfd);
if (iter == _connections.end())
return false;
else
return true;
}
void AddConnection(Connection *conn)
{
if (!conn)
return;
if (IsConnectionExists(conn->SockFd()))
return;
// 1. 添加TcpServer unordered_map
_connections.insert(std::make_pair(conn->SockFd(), conn));
lg.LogMessage(Info, "add a new connection success, sockfd: %d\n", conn->SockFd());
// 2. 让epoller管理起来
_epoller.AddEvent(conn->SockFd(), conn->Events());
}
void EnableReadWrite(int sockfd, bool readable, bool writeable)
{
if (!IsConnectionExists(sockfd))
return;
uint32_t events = ((readable ? EPOLLIN : 0) | (writeable ? EPOLLOUT : 0) | EPOLLET);
// 1. 修改connection自己关心的事件
_connections[sockfd]->SetEvents(events);
// 2. 修改epoller, 写透到内核
_epoller.ModEvent(sockfd, _connections[sockfd]->Events());
}
void RemoveConnection(int sockfd)
{
if (!IsConnectionExists(sockfd))
return;
// 1. 清理connection自己关心的事件
_connections[sockfd]->SetEvents(0);
// 2. 清理epoller中对于sockfd的关心, 写透到内核
_epoller.DelEvent(sockfd);
// 3. 移除在_connections中的key value
_connections.erase(sockfd);
}
void LoopOnce(int timeout) // 事件派发
{
int n = _epoller.Wait(_revs, gmaxevents, timeout);
for (int i = 0; i < n; i++)
{
int sockfd = _revs[i].data.fd;
uint32_t events = _revs[i].events;
// 将所有的epoll检测到的fd的问题,最后统一转换成为读写错误
if (events & EPOLLERR)
events |= (EPOLLIN | EPOLLOUT);
if (events & EPOLLHUP)
events |= (EPOLLIN | EPOLLOUT);
// 未来所有的IO操作,都会转化成为下面的部分
// 整个代码有没有执行IO代码?没有!
// IO代码将来在哪里???connection的回调中.
// 我们在干什么???事件派发
if ((events & EPOLLIN) && IsConnectionExists(sockfd)) // 细节
{
if (_connections[sockfd]->_recver)
_connections[sockfd]->_recver(_connections[sockfd]);
}
if ((events & EPOLLOUT) && IsConnectionExists(sockfd))
{
if (_connections[sockfd]->_sender)
_connections[sockfd]->_sender(_connections[sockfd]);
}
}
}
void Dispatcher() // 事件派发器
{
_isrunning = true;
while (_isrunning)
{
_epoller.DebugFdList();
LoopOnce(_timeout); // 事件派发
// 对其他事情进行处理
}
_isrunning = false;
}
~TcpServer()
{
}
private:
Epoller _epoller;
struct epoll_event _revs[gmaxevents];
int _timeout;
std::unordered_map<int, Connection *> _connections;
bool _isrunning;
// std::vector<func_t> others;
};















二、OneThreadOneLoop多进程⽅案

One Thread One Loop(OTOL)是⼀种设计模式,通常⽤于描述基于事件驱动编程(EventDriven Programming)的架构,特别是在使⽤异步I/O(Asynchronous I/O)框架时.这种模式强调每个线程运⾏⼀个独⽴的事件循环(Event Loop),从⽽实现⾼效的并发处理.
核⼼概念
1.事件循环(Event Loop)
- 事件循环是异步编程的核⼼,负责监听事件(如I/O操作、定时器事件等)并触发相应的回调函数.
- 它通常是⼀个单线程的执⾏模型,通过多路复⽤技术(如 epoll 、 select 或poll)⾼效地管理多个I/O操作.
2.单线程模型
- 在 One Thread One Loop 模式中,每个线程运⾏⼀个独⽴的事件循环.
- 这种设计避免了多线程编程中的复杂同步问题,同时利⽤了现代操作系统⾼效的I/O多路复⽤机制.
- 只要把单Reactor写完,扩展多进程很容易.
- 每个进程管理的连接和fd,彼此不要重复.
- 每个Reacor,⾃⼰处理⾃⼰的sockfd的完整⽣命周期,不涉及任何IO穿插,乱序问题.

2.1OneThreadOneLoop多线程⽅案1---主从Reactor多线程模型

注意:管道也能线程间通信,所以我们可以使⽤同样的⽅案进⾏设计.
cpp
#include <iostream>
#include <thread>
#include <unistd.h>
#include <cstring>
// 定义管道的读写端
int pipefds[2];
// ⼯作线程函数
void worker_thread() {
char buffer[256];
int n;
while (true) {
n = read(pipefds[0], buffer, sizeof(buffer) - 1);
if (n > 0) {
buffer[n] = '\0'; // 确保字符串结尾
std::cout << "Worker thread received: " << buffer << std::endl;
} else if (n == 0) {
std::cout << "No more data to read. Exiting worker thread." <<
std::endl;
break;
} else {
std::cerr << "Error reading from pipe." << std::endl;
break;
}
}
}
int main() {
// 创建管道
if (pipe(pipefds) == -1) {
perror("pipe");
return 1;
}
// 创建⼯作线程
std::thread worker(worker_thread);
// 主线程向管道写⼊数据
const char* messages[] = {"Hello from main thread!", "Another message.",
"Goodbye!"};
for (const char* msg : messages) {
sleep(1);
if (write(pipefds[1], msg, strlen(msg)) < 0) {
perror("write");
break;
}
sleep(1);
std::cout << "Main thread sent: " << msg << std::endl;
}
// 等待⼯作线程结束
worker.join();
// 关闭管道(可选)
close(pipefds[0]);
close(pipefds[1]);
return 0;
}
主线程负责监听和接收客户端连接,然后通过Pipe(管道)把连接分发给多个工作线程,每个工作线程拥有独立的 Reactor/EventLoop,负责处理自己管理的连接.
它属于一种 主从Reactor多线程模型(Main Reactor + Sub Reactors),也可以理解为单Acceptor + 多Worker EventLoop的网络服务器架构.
1.方案1的整体架构
客户端连接
│
▼
┌──────────────────┐
│ Master 主线程 │
│ listenSocket │
│ Reactor + accept │
└────────┬─────────┘
│
新连接 socket fd
│
选择工作线程
│
┌───────────┼───────────┐
│ │ │
▼ ▼ ▼
Pipe 1 Pipe 2 Pipe 3
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Worker 1 │ │ Worker 2 │ │ Worker 3 │
│ Reactor 1│ │ Reactor 2│ │ Reactor 3│
│ EventLoop│ │ EventLoop│ │ EventLoop│
└──────────┘ └──────────┘ └──────────┘
│ │ │
▼ ▼ ▼
处理连接1 处理连接2 处理连接3
read/write read/write read/write
业务逻辑 业务逻辑 业务逻辑
2.具体工作流程
假设一个客户端连接服务器:
- Master 监听: 主线程的 Reactor 监听
listenSocket,发现有新连接. - Master 接收: 主线程调用
accept(),得到客户端的socket fd,例如fd=10. - 选择 Worker: 主线程通过轮询算法(Round Robin)选择
Worker 2. - Pipe 通知: 主线程把
fd=10写入Worker 2对应的 Pipe. - Worker 接管:
Worker 2的 EventLoop 监听到 Pipe 可读,读取fd=10,创建Connection对象,并把连接注册到自己的 Reactor. - 处理业务: 以后这个连接的
read、write和业务回调主要由Worker 2负责,Master 不再处理该连接的数据收发.
3.为什么叫 OneThreadOneLoop?
因为它遵循一个核心原则:
每一个 I/O 工作线程拥有一个独立的 EventLoop.
| 线程 | EventLoop | 职责 |
|---|---|---|
| Master | Main Reactor | 监听、accept、连接分发 |
| Worker 1 | Reactor 1 | 负责分配给自己的连接 |
| Worker 2 | Reactor 2 | 负责分配给自己的连接 |
| Worker 3 | Reactor 3 | 负责分配给自己的连接 |
多个 Worker 可以在不同 CPU 核心上并行运行,而每个 Worker 内部通过 I/O 多路复用处理多个连接事件.
4.方案1最核心的特点
它将"接收连接"和"处理连接"分离.
- Master 只做新连接接入和分配.
- Worker 专门负责客户端连接的 I/O 和业务处理.
- 每个 Worker 有自己的 Reactor,减少多个线程同时操作同一连接的情况.
- Master 与 Worker 通过 Pipe 通信,存在一定的线程间通知开销.
这里还有一个容易混淆的点:
Worker 继承 listenSocket,不代表 Worker 也会调用 accept().
5.它与单线程 Reactor 有什么区别?
| 对比维度 | 单线程 Reactor | OneThreadOneLoop 方案1 |
|---|---|---|
| EventLoop | 1 个 | Master 1 个 + Worker N 个 |
| accept | 主线程 | Master |
| 连接 I/O | 单线程处理 | 多个 Worker 并行处理 |
| 业务逻辑 | 单线程执行 | 不同 Worker 可并行执行 |
| 线程通信 | 一般不需要 | Pipe 通知 |
| 性能瓶颈 | 单线程处理能力 | Master 接入能力、Worker 负载等 |
2.2Eventfd事件驱动
1.什么是 eventfd 事件驱动?
eventfd 是 Linux 提供的一种基于文件描述符的事件通知机制.
它的核心作用是:
一个线程发送通知,另一个线程通过epoll感知事件,从而被唤醒并执行相应任务.
可以把它理解为一个"电子门铃":
- Master 线程: 有新连接时,向 Worker 的
eventfd写入数字1,相当于按门铃. - Worker 线程: 使用
epoll_wait()等待事件,门铃响了就被唤醒. - Worker 处理: 读取
eventfd,然后从共享任务队列中取出新连接,注册到自己的 Reactor.
这里有一个关键点:
eventfd 负责通知,不负责传输 socket fd.如果需要把连接交给Worker,通常还需要一个线程安全队列来保存新连接的 fd.
2.在OneThreadOneLoop中的架构

每个 Worker 都有自己的 eventfd 和 epoll,既能监听客户端连接的读写事件,也能监听主线程发来的唤醒通知.
3.eventfd 为什么能唤醒 epoll?
eventfd 内部维护一个 64 位无符号计数器.默认情况下,它的行为如下:

当计数器大于 0,eventfd 就处于可读状态,因此注册了它的 epoll 能报告 EPOLLIN 事件.
4.C++ 实现思路
c
// 初始化:每个 Worker 一个 eventfd
int wakeupFd = eventfd(
0, EFD_NONBLOCK | EFD_CLOEXEC
);
// Worker 将 wakeupFd 注册到自己的 epoll
epoll_event ev{};
ev.events = EPOLLIN;
ev.data.fd = wakeupFd;
epoll_ctl(epollFd, EPOLL_CTL_ADD, wakeupFd, &ev);
// Master:接收到新连接后
worker.queue.push(clientFd); // 线程安全队列
eventfd_write(worker.wakeupFd, 1);
// Worker:事件循环
while (running) {
int n = epoll_wait(epollFd, events, MAX_EVENTS, -1);
for (int i = 0; i < n; ++i) {
if (events[i].data.fd == wakeupFd) {
eventfd_t count;
eventfd_read(wakeupFd, &count);
// 取出任务,创建 Connection
// 注册客户端 fd 到当前 EventLoop
processPendingConnections();
} else {
// 处理客户端 read/write 事件
handleConnectionEvent(events[i]);
}
}
}
这是简化后的核心逻辑,省略了线程安全队列实现、错误处理和资源清理.实际实现中还应注意入队与通知的顺序、通知失败处理,以及批量读取任务.
5.Pipe与eventfd的区别
| 对比 | Pipe | eventfd |
|---|---|---|
| 主要用途 | 传输字节数据 | 事件通知与计数 |
| 文件描述符 | 通常需要 2 个 | 1 个 |
| 可以传输 socket fd 整数值 | 可以 | 不能直接传输 |
| 支持 epoll | 支持 | 支持 |
| 线程间通知 | 可以 | 更有针对性 |
| 额外数据结构 | 不一定需要 | 通常配合任务队列 |
2.3Eventfd⼯作模式
Linux 的 eventfd 主要有 两种工作模式:普通计数器模式(默认模式)和信号量模式(EFD_SEMAPHORE).
两种模式的核心区别是:读取事件时,是一次性清空计数器,还是每次只消耗一个计数.
1.两种工作模式

1.普通计数器模式
创建方式:
c
int efd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
假设连续写入三次:
c
eventfd_write(efd, 1);
eventfd_write(efd, 1);
eventfd_write(efd, 1);
此时内部计数器为 3.
执行:
c
eventfd_t value;
eventfd_read(efd, &value);
读取结果 value = 3,计数器直接变为 0.
2.信号量模式
创建方式:
c
int efd = eventfd(
0,
EFD_SEMAPHORE | EFD_NONBLOCK | EFD_CLOEXEC
);
同样写入三次 1 后,连续读取的变化:
| 操作 | 读取结果 | 剩余计数器 |
|---|---|---|
| 初始 | --- | 3 |
| 第一次 read | 1 | 2 |
| 第二次 read | 1 | 1 |
| 第三次 read | 1 | 0 |
| 第四次 read | EAGAIN | 0 |
其中第四次返回 EAGAIN,是因为设置了 EFD_NONBLOCK.
2.交互模拟:eventfd 读写过程

3.阻塞与非阻塞模式
这和前面两种计数器模式是两个独立的维度.
| 场景 | 阻塞模式 | 非阻塞模式 |
|---|---|---|
| 计数器为 0 时 read | 等待计数器变为非零 | 返回 EAGAIN |
| 写入导致计数器超出最大值 | 等待可以写入 | 返回 EAGAIN |
| 常见用途 | 直接等待事件 | 配合 epoll 事件循环 |
另外,epoll 的LT(水平触发)和ET(边缘触发)是事件就绪通知策略,与 eventfd 的普通/信号量模式不是同一回事.
4.OneThreadOneLoop 应该使用哪一种?
推荐:普通计数器模式 + EFD_NONBLOCK + epoll + 线程安全任务队列.
主从 Reactor 多线程架构通常是:
Master 接收到新连接
│
▼
将 socket fd 放入 Worker 任务队列
│
▼
eventfd_write(efd, 1)
│
▼
Worker 的 epoll_wait 被唤醒
│
▼
eventfd_read() 清空通知计数
│
▼
取出任务队列中的连接
│
▼
注册到 Worker 自己的 EventLoop
│
▼
处理客户端 read / write 事件
普通模式更适合这里的原因是:Worker 不需要按通知次数逐个处理 eventfd,只需被唤醒一次,然后批量处理队列里的全部待处理任务.
2.4OneThreadOneLoop多线程⽅案2---线程安全任务队列+eventfd事件通知机制

主从Reactor多线程模型+线程安全任务队列(Queue + Mutex)+eventfd事件通知机制.
与前面的方案1相比,最大的变化是:
- 方案1: Master 通过
Pipe将 socket fd 传递给 Worker. - 方案2: Master 通过
Queue传递 socket fd,再通过eventfd通知 Worker 来取.
它是一种将连接传递与线程唤醒分离的设计.
1.方案2整体架构
客户端连接
│
▼
┌────────────────────┐
│ Master 主线程 │
│ listenSocket │
│ Reactor + accept() │
└─────────┬──────────┘
│
获取 socket fd
│
▼
选择目标 Worker
│
┌───────────┼───────────┐
│ │ │
▼ ▼ ▼
Queue 1 Queue 2 Queue 3
+ Mutex + Mutex + Mutex
│ │ │
│ │ │
eventfd 1 eventfd 2 eventfd 3
│ │ │
▼ ▼ ▼
Worker 1 Worker 2 Worker 3
Reactor 1 Reactor 2 Reactor 3
EventLoop 1 EventLoop 2 EventLoop 3
│ │ │
▼ ▼ ▼
取出 fd 取出 fd 取出 fd
注册 epoll 注册 epoll 注册 epoll
│ │ │
▼ ▼ ▼
read/write read/write read/write
这里画的是每个 Worker 一个队列的推荐实现.顶部画了一个公共 fd 队列,那也是可行的变体,后面会说明区别.
2.方案2的五个工作步骤
以客户端连接 fd=10 为例.
第一步:Master 接收新连接
Master 使用 epoll 监听 listenSocket,检测到新连接后调用 accept(),得到客户端 fd=10.
第二步:将 fd 放入任务队列
Master 按轮询等策略选择 Worker 2,获取对应队列的 Mutex,将 fd=10 放入队列,随后释放锁.
c
lock(mutex);
queue.push(10);
unlock(mutex);
第三步:eventfd 通知 Worker
Master 向 Worker 2 的 eventfd 写入 1:
c
eventfd_write(worker2.eventfd, 1);
Worker 2 的 Reactor 已经通过 epoll 监听这个 eventfd,因此可以感知到通知.
第四步:Worker 被唤醒并取出 fd
Worker 2 的 epoll_wait() 返回,执行 eventfd 的可读回调,读取 eventfd 计数器,然后加锁取出队列中的待处理 fd.
c
eventfd_read(eventfd, &value);
lock(mutex);
// 批量取出队列里的 fd
unlock(mutex);
第五步:注册到 Worker 的 Reactor
Worker 2 为 fd=10 创建 Connection 和相应的事件监听对象,将其加入自己的 epoll.
从此,fd=10 的读写事件主要由 Worker 2 的 EventLoop 负责,Master 不再处理其业务数据.
3.为什么要同时使用 Queue、Mutex、eventfd?
这三个组件解决的是不同问题.
| 组件 | 主要作用 | 形象理解 |
|---|---|---|
| Queue | 保存待处理 socket fd | 快递仓库 |
| Mutex | 保护队列并发访问 | 仓库门锁 |
| eventfd | 通知 Worker 有新任务 | 门铃 |
| Reactor / epoll | 监听与分发事件 | 工作调度员 |
核心思想:Queue 传递数据,eventfd 传递通知.
eventfd 本身只维护计数器,不能直接保存 fd=10、fd=11、fd=12 这样的任务列表.
4.方案1与方案2的区别
| 对比项 | 方案1(Pipe) | 方案2(eventfd) |
|---|---|---|
| 线程间通信 | Pipe | Queue + eventfd |
| fd 传递 | 写入 Pipe | 放入 Queue |
| 唤醒 Worker | Pipe 可读 | eventfd 可读 |
| 数据同步 | 依靠 Pipe 字节流 | Mutex 保护 Queue |
| 任务批量处理 | 可以 | 可以 |
| 复杂程度 | 较简单 | 需要管理队列和同步 |
| 核心优势 | 传输与通知合一 | 任务存储与通知解耦 |
这里需要注意:不能简单认为 eventfd 一定比 Pipe 性能更高.eventfd 的通知机制很轻量,但方案2引入了队列和锁,实际性能取决于锁竞争、通知频率、批处理方式和负载.

🚀真正的勇者不是流泪的人,而是含泪奔跑的人!
敬请期待下一篇文章内容
每日心灵鸡汤: 慢慢来,一切都在路上!
人生没有白走的路,每一步都算数.不要因为暂时看不到结果,就怀疑自己的努力.花有花期,人有时运,每个人都有属于自己的成长节奏.你不必事事优秀,也不必时时坚强.累了就休息,迷茫了就调整方向,但别轻易否定那个一直努力的自己.生活不会辜负每一个认真前行的人.愿你眼里有光,心中有梦,脚下有路.把今天过好,就是对明天最好的交代.
