核心设计模式:Reactor(反应器)模式。
构建一个单线程、非阻塞、边缘触发(ET)、事件驱动的 TCP 服务器框架。该框架能够同时处理数千个客户端连接,支持自定义应用层协议(我们以计算器为例,使用 长度\r\nJSON\r\n 格式),并实现业务逻辑与网络 IO 的完全解耦。
一、角色构成
| 角色名称 | 在代码中的对应 | 职责描述 |
|---|---|---|
| 事件源 (Event Source) | 文件描述符(socket) | 产生事件的对象,如监听套接字(有新连接)、普通套接字(可读/可写/出错)。 |
| 事件分离器 (Event Demultiplexer) | Epoller 类 | 等待事件的发生,并返回就绪的事件列表。这里使用 epoll 作为同步 I/O 多路复用器。 |
| 反应器 (Reactor) | Reactor 类 | 注册/注销事件源及其处理器,持有事件分离器,在事件循环中获取就绪事件并分发给相应的处理器。 |
| 事件处理器 (Event Handler) | Connection 派生类(Listener, Channel) | 定义处理特定事件的方法:Recver()(可读)、Sender()(可写)、Excepter()(异常/关闭)。 |
| 具体事件处理器 (Concrete Handler) | Listener, Channel | 实现具体的读写、接受连接等业务逻辑。 |
二、工作流程(以单线程 Reactor 为例)
- 初始化阶段
-
创建 Reactor 对象(内部创建 Epoller)。
-
创建 Listener(监听套接字),设置其回调(协议解析函数)。
-
调用 Reactor::AddConnection(listener),将 listener 的 fd 和事件(EPOLLIN | EPOLLET)注册到 Epoller 中。
- 事件循环(Reactor::Dispatcher)
c
while (true) {
LoopOnce(timeout); // 等待并分发事件
CheckTimeout(); // 处理超时连接
}
- 事件等待与分发(Reactor::LoopOnce)
-
调用 _epoller->Wait(_revs, max, timeout),阻塞等待就绪事件。
-
返回后,遍历 _revs 数组:
-
根据 data.fd 从 _conns 映射表中找到对应的 Connection 对象。
-
若事件包含 EPOLLHUP 或 EPOLLERR,调用 conn->Excepter()。
-
若事件包含 EPOLLIN,调用 conn->Recver()。
-
若事件包含 EPOLLOUT,调用 conn->Sender()。
- 事件处理器执行(以普通连接 Channel 为例)
- 可读事件(Recver):
-
循环 recv 直到 EAGAIN,数据追加到 _inbuffer。
-
调用业务回调 _cb(_inbuffer)(即 Parser::Parse),后者会解析 _inbuffer 中的完整报文,生成响应,并 消费掉已处理的报文。
-
响应存入 _outbuffer。
-
若 _outbuffer 非空,调用 Reactor::EnableReadWrite(fd, true, true),即向 Epoller 注册 EPOLLOUT 事件。
- 可写事件(Sender):
-
循环 send 直到 EAGAIN 或 _outbuffer 空。
-
发送完毕后,若 _outbuffer 为空,调用 Reactor::EnableReadWrite(fd, true, false),移除 EPOLLOUT 事件。
- 异常处理(Excepter):
- 调用 Reactor::DelConnection(fd),从 Epoller 中删除 fd,关闭连接,从映射表中移除。
- 超时检查(CheckTimeout)
- 遍历所有 Connection,若当前时间与 LastActive() 之差超过阈值,则调用 DelConnection 清理。
设计要点总结
-
事件源与处理器一一对应:每个 fd 对应一个 Connection 子类实例,Reactor 只负责调度,不关心具体处理逻辑。
-
分离事件注册与等待:Epoller 只提供系统调用封装,Reactor 管理生命周期和事件修改。
-
边缘触发(ET)与循环读写:要求处理器内部循环处理所有数据,直至 EAGAIN,这是高性能的保证。
-
写事件按需注册:仅在 _outbuffer 有数据时才添加 EPOLLOUT,避免无效唤醒。
-
回调分层:网络层(Channel)调用协议层(Parser),协议层调用业务层(Calculator),各层职责清晰。这个在
main.cc中会重点提到
三、模块划分
1. Util.hpp
职责:提供通用的底层工具函数。
核心函数:
-
SetNonBlock(int fd):使用 fcntl 设置文件描述符为非阻塞模式。这是 ET 模式的前提。
-
Event2String(uint32_t events):将 epoll 事件掩码转为可读字符串(用于调试)。
cpp
#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(f1<0)
{
return;
}
fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}
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;
}
2. InetAddr.hpp
职责:封装 sockaddr_in,统一处理网络地址和主机地址的转换。
提供接口:
-
构造函数(端口、IP)。
-
Ip()、Port()、ToString()。
-
Addr() 返回 struct sockaddr*,Length() 返回长度。
意义:让上层代码不再直接操作 sockaddr_in,提高可读性。
cpp
#pragma once
// 这个类,描述client socket信息的类
// 方便我们后续用它来管理客户端
#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#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()
{}
InetAddr(const struct sockaddr_in &addr)
: _addr(addr)
{
Net2Host();
}
InetAddr(uint16_t port, const std::string &ip = "0.0.0.0")
: _port(port), _ip(ip)
{
Host2Net();
}
void Init(const struct sockaddr_in &addr)
{
_addr = addr;
Net2Host();
}
std::string Ip()
{
return _ip;
}
uint16_t Port()
{
return _port;
}
struct sockaddr* Addr()
{
return Conv(_addr);
}
socklen_t Length()
{
return sizeof(_addr);
}
std::string ToString()
{
return _ip + "-" + std::to_string(_port);
}
bool operator==(const InetAddr &addr)
{
return (_ip == addr._ip && _port == addr._port);
// return (_ip == addr._ip);
}
~InetAddr()
{
}
private:
struct sockaddr_in _addr; // 网络风格地址
// 主机风格地址
std::string _ip;
uint16_t _port;
};
3. Socket.hpp
职责:封装基本的 socket 系统调用,并强制设置非阻塞和地址重用。
设计:定义抽象基类 Socket,实现类 TcpSocket。
核心方法:
-
Create:调用 socket(),设置 SO_REUSEADDR 和 SO_REUSEPORT,并设为非阻塞。
-
Bind、Listen。
-
Accept:返回新连接 fd,同时通过参数返回客户端地址和错误码(用于区分 EAGAIN、EINTR)。
为什么单独封装:方便后期扩展(如增加 UDP 支持),且集中处理非阻塞设置。
cpp
#ifndef __SOCKET_HPP__
#define __SOCKRT_HPP__
#include <iostream>
#include <string>
#include <unistd.h>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <memory>
#include "Logger.hpp"
#include "InetAddr.hpp"
#include "Util.hpp"
enum
{
OK,
CREATE_ERR,
BIND_ERR,
LISTEN_ERR,
};
static int gbacklog = 16;
static const int gsockfd = -1;
class Socket
{
public:
virtual ~Socket() {}
virtual void CreateSocketOrDie() = 0;
virtual void BindSocketOrDie(int port) = 0;
virtual void ListenSocketOrDie(int backlog) = 0;
// virtual std::shared_ptr<Socket> Accept(InetAddr *clientaddr) = 0;
virtual int Accept(InetAddr *clientaddr, int *error) = 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;
virtual bool Connect(InetAddr &peer) = 0;
// 其他接口
public:
void BuildListenSocketMethod(int _port)
{
CreateSocketOrDie();
BindSocketOrDie(_port);
ListenSocketOrDie(gbacklog);
}
void BuildClientSocketMethod()
{
CreateSocketOrDie();
}
// void BuildUdpSocketMethod()
// {
// CreateSocketOrDie();
// BindSocketOrDie();
// }
};
class TcpSocket : public Socket
{
public:
TcpSocket() : _sockfd(gsockfd)
{}
TcpSocket(int sockfd): _sockfd(sockfd)
{}
void CreateSocketOrDie() override
{
_sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (_sockfd < 0)
{
LOG(LogLevel::FATAL) << "create socket error!";
exit(CREATE_ERR);
}
SetNonBlock(_sockfd);
int opt = 1;
// 地址复用 --> socket addr 重复使用
setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));
LOG(LogLevel::INFO) << "create socket success!";
}
void BindSocketOrDie(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::INFO) << "bind socket success!";
}
void ListenSocketOrDie(int backlog) override
{
if(listen(_sockfd, backlog) != 0)
{
LOG(LogLevel::FATAL) << "listen socket error!";
exit(LISTEN_ERR);
}
LOG(LogLevel::INFO) << "listen socket success!";
}
// std::shared_ptr<Socket> Accept(InetAddr *clientaddr) override
int Accept(InetAddr *clientaddr, int *error) override
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int fd = accept(_sockfd, (struct sockaddr*)&peer, &len);
*error = errno;
if(fd < 0)
{
LOG(LogLevel::WARNING) << "accept socket Done!";
return -1;
}
LOG(LogLevel::INFO) << "accept socket success!";
clientaddr->Init(peer);
return fd;
// return std::make_shared<TcpSocket>(fd);
}
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)
{
//也有问题
return send(_sockfd, in.c_str(), in.size(), 0);
}
bool Connect(InetAddr &peer) override
{
int n = connect(_sockfd, peer.Addr(), peer.Length());
if(n >= 0)
return true;
else
return false;
}
~TcpSocket()
{
}
private:
int _sockfd;
};
// class UdpSocket : public Socket
// {
// };
#endif
4. Epoller.hpp
职责:纯 epoll 系统调用的封装,不涉及业务逻辑。
核心方法:
-
AddEvent(EPOLL_CTL_ADD)
-
ModEvent(EPOLL_CTL_MOD)
-
DelEvent(EPOLL_CTL_DEL)
-
Wait:返回就绪事件数组和个数。
设计要点:只操作 struct epoll_event,上层(Reactor)负责管理 data.fd 的关联。
cpp
#pragma once
#include <iostream>
#include <sys/epoll.h>
#include "Util.hpp"
#include "Logger.hpp"
class Epoller
{
private:
//OperEventHelper 是 epoll 事件管理的核心辅助函数,封装了 epoll_ctl 的调用细节
int OperEventHelper(int sockfd, uint32_t events, int oper)
{
sturct epoll_event ev;
ev.events = events;
ev.data.fd = sockfd;
return epoll_ctl(_epfd, oper, sockfd, &ev);
}
public:
Epoller()
{
_epfd = epoll_create(128);
if(_epfd < 0)
{
LOG(LogLevel::FATAL) << "epoll create fatal";
exit(1);
}
LOG(LogLevel::INFO) << "create epoll fd success: "<< _epfd;
}
void AddEvent(int sockfd, uint32_t events)
{
int n = OperEventHelper(sockfd, events, EPOLL_CTL_ADD);
if (n != 0)
{
LOG(LogLevel::INFO) << "add: " << sockfd << " events: "
<< Event2String(events) << " to epoller failed";
return;
}
LOG(LogLevel::INFO) << "add: " << sockfd << " events: "
<< Event2String(events) << " to epoller success";
}
void DelEvent(int sockfd)
{
int n = epoll_ctl(_epfd, EPOLL_CTL_DEL, sockfd, nullptr);
if (n != 0)
{
LOG(LogLevel::INFO) << "Del: " << sockfd << " from epoller failed";
return;
}
LOG(LogLevel::INFO) << "Del: " << sockfd << " from epoller success";
}
void ModEvent(int sockfd, uint32_t events)
{
int n = OperEventHelper(sockfd, events, EPOLL_CTL_MOD);
if (n != 0)
{
LOG(LogLevel::INFO) << "Mod: " << sockfd << " events: "
<< Event2String(events) << " to epoller failed";
return;
}
LOG(LogLevel::INFO) << "Mod: " << sockfd << " events: "
<< Event2String(events) << " to epoller success";
}
int Wait(struct epoll_event revs[], int num, int timeout)
{
int n = epoll_wait(_epfd, revs[], num, timeout);
//TODO
return n;
}
~Epoller()
{
if (_epfd >= 0)
{
close(_epfd);
}
}
private:
int _epfd;
};
}
5. Connection.hpp -- 抽象基类
职责:定义服务器中所有"连接"的统一接口,并持有每个连接独有的数据。
数据成员:
-
_fd、_events(当前 epoll 关心的事件,用于同步修改)。
-
_inbuffer、_outbuffer(应用层读写缓冲区,解决 TCP 粘包)。
-
_peer(对端地址)。
-
_owner(指向所属 Reactor 的指针,用于回调 reactor 的方法)。
-
_cb(业务回调,std::function<string(string&)>,由协议层提供)。
-
_lastActive(用于超时检查)。
cpp
#pragma once
#include <iostream>
#include <string>
#include <functional>
#include "InetAddr.hpp"
class Reactor;
using callback_t = std::function<std::string(std::string &inbuffer)>;
//connection :标识服务器收到的一条连接
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;
std::string _inbuffer;
std::string _outbuffer;
uint64_t lasttimestamp;
InetAddr _peer;
Reactor *owner; //回指指针,方便我们使用Reactor中的方法
callback_t _cb;
}
6. Listener.hpp -- 监听套接字
继承:Connection。
-
构造函数:创建 TcpSocket,执行 Create/Bind/Listen,设置 _events = EPOLLIN | EPOLLET。
-
实现 Recver:
循环调用 _sock->Accept 直到返回 -1 且 errno == EAGAIN。
-
对于每个新连接 fd,调用 SetNonBlock。
-
创建 Channel 对象(std::make_shared(fd, clientaddr))。
-
将 Listener 自己的 _cb(即协议解析回调)传递给 Channel。
-
调用 Owner()->AddConnection(channel) 将其加入 Reactor。
-
Sender 和 Excepter 为空实现(监听套接字不需要发送和主动错误处理)。
cpp
#pragma once
#include <iostream>
#include <memory>
#include "InetAddr.hpp"
#include "Epoller.hpp"
#include "Connection.hpp"
#include "Socket.hpp"
#include "Channel.hpp"
#include "Util.hpp"
//链接管理器: 创建listen,获取新链接
class Listener : public Connection
{
public:
Listener(uint16_t port)
: _listensockfd(std::make_unique<TcpSocket>()),
_port(port)
{
_listensockfd->BuildListenSocketMethod(_port);
SetSocketfd(_listensockfd->SockFd());
SetEvents(EPOLLIN | EPOLLET);
}
void Recver() override
{
//获取新连接,非阻塞循环获取,因为我们是ET模式,保证本次获取连接,都必须处理完
while(true)
{
InetAddr clientaddr;
int error = 0;
int sockfd = _listensockfd->Accept(&clientaddr, &error);
if(sockfd < 0)
{
// 处理接受失败的情况
if (errno == EAGAIN)
// 非阻塞模式下没有更多待接受的连接,正常退出循环
break;
else if (errno == EINTR)
// 被系统信号中断,继续尝试接受
continue;
else
// 其他错误(如系统资源不足),退出循环
break;
}
}
//获取新链接成功,不可以直接读,应添加到Reactor中!
//1.构建新的连接
SetNonBlock(sockfd);
std::shared_ptr<Connection> conn = std::make_shared<Channel>();
conn->SetCallback(_cb);
//添加到Reactor中
Owner()->AddConnection(conn);
}
void Sender() override
{
// empty
}
void Excepter() override
{
// empty
}
~Listener() {}
private:
std::unique_ptr<Socket> _listensockfd;
uint16_t _port;
}
7. Channel.hpp -- 普通数据连接
继承:Connection。
实现 Recver:
- 更新活跃时间。
- 循环调用 recv 直到返回 EAGAIN(非阻塞),数据追加到 _inbuffer。
- 若 recv 返回 0 或错误,调用 Excepter()。
- 调用 _cb(_inbuffer) 获取响应字符串(该回调内部会解析 _inbuffer 并消费已处理部分)。
- 将响应追加到 _outbuffer。
- 若 _outbuffer 非空,调用 Owner()->EnableReadWrite(_fd, true, true) 添加 EPOLLOUT 事件。
实现 Sender:
- 更新活跃时间。
- 循环调用 send 直到 _outbuffer 为空或返回 EAGAIN。
- 发送成功后从 _outbuffer 中 erase 已发部分。
- 若 _outbuffer 为空,调用 Owner()->EnableReadWrite(_fd, true, false) 移除 EPOLLOUT;否则保留(等待下一次写事件)。
实现 Excepter:
调用 Owner()->DelConnection(_fd),清理 epoll、关闭 fd、从 map 中移除。
为什么用 _inbuffer 累积数据:TCP 是流式,一次 recv 可能只收到半个报文,需要缓存到完整报文出现。
cpp
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include "Epoller.hpp"
#include "Connection.hpp"
// 关不关心数据是什么?
class Channel : public Connection
{
static const int buffersize = 1024;
public:
Channel(int sockfd, InetAddr &addr)
{
SetSocketfd(sockfd);
SetAddr(addr);
SetEvents(EPOLLIN | EPOLLET);
}
void Recver() override
{
// 更新连接的最后活跃时间(用于超时检测)
Update();
// ET模式要求:必须循环读取完所有可用数据
while (true)
{
char buffer[buffersize]; // 临时缓冲区
// 非阻塞读取数据(_sockfd已设置为非阻塞模式)
// sizeof(buffer) - 1 留一个字节给字符串结束符
ssize_t n = recv(_sockfd, buffer, sizeof(buffer) - 1, 0);
if (n > 0)
{
// 成功读取数据
// 添加字符串结束符,确保可以作为C字符串处理
buffer[n] = 0;
// 将读取的数据追加到输入缓冲区
_inbuffer += buffer;
}
else if (n == 0)
{
// n == 0 表示客户端主动关闭连接
LOG(LogLevel::INFO) << "client quit, client is : " << _peer.ToString();
// 处理连接关闭(清理资源、从Reactor中移除等)
Excepter();
// 退出函数,不再继续处理
return;
}
else // n < 0,表示读取失败
{
// 通过 errno 判断失败原因(errno 是标准库提供的全局错误码变量)
if (errno == EAGAIN)
{
// EAGAIN:非阻塞模式下没有更多数据可读
// 这是正常情况,退出循环
break;
}
else if (errno == EINTR)
{
// EINTR:读取操作被系统信号中断
// 继续循环,尝试再次读取
continue;
}
else
{
// 其他错误:如连接异常断开等
LOG(LogLevel::INFO) << "recv error, client is : " << _peer.ToString();
// 处理异常情况
Excepter();
// 退出函数
return;
}
}
}
// 循环结束,本轮数据已全部读完
// 调用协议解析回调处理数据
// 注意:回调函数负责判断数据是否完整,不完整时返回空字符串
// _inbuffer 会保留未解析的数据,等待下次数据到达后继续解析
_outbuffer += _cb(_inbuffer);
// 打印输出缓冲区内容(调试用)
std::cout << "_outbuffer: " << _outbuffer << std::endl;
// 如果有响应数据,启用写事件监听
// 当套接字可写时,Reactor会触发Sender()方法发送数据
if (!_outbuffer.empty())
Owner()->EnableReadWrite(_sockfd, true, true); // 启用读和写事件
}
void Sender() override
{
// 步骤1: 更新连接的最后活跃时间(用于超时检测)
Update();
// 步骤2: 非阻塞循环发送数据
while (true)
{
// 发送数据(_sockfd已设置为非阻塞模式)
ssize_t n = send(_sockfd, _outbuffer.c_str(), _outbuffer.size(), 0);
if (n > 0) {
// 成功发送了 n 字节
_outbuffer.erase(0, n); // 从缓冲区头部移除已发送的数据
if (_outbuffer.size() == 0) {
// 数据全部发送完毕,退出循环
break;
}
// 否则继续循环发送剩余数据
}
else if (n == 0) {
// 连接关闭(很少见,通常通过 recv 返回 0 判断)
break;
}
else if (n < 0) {
// 发送失败,通过 errno 判断原因
if (errno == EAGAIN) {
// 发送缓冲区已满,稍后再试
break;
}
else if (errno == EINTR) {
// 被系统信号中断,继续尝试发送
continue;
}
else {
// 其他错误(如连接断开)
Excepter(); // 处理异常(清理资源、移除连接等)
return; // 退出函数
}
}
}
// 步骤3: 根据缓冲区状态调整写事件监听
if (!_outbuffer.empty()) {
// 数据未发完(发送缓冲区满),继续监听写事件
Owner()->EnableReadWrite(_sockfd, true, true);
}
else {
// 数据已全部发完,关闭写事件监听
Owner()->EnableReadWrite(_sockfd, true, false);
}
}
void Excepter() override
{
// 统一错误处理!
Owner()->DelConnection(_sockfd);
}
};
8. Protocol.hpp -- 协议与序列化(与具体业务相关,但保持独立)
定义:Request、Response 结构体,包含序列化/反序列化方法(使用 JSON)。
-
静态工具类 Protocol:
-
Package(const string& json):返回 长度\r\njson\r\n。
-
Unpack(string& buf, string* out):
-
从 buf 中解析出一个完整报文,提取 JSON 存入 out。
-
关键:解析成功后从 buf 中删除该报文(通过 erase)。
-
返回报文长度,0 表示不完整,-1 表示协议错误。
设计要点:解包函数直接修改输入缓冲区,这是"谁解析谁清理"原则的体现,避免上层重复解析。
cpp
#pragma once
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
class Request
{
public:
Request()
{
_x = _y = _oper = 0;
}
// 序列号对象
bool Serialize(std::string *out)
{
//1. 手写 "_x" "_oper" "_y" --- 字符串拼接转换
//2. 现成工具
Json::Value root;
root["x"] = _x;
root["y"] = _y;
root["oper"] = _oper;
Json::StyledWriter writer;
*out = writer.write(root);
if(out->empty())
return false;
return true;
}
// 反序列化对象
bool Deserialize(std::string &in)
{
Json::Reader reader;
Json::Value root;
bool ret = reader.parse(in, 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:
public: // 加快速度
// x oper y -- 约定1
int _x;
int _y;
char _oper;
};
class Response
{
public:
Response():_result(0), _code(0)
{
}
// 序列号对象
bool Serialize(std::string *out)
{
//1. 手写 "_x" "_oper" "_y" --- 字符串拼接转换
//2. 现成工具
Json::Value root;
root["result"] = _result;
root["code"] = _code;
Json::StyledWriter writer;
*out = writer.write(root);
if(out->empty())
return false;
return true;
}
// 反序列化对象
bool Deserialize(std::string &in)
{
Json::Reader reader;
Json::Value root;
bool ret = reader.parse(in, 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()
{
std::cout << _result << "[" << _code << "]" << std::endl;
}
~Response()
{
}
private:
int _result; //
int _code; // 可信度
};
static const std::string sep = "\r\n";
class Protocol
{
public:
static std::string Package(const std::string &jsonstr)
{
// jsonstr -> len\r\njsonstr\r\n
if(jsonstr.empty())
return std::string();
std::string json_length = std::to_string(jsonstr.size());
return json_length + 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;
}
static int Unpack(std::string &origin_str, std::string *package)
{
if(!package)
return 0;
auto pos = origin_str.find(sep); // 从左向右
if(pos == std::string::npos)
return 0;
//至少说明,我们收到了一个报文的长度
std::string len_str = origin_str.substr(0, pos);
if(!DigitSafeCheck(len_str))
{
return -1;
}
int digit_len = std::stoi(len_str);
// 如果我得到了当前报文的长度
// 根据协议,我可以推测出,一个完整报文的长度是多少
int target_len = len_str.size() + digit_len + 2 * sep.size();
if(origin_str.size() < target_len)
return 0;
///////////////上面的逻辑,我们对origin_string都没做/////////////////
// 我保证,origin_str内部,一定有一个完整的报文请求!
*package = origin_str.substr(pos + sep.size(), digit_len);
origin_str.erase(0, target_len);
return package->size();
}
};
9. Parser.hpp -- 协议解析器 + 业务调度器
职责:将 Protocol::Unpack 与业务回调(如 Calculator::Exec)串联起来。
构造函数:接收一个 handler_t(std::function<Response(Request&)>)。
方法 Parse:
-
输入参数:std::string &inbuffer(引用)。
-
循环调用 Protocol::Unpack(inbuffer, &jsonstr),直到返回 0 或 -1。
-
对每个完整报文:反序列化为 Request,调用业务 handler 得到 Response,序列化,调用 Protocol::Package 打包。
-
将所有打包后的响应拼接成一个字符串返回。
注意:inbuffer 会通过 Unpack 被修改(已处理的报文被删除)。
为什么单独抽取 Parser:让网络层(Channel)不关心协议细节,只需要调用一个回调即可。
cpp
#pragma once
#include "Protocol.hpp"
#include "Logger.hpp"
#include <iostream>
#include <string>
#include <functional>
using handler_t = std::function<Response (Request &req)>;
// 只负责对报文进行各种解析工作
// 对解析出来的请求和应答,Parser应该归你处理吗??
class Parser
{
public:
Parser(handler_t handler):_handler(handler)
{}
std::string Parse(std::string &inbuffer)
{
LOG(LogLevel::DEBUG) << "inbuffer: \r\n" << inbuffer;
std::string send_str;
while (true)
{
std::string jsonstr;
// 1. 解析报文
int n = Protocol::Unpack(inbuffer, &jsonstr);
if (n < 0)
break;
else if (n == 0)
break; // 我已经将inbuffer所有完整报文处理完毕
else
{
LOG(LogLevel::DEBUG) << "jsonstr: \r\n" << jsonstr;
Request req;
// 2. 反序列化
if (!req.Deserialize(jsonstr))
{
return std::string();
}
// 3. 根据req->response, 具体的业务
Response resp = _handler(req);
// 4. 对resp在进行序列化
std::string resp_json;
if (!resp.Serialize(&resp_json))
{
return std::string();
}
// 5. 打包
send_str += Protocol::Package(resp_json);
}
}
return send_str;
}
~Parser() {}
private:
handler_t _handler;
};
10. Reactor.hpp -- 事件驱动核心
成员:
-
std::unique_ptr _epoller。
-
std::unordered_map<int, std::shared_ptr> _conns。
-
struct epoll_event _revs[MAX_EVENTS](用于存放就绪事件)。
核心方法:
- AddConnection(shared_ptr conn):
-
设置 conn->SetOwner(this)。
-
插入 _conns 表。
-
调用 _epoller->AddEvent(conn->Fd(), conn->Events())。
- DelConnection(int fd):
-
调用 _epoller->DelEvent(fd)。
-
从 _conns 中移除。
-
close(fd)。
- EnableReadWrite(int fd, bool read, bool write):
-
查找对应的 Connection。
-
根据 read/write 重新计算 events(保留 EPOLLET)。
-
更新 Connection::_events。
-
调用 _epoller->ModEvent(fd, new_events)。
- LoopOnce(int timeout):
-
调用 _epoller->Wait(_revs, MAX_EVENTS, timeout),获取就绪事件数量 n。
-
遍历 _revs[0...n-1]:
-
根据 data.fd 查找 Connection。
-
若事件包含 EPOLLHUP|EPOLLERR,调用 conn->Excepter()。
-
若包含 EPOLLIN,调用 conn->Recver()。
-
若包含 EPOLLOUT,调用 conn->Sender()。
- Dispatcher():无限循环调用 LoopOnce 和超时检查。
CheckTimeout():遍历 _conns,删除超时连接(通过 LastActive 时间戳)。
cpp
pragma once
#include <iostream>
#include <string>
#include <unordered_map>
#include <memory>
#include "Util.hpp"
#include "Epoller.hpp"
#include "Connection.hpp"
// Reactor : Reactor是一个反应堆,本质是利用多路转接,激活自己所管理的一个一个节点Connection
// Reactor : 容器!!!增删查改Connection
// 在Reactor角度,需要区分你是Listenersockfd,还是普通sockfd??不区分!
class Reactor
{
static const int size =128;
private:
bool IsExist(std::shared_ptr<Connection> &conn)
{
auto iter = _connections.find(conn->SockFd());
return iter != _connections.end();
}
bool IsExist(int sockfd)
{
auto iter = _connections.find(sockfd);
return iter != _connections.end();
}
public:
Reactor()
: _epoller(std::make_unique<Epoller>())
{
}
void AddConnection(std::make_ptr<Connection> &conn)
{
if(IsExist(conn))
{
LOG(LogLevel::INFO) << conn->Sockfd() << " conn in Reactor!";
return;
}
//0.让conn回指当前reactor
conn->SetOwner(this);
//1.conn插入到_connections
_connections.insert(std::make_pair(conn->SockFd(), conn));
//2.conn->sockfd,event 写透到内核里
_epoller->AddEvent(conn->Sockfd(), conn->Events())
LOG(LogLevel::INFO) << conn->Sockfd() << " conn add to Reactor";
}
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(scokfd, events);
}
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 LoopOnce(int timeout)
{
int n = _epoller->Wait(revs, szie, timeout);
for(int i = 0; i<n; i++)
{
int sockfd = revs[i].data.fd; //就绪的sockfd
uint32_t events = revs[i].events;
// 统一报错的处理方法!
if (events & EPOLLHUP)
events = (EPOLLIN | EPOLLOUT);
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 = 1000;
while(true)
{
//处理事件
LoopOnce(timeout);
ShowConnection();
CheckTimeOut();
}
}
void CheckTimeOut()
{
uint64_t currtime = time(nullptr);
for(auto &conn: _connections)
{
uint64_t t = currtime - conn.second->LastActive();
if(t > 5*60*1000)
{
// 随便写的
DelConnection(conn.second->Sockfd());
}
}
}
void ShowConnection()
{
std::cout << "#############################" << std::endl;
for(auto &conn: _connections)
{
std::cout << conn.second->Sockfd() << " : "
<< Event2String(conn.second->Events()) << std::endl;
}
std::cout << "#############################" << std::endl;
}
~Reactor()
{
}
private:
std::unordered_map<int, std::shared_ptr<Connection>> _connections;
std::unique_ptr<Epoller> _epoller;
struct epoll_event revs[size];
}
11. main.cc -- 组装
-
创建业务对象(如 Calculator)。
-
创建 Parser,绑定业务回调。
-
创建 Listener,设置其回调为 Parser::Parse。
-
创建 Reactor,调用 AddConnection(listener)。
-
启动 Reactor::Dispatcher()。
cpp
#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;
}

三者如何联动(一次请求的处理流程)
- 新客户端连接到达:
-
Listener::Recver 被 epoll 触发,循环 accept,创建 Channel 对象。
-
Channel 被添加到 Reactor,其回调被设置为 listener 的回调(即上面那个 lambda)。
- 客户端发送请求数据:
-
Channel::Recver 循环读取数据,存入 _inbuffer。
-
调用 _cb(_inbuffer),即执行 [&parse_protocol](std::string &inbuffer) { return parse_protocol->Parse(inbuffer); }。
- 协议解析:
-
parse_protocol->Parse(inbuffer) 被调用,它内部:
-
循环调用 Protocol::Unpack 提取完整 JSON 报文。
-
反序列化为 Request 对象。
-
调用自身构造时传入的 handler_t 回调,即 [&cal](Request &req) -> Response { return cal->Exec(req); }。
-
得到 Response,序列化为 JSON,打包成 长度\r\njson\r\n 格式。
-
返回拼接后的响应字符串。
- 响应发送:
-
_cb 返回的响应字符串被 Channel::Recver 追加到 _outbuffer。
-
Channel 启用写事件,最终通过 Channel::Sender 发送给客户端。
- 强制非阻塞 + 边缘触发(ET)
所有 socket 必须设为非阻塞,并采用 ET 模式。非阻塞是 ET 的前提,ET 可减少 epoll 的触发次数,提升高并发效率;代价是用户必须循环读写直到 EAGAIN。
-
监听套接字循环 accept 直到 EAGAIN
在 ET 模式下,监听套接字只通知一次新连接。必须循环调用 accept 直至返回 EAGAIN,才能将已完成队列中的所有连接取走,避免连接丢失。
-
普通连接循环 recv 直到 EAGAIN
ET 模式下可读事件只触发一次,因此必须在 Recver 中循环接收数据,直到内核返回 EAGAIN,确保接收缓冲区内的所有数据都被读取。
-
写事件按需注册,避免空转
仅在 _outbuffer 非空时才注册 EPOLLOUT 事件;发送完毕后立即注销。这样能防止 epoll 在无数据可写时频繁触发写事件,有效降低 CPU 消耗。
-
协议解包直接消费输入缓冲区(引用传递 + 修改)
协议解析层(Unpack 和 Parse)通过引用修改输入缓冲区,在处理完完整报文后立即从缓冲区中删除。这避免了上层重复解析,也防止缓冲区无限增长。
-
超时检查:定期清理不活跃连接
在 Reactor 事件循环中定期遍历所有连接,检查其最后活跃时间。超过阈值的连接将被关闭并清理,以此作为兜底机制,防止客户端异常断开造成的资源泄漏。
-
事件分发:根据 fd 查找对应的 Connection
epoll_event.data.fd 直接存放文件描述符,Reactor 通过 fd 在映射表中查找对应的 Connection 对象。这实现了 O(1) 的事件分发,并利用多态将具体处理交给派生类的 Recver/Sender/Excepter。
Reactor 不需要关心每个 fd 具体是什么类型的连接(监听套接字或普通数据套接字),也不需要遍历所有连接去匹配 fd。fd 就是索引,直接定位到对应的处理器对象。
-
回调链:业务逻辑与网络 IO 解耦
通过三层回调将业务(Calculator)、协议(Parser)、网络(Listener/Channel)完全分离。每层只通过回调接口通信,更换任意一层均不影响其他层,极大提高了可维护性和可扩展性。