- Reactor模式简介:
以下代码实现的是 经典单线程 Reactor 模式------ 一种基于 "事件驱动" 的高并发 I/O 设计模式,核心是用一个 "反应器"(Epoller)统一监听所有 socket 事件,事件就绪后分发给对应处理器,全程单线程执行,避免多线程竞争,兼顾效率与简洁性。设计思想是 "不浪费 CPU 做无用功"------ 仅在事件就绪时处理,无事件时阻塞等待
特点:
- 单线程执行:
整个 Reactor 的 "等待事件→分发事件→处理事件" 全程在一个线程中执行(Loop 循环所在线程)
优势:无多线程竞争,不需要互斥锁,代码简单、无死锁风险;
局限:若某个处理器处理耗时过长(如复杂业务计算),会阻塞整个服务器,因此要求 "处理器只做 I/O 操作,业务逻辑尽量轻量化"(代码中 _OnMessage 回调就是为了隔离业务,便于后续优化为线程池)。 - 基于 epoll 边缘触发(ET)
事件仅在 "状态变化瞬间" 触发一次,事件触发次数少,CPU 利用率高,适合高并发场景 - 事件驱动 + 无轮询
完全由 "事件" 驱动执行,无事件时阻塞在 epoll_wait,不做无效轮询,资源占用极低 - 其他Reactor变体:
1.多线程 Reactor:Reactor 线程负责事件分发,业务处理 / 耗时操作交给线程池,适合高并发、业务复杂的场景(可基于当前代码扩展:_OnMessage 回调中提交任务到线程池);
2.主从 Reactor:多个 Reactor 线程,主 Reactor 负责接收新连接,从 Reactor 负责处理已连接的 I/O 事件,适合超大规模并发(如 Nginx 架构)。
一.TcpServer.hpp
- 简介两个类之间的关系;
TcpServer(服务器核心)和 Connection(连接封装)是 "一对多的依赖管理关系"------TcpServer 是 "管理者",负责创建、管理、销毁多个 Connection 对象;Connection 是 "被管理的连接实例",封装单个客户端的核心资源,通过弱引用依赖 TcpServer 实现事件回调和资源访问,两者通过 "智能指针 + 回调函数" 实现安全解耦。
TcpServer 负责全局资源(epoll、监听 socket、连接映射)和事件调度,保证高并发下的效率和秩序;
Connection 负责局部资源(单个连接的 fd、缓冲区、客户端信息)封装,让每个连接的状态独立,便于维护和调试
- 整体流程:
1.服务器启动:Init() 初始化监听 socket,注册 Accepter 回调到 epoll;
2.客户端连接:epoll 触发 EPOLLIN 事件,Accepter 调用 accept 建立新连接,创建 Connection 对象,注册 Recver/Sender/Excepter 回调,添加到 epoll 和 _connections;
3.数据接收:客户端发送数据,epoll 触发 EPOLLIN,Recver 循环读取数据存入 _inbuffer,调用 _OnMessage 交给上层业务处理;
4.业务处理:上层业务(如 Calculator)处理数据,生成响应写入 _outbuffer,触发 Sender;
5.数据发送:Sender 循环发送 _outbuffer 数据,发送不完则开启写事件监听,等待缓冲区空闲后再次触发发送;
6.连接关闭:客户端正常退出或异常断开,触发 Excepter,清理 epoll 监听、关闭 fd、从 _connections 移除连接。
Accepter
连接管理器
- weak_ptr的lock方法
std::weak_ptr 本身并不控制所指向对象的生命周期(不会增加对象的引用计数),但通过 lock() 方法,可以尝试获取一个指向该对象的 std::shared_ptr。如果 weak_ptr 所指向的对象仍然存在(即还没有被销毁),lock() 会返回一个有效的 shared_ptr,通过这个 shared_ptr 就可以安全地访问对象;如果对象已经被销毁,lock() 会返回一个空的 shared_ptr(nullptr)。这样就可以在访问对象之前,先检查对象是否还存在,避免了访问已经销毁的对象而导致的未定义行为。
cpp
void Accepter(std::weak_ptr<Connection> conn)
{
auto connection=conn.lock();
while(true)
{
struct sockaddr_in peer;
socklen_t len=sizeof(peer);
int sock=::accept(connection->SockFd(),(struct sockaddr *)&peer,&len);
if(sock>0)
{
uint16_t peerport=ntohs(peer.sin_port);
char ipbuf[128];
inet_ntop(AF_INET,&peer.sin_addr,ipbuf,sizeof(ipbuf));
lg(Debug,"get a new client,get info->[%s:%d], sockfd : %d",ipbuf,peerport,sock);
SetNonBlockOrDie(sock);
// listensock只需要设置_recv_cb, 而其他sock,读,写,异常
AddConnection(sock,EVENT_IN,
std::bind(&TcpServer::Recver,this,placeholders::_1),
std::bind(&TcpServer::Sender,this,placeholders::_1),
std::bind(&TcpServer::Excepter,this,placeholders::_1)
,ipbuf,peerport);
}
else
{
if(errno==EWOULDBLOCK) break;//没有新客户端连接到来
else if(errno==EINTR) continue;//系统调用被信号中断
else break;//异常错误
}
cout<<"连接接收成功"<<endl;
}
}
Recver
- 是 Reactor 服务器中接收客户端数据的核心业务逻辑------ 专门处理客户端连接 socket 的 EPOLLIN 事件(有数据可读时触发),通过非阻塞 recv 批量读取所有待处理数据,处理连接断开 / 异常情况,并触发业务层的消息解析
- 安全检查 Connection 是否存活
conn.expired():检查 weak_ptr 指向的 Connection 是否已销毁(若已销毁,直接返回,避免野指针访问);
conn.lock():将 weak_ptr (客户端连接对应的connection对象)升级为 shared_ptr(改连接的资源),确保后续操作的 Connection 对象是有效且安全的。
cpp
void Recver(std::weak_ptr<Connection> conn)
{
if(conn.expired()) return;
auto connection=conn.lock();
int sock=connection->SockFd();
while(true)
{
char buffer[g_buffer_size];
memset(buffer,0,sizeof(buffer));//每次读取前先清空,避免重复读取
ssize_t n=recv(sock,buffer,sizeof(buffer)-1,0);
if(n>0)
{
connection->AppendInBuffer(buffer);
}
else if(n==0)
{
lg(Info,"sockfd: %d,client info %s:%d quit ...",sock,connection->_ip.c_str(),connection->_port);
connection->_except_cb(connection);
return;
}
else{
if(errno==EWOULDBLOCK) break;
else if(errno==EINTR) continue;
else{
lg(Warning,"Sockfd:%d, client info %s:%d recv error...",sock,connection->_ip.c_str(),connection->_port);
connection->_except_cb(connection);
return;
}
}
}
//数据有了但不一定全,进行检测,如果有完整报文就进行处理
_OnMessage(connection);//业务层
}
Sender
- 关于发送缓冲区outbuffer的清空,不能用memset,因为只会将指定的字节数清零,不改变缓冲区的有效数据长度和范围,0也是有效字节,偏移量没有改变,send会将整个缓冲区当成数据发送,所以会造成重复。而erase会从容器中移动指定长度的数据,并且会更新容器的有效长度,剩下的部分就是未发送的数据,所以不会重复处理
- 整体逻辑为,写事件就绪时触发回调 → 尽可能发送缓冲区数据 → 若有剩余数据则继续监听写事件,直到数据发完为止。因为是ET模式,一次监听注册对应一次触发,触发后监听自动失效。所以发送完数据后需要检擦缓冲区,若有数据剩余需要重新开启写事件监听,等待下次写事件触发
cpp
void Sender(std::weak_ptr<Connection> conn)
{
if(conn.expired()) return;
auto connection=conn.lock();
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;
else if(errno==EINTR) continue;
else{
lg(Warning,"Sockfd:%d, client info %s:%d recv error...",connection->SockFd(),connection->_ip.c_str(),connection->_port);
connection->_except_cb(connection);
return;
}
}
}
if(!outbuffer.empty())
{
//还有数据,开启写事件关心
EnableEvent(connection->SockFd(),true,true);
}
else{
//没有数据了,关闭对写事件关心
EnableEvent(connection->SockFd(),true,false);
}
}
Excepter
- 异常连接的处理,清理步骤按"删监听→关 fd→删映射" 的顺序,可以避免无效操作和资源泄漏
1.先删 epoll 监听(EPOLL_CTL_DEL):
若先关闭 fd 再删监听,epoll 可能还会残留对该 fd 的监听(或触发无效事件)------ 因为 close(fd) 后,fd 可能被系统复用给新的 socket,导致 epoll 监听错对象。
2.再关闭 fd(close(fd)):
必须在删除监听后关闭,否则关闭 fd 后,epoll 对该 fd 的监听就成了 "无效监听",后续可能触发异常。
3.最后删映射表(_connections.erase(fd)):
映射表中的 shared_ptr 是连接对象的持有者之一,若先删映射表,Connection 可能被析构,导致后续 close(fd) 时调用 conn->SockFd() 出错(野指针)。必须等所有系统资源(监听、fd)清理完,再删除映射表中的引用。
cpp
void Excepter(std::weak_ptr<Connection> connection)
{
if(connection.expired()) return;
auto conn=connection.lock();
int fd=conn->SockFd();
lg(Warning,"Excepter hander sockfd:%d , client info %s:%d excepter handler",
fd,conn->_ip.c_str(),conn->_port);
//1.移除对特定fd的关心
_epoller_ptr->EpollerUpdate(EPOLL_CTL_DEL,fd,0);
//2.关闭异常的文件描述符
lg(Debug,"close %d done...\n",fd);
close(fd);
//3.从unordered_map中移除
lg(Debug,"remove %d from _connections...\n",fd);
_connections.erase(fd);
}
EnableEvent
事件监听控制
cpp
void EnableEvent(int sock,bool readable,bool writeable)
{
uint32_t events=0;
events |= ((readable?EPOLLIN:0)|(writeable ? EPOLLOUT:0)|EPOLLET);
_epoller_ptr->EpollerUpdate(EPOLL_CTL_MOD,sock,events);
}
IsConnectionSafe
- 函数仅检验fd在映射表中是否存在,但不保证fd在socket上有效,所以不能替代 weak_ptr 的 expired() 检查,在获取 Connection 对象后,仍需通过智能指针保证安全性(如 lock() 升级)在获取 Connection 对象后,仍需通过智能指针保证安全性(如 lock() 升级)
bool IsConnectionSafe(int fd)
{
auto it=_connections.find(fd);
if(it==_connections.end()) return false;
else return true;
}
Dispatcher
事件派发器:
通过调用封装epoll_wait的函数来进行监听等待,当事件就绪后会返回n个就绪事件,依次进行遍历对异常进行/转化(后续回调时再处理,实现解耦),再对读写事件分别调用回调
cpp
void Dispatcher(int timeout)
{
int n=_epoller_ptr->EpollerWait(revs,num,timeout);
for(int i=0;i<n;i++)
{
uint32_t events=revs[i].events;
int sock=revs[i].data.fd;
//统一把异常事件转化为读写问题,在回调函数中通过errno具体值来判断异常并处理
if(events&EPOLLERR) events|=(EPOLLIN|EPOLLOUT);
if(events&EPOLLHUP) events|=(EPOLLIN|EPOLLOUT);
if((events&EPOLLIN)&&IsConnectionSafe(sock))
{
if(_connections[sock]->_recv_cb)//判断回调函数存在
_connections[sock]->_recv_cb(_connections[sock]);
}
if((events&EPOLLOUT)&&IsConnectionSafe(sock))
{
if(_connections[sock]->_send_cb)//判断回调函数存在
_connections[sock]->_send_cb(_connections[sock]);
}
}
}
Loop
- 核心职责:
初始化服务运行状态(_quit = false);
无限循环执行 "事件分发(Dispatcher)+ 状态打印(PrintConnection)";
基于 _quit 标志控制服务启停,确保优雅退出。 - _quit如何被触发
1.服务收到退出信号(如 SIGINT Ctrl+C、SIGTERM 正常退出):注册信号处理函数,在函数中设置 _quit = true。
2.服务内部错误(如配置加载失败、epoll 初始化失败):在错误处理逻辑中设置 _quit = true,让主循环退出,服务优雅关闭。
手动触发退出(如运维通过命令发送退出指令):服务监听指令,收到后设置 _quit = true
cpp
void Loop()
{
_quit=false;
while(!_quit)
{
Dispatcher(-1);//阻塞等到事件就绪,节省cpu资源
PrintConnection();
}
_quit=true;
}
整体代码
cpp
#pragma once
#include <iostream>
#include<string>
#include<memory>
#include<cerrno>
#include<functional>
#include<unordered_map>
#include"log.hpp"
#include"nocopy.hpp"
#include"Epoller.hpp"
#include"Socket.hpp"
#include"Comm.hpp"
using namespace std;
//前置声明,两个类会互相用到对方,解决循环依赖
class Connection;
class TcpServer;
uint32_t EVENT_IN=(EPOLLIN|EPOLLET);//读事件关心+边缘模式触发
uint32_t EVENT_OUT=(EPOLLOUT|EPOLLET);
const static int g_buffer_size=128;
using func_t =std::function<void(std::weak_ptr<Connection>)>;//正常事件的回调函数
using except_func=std::function<void(std::weak_ptr<Connection>)>;//异常事件的回调
class Connection
{
public:
Connection(int sock): _sock(sock)
{}
void SetHandler(func_t recv_cb,func_t send_cb,except_func except_cb)
{
_recv_cb=recv_cb;
_send_cb=send_cb;
_except_cb=except_cb;
}
int SockFd() {return _sock;}
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;
}
void SetWeakPtr(std::weak_ptr<TcpServer> tcp_server_ptr)
{
_tcp_server_ptr=tcp_server_ptr;
}
~Connection()
{}
private:
int _sock;
std::string _inbuffer;
std::string _outbuffer;
public:
//三个回调成员,实现事件触发到业务处理,connection只负责事件触发,业务逻辑由TcpServer注册进来
func_t _recv_cb;
func_t _send_cb;
except_func _except_cb;
//回调指针,让连接器能访问讷服务器全局资源,Connection是由服务器创建的,需要建立关系来处理连接
std::weak_ptr<TcpServer> _tcp_server_ptr;
std::string _ip;
uint16_t _port;
};
class TcpServer:public std::enable_shared_from_this<TcpServer>, public nocopy
{
static const int num=128;
public:
TcpServer(uint16_t port,func_t OnMessage)
:_port(port),
_OnMessage(OnMessage),
_quit(true),
_listensock_ptr(new Sock()),
_epoller_ptr(new Epoller())
{}
~TcpServer()
{}
void Init()
{
_listensock_ptr->Socket();
SetNonBlockOrDie(_listensock_ptr->Fd());
_listensock_ptr->Bind(_port);
_listensock_ptr->Listen();
lg(Info,"create listen socket success: %d",_listensock_ptr->Fd());
AddConnection(_listensock_ptr->Fd(),EVENT_IN, std::bind(&TcpServer::Accepter,this,std::placeholders::_1),nullptr,nullptr);
}
//连接注册入口
void AddConnection(int sock,uint32_t event,func_t recv_cb,func_t send_cb,
except_func excpt_cb,const std::string &ip="0.0.0.0",uint16_t port=0)
{
//1. 给sock也建立一个connection对象,将listensock添加到Connection中,
//同时,listensock和Connecion放入_connections
std::shared_ptr<Connection> new_connnection(new Connection(sock));
new_connnection->SetWeakPtr(shared_from_this());//返回当前 TcpServer 实例的 shared_ptr
new_connnection->SetHandler(recv_cb,send_cb,excpt_cb);
new_connnection->_ip=ip;
new_connnection->_port=port;
//2.添加到unordered_map,将套接字与对应方法建立对应关系,由服务器管理
_connections.insert(std::make_pair(sock,new_connnection));
//3.添加对应事件到内核中,注册epoll事件
_epoller_ptr->EpollerUpdate(EPOLL_CTL_ADD,sock,event);
cout<<"连接注册成功"<<endl;
}
//连接管理器
void Accepter(std::weak_ptr<Connection> conn)
{
auto connection=conn.lock();
while(true)
{
struct sockaddr_in peer;
socklen_t len=sizeof(peer);
int sock=::accept(connection->SockFd(),(struct sockaddr *)&peer,&len);
if(sock>0)
{
uint16_t peerport=ntohs(peer.sin_port);
char ipbuf[128];
inet_ntop(AF_INET,&peer.sin_addr,ipbuf,sizeof(ipbuf));
lg(Debug,"get a new client,get info->[%s:%d], sockfd : %d",ipbuf,peerport,sock);
SetNonBlockOrDie(sock);
// listensock只需要设置_recv_cb, 而其他sock,读,写,异常
AddConnection(sock,EVENT_IN,
std::bind(&TcpServer::Recver,this,placeholders::_1),
std::bind(&TcpServer::Sender,this,placeholders::_1),
std::bind(&TcpServer::Excepter,this,placeholders::_1)
,ipbuf,peerport);
}
else
{
if(errno==EWOULDBLOCK) break;//没有新客户端连接到来
else if(errno==EINTR) continue;//系统调用被信号中断
else break;//异常错误
}
cout<<"连接接收成功"<<endl;
}
}
//事件管理器,只需要处理IO工作,不需要关心数据格式
void Recver(std::weak_ptr<Connection> conn)
{
if(conn.expired()) return;
auto connection=conn.lock();
int sock=connection->SockFd();
while(true)
{
char buffer[g_buffer_size];
memset(buffer,0,sizeof(buffer));//每次读取前先清空,避免重复读取
ssize_t n=recv(sock,buffer,sizeof(buffer)-1,0);
if(n>0)
{
connection->AppendInBuffer(buffer);
}
else if(n==0)
{
lg(Info,"sockfd: %d,client info %s:%d quit ...",sock,connection->_ip.c_str(),connection->_port);
connection->_except_cb(connection);
return;
}
else{
if(errno==EWOULDBLOCK) break;
else if(errno==EINTR) continue;
else{
lg(Warning,"Sockfd:%d, client info %s:%d recv error...",sock,connection->_ip.c_str(),connection->_port);
connection->_except_cb(connection);
return;
}
}
}
//数据有了但不一定全,进行检测,如果有完整报文就进行处理
_OnMessage(connection);//业务层
}
//网络发送
void Sender(std::weak_ptr<Connection> conn)
{
if(conn.expired()) return;
auto connection=conn.lock();
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;
else if(errno==EINTR) continue;
else{
lg(Warning,"Sockfd:%d, client info %s:%d recv error...",connection->SockFd(),connection->_ip.c_str(),connection->_port);
connection->_except_cb(connection);
return;
}
}
}
if(!outbuffer.empty())
{
//还有数据,开启写事件关心
EnableEvent(connection->SockFd(),true,true);
}
else{
//没有数据了,关闭对写事件关心
EnableEvent(connection->SockFd(),true,false);
}
}
//异常连接处理
void Excepter(std::weak_ptr<Connection> connection)
{
if(connection.expired()) return;
auto conn=connection.lock();
int fd=conn->SockFd();
lg(Warning,"Excepter hander sockfd:%d , client info %s:%d excepter handler",
fd,conn->_ip.c_str(),conn->_port);
//1.移除对特定fd的关心
_epoller_ptr->EpollerUpdate(EPOLL_CTL_DEL,fd,0);
//2.关闭异常的文件描述符
lg(Debug,"close %d done...\n",fd);
close(fd);
//3.从unordered_map中移除
lg(Debug,"remove %d from _connections...\n",fd);
_connections.erase(fd);
}
//事件监听控制
void EnableEvent(int sock,bool readable,bool writeable)
{
uint32_t events=0;
events |= ((readable?EPOLLIN:0)|(writeable ? EPOLLOUT:0)|EPOLLET);
_epoller_ptr->EpollerUpdate(EPOLL_CTL_MOD,sock,events);
}
bool IsConnectionSafe(int fd)
{
auto it=_connections.find(fd);
if(it==_connections.end()) return false;
else return true;
}
//事件派发器
void Dispatcher(int timeout)
{
int n=_epoller_ptr->EpollerWait(revs,num,timeout);
for(int i=0;i<n;i++)
{
uint32_t events=revs[i].events;
int sock=revs[i].data.fd;
//统一把异常事件转化为读写问题,在回调函数中通过errno具体值来判断异常并处理
if(events&EPOLLERR) events|=(EPOLLIN|EPOLLOUT);
if(events&EPOLLHUP) events|=(EPOLLIN|EPOLLOUT);
if((events&EPOLLIN)&&IsConnectionSafe(sock))
{
if(_connections[sock]->_recv_cb)//判断回调函数存在
_connections[sock]->_recv_cb(_connections[sock]);
}
if((events&EPOLLOUT)&&IsConnectionSafe(sock))
{
if(_connections[sock]->_send_cb)//判断回调函数存在
_connections[sock]->_send_cb(_connections[sock]);
}
}
}
void Loop()
{
_quit=false;
while(!_quit)
{
Dispatcher(-1);//阻塞等到事件就绪,节省cpu资源
PrintConnection();
}
_quit=true;
}
void PrintConnection()
{
cout<<"_connections fd list: ";
for(auto &c:_connections)
{
cout<<c.second->SockFd()<<", ";
if(!c.second->InBuffer().empty())
{
cout<<"inbuffer: "<<c.second->InBuffer().c_str();//帮助调试,数据是否满足预期
}
else {
cout<<"读取完毕"<<endl;
break;
}
}
cout<<endl;
}
private://智能指针和容器的设计,都是为了保证内存安全、提升性能、解耦模块
shared_ptr<Epoller> _epoller_ptr;//核心事件管理器,用于处理所有IO事件
shared_ptr<Sock> _listensock_ptr;//管理监听套接字,是Reactor模式的入口
unordered_map<int,std::shared_ptr<Connection>> _connections;//连接管理器,可快速找到每个套接字所对应的connection
struct epoll_event revs[num];//就绪事件数组
uint16_t _port;
bool _quit;//退出控制标志,控制主循环的启停、避免强制终止导致的资源泄漏与连接异常等问题,适配信号响应主动命令异常处理等多种退出场景
func_t _OnMessage;//让上层处理信息
};
二.main.cc
- 全局业务对象:Calculator calculator;
所有客户端的计算请求,都通过这个统一的 calculator 对象处理; - 业务回调函数:
这是核心粘合函数,TcpServer 接收客户端数据后,会自动调用它,触发业务处理,作为网络层到业务层的桥梁
cpp
#include "TcpServer.hpp"//处理IO
#include"Calculator.hpp"//处理业务的
#include<memory>
#include"log.hpp"
#include<functional>
#include<iostream>
Calculator calculator;
void DefaultOnMessage(weak_ptr<Connection>conn)
{
if(conn.expired()) return;
auto connection_ptr=conn.lock();
//对报文进行处理,这里没有对粘包和拆包问题进行处理可能有bug
cout<<"得到了上层的数据:"<<connection_ptr->InBuffer()<<endl;
string response_str=calculator.Handler(connection_ptr->InBuffer());
//connection_ptr->InBuffer().clear()清空缓冲区,避免下次写入时追加
if(response_str.empty()) return;
lg(Debug,"%s",response_str.c_str());
//发送数据到tcp服务器的发送缓冲区中
connection_ptr->AppendOutBuffer(response_str);
connection_ptr->_send_cb(connection_ptr);//让回调指针绑定当前连接,访问当前资源
//这样的方式也可以,直接调用,上面属于回调
// auto tcpserver=connection_ptr->_tcp_server_ptr.lock();
// tcpserver->Sender(connection_ptr);
}
int main()
{
shared_ptr<TcpServer> epoll_svr(new TcpServer(8888,DefaultOnMessage));
epoll_svr->Init();
epoll_svr->Loop();
return 0;
}
三.CMakeLists.txt
cmake简单指令介绍
- 1.隔离创建目录,在其中隔离生成编译产生的中间文件
编译过程会生成大量中间文件,隔离目录可以避免污染源代码,可以同时支持debug和release模式,不用全部删除重新来;若重新构建,直接删除对应的构建目录(如 rm -rf build_debug),重新创建并执行 cmake 即可,没有任何残留;多人协作时,每个人都在自己的 build 目录构建,不会因环境不同产生冲突(CMake 会在各自的 build 目录生成独立的缓存和 Makefile) - 2.配置cmake,使用cmake ...指令(...代表源代码目录在上级)
常用选项(根据需求添加):
无选项(默认):使用系统默认编译器(gcc/g++),生成 Makefile
-DCMAKE_BUILD_TYPE=Debug:编译 Debug 版本(含调试信息,支持 gdb 调试)
-DCMAKE_BUILD_TYPE=Release:编译 Release 版本(优化代码,运行更快)
-DCMAKE_INSTALL_PREFIX=/usr/local:指定安装路径(默认是 /usr/local,中间文件还是会在build目录下生成,可执行文件才会在指定安装路径下生成) - 编译项目,使用make指令
cpp
cmake_minimum_required(VERSION 3.10)#声明CMake版本要求
project(Reactor)#定义项目名称
#指定c++11编译标准
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_executable(reactor_server main.cc)#指定可执行文件的名称和源代码,让CMake自动生成编译规则
target_link_libraries(reactor_server jsoncpp)
add_executable(client ClientCal.cc)
target_link_libraries(client jsoncpp)
四.Epoller.hpp
cpp
#pragma once
#include"log.hpp"
#include<cerrno>
#include<cstring>
#include<sys/epoll.h>
#include"nocopy.hpp"
class Epoller:public nocopy//通过继承来禁止拷贝和赋值
{
static const int size=128;
public:
Epoller()
{
_epfd=epoll_create(size);
if(_epfd==-1) lg(Error,"epoll_create error: %s",strerror(errno));
else lg(Info,"epoll_create success: %s",strerror(errno));
}
int EpollerWait(struct epoll_event revents[],int num,int timeout)
{
int n=epoll_wait(_epfd,revents,num,/*_timeout*/-1);
return n;
}
int EpollerUpdate(int op,int sock,uint32_t event)
{
int n=0;
if(op==EPOLL_CTL_DEL)
{
n=epoll_ctl(_epfd,op,sock,0);
if(n!=0) lg(Error,"epoll_ctl delete error!");
}
else{//增加和修改
struct epoll_event ev;
ev.data.fd=sock;//方便后续得知哪一个fd就绪了
ev.events=event;
n=epoll_ctl(_epfd,op,sock,&ev);
if(n!=0) lg(Error,"epoll_ctl error!");
}
return n;
}
~Epoller()
{
if(_epfd>0) close(_epfd);
}
private:
int _epfd;
int _timeout{3000};
};
五. Comm.hpp
cpp
#pragma once
#include<unistd.h>
#include<fcntl.h>
void SetNonBlockOrDie(int sock)
{
int fl=fcntl(sock,F_GETFL);
if(fl<0) _exit(0);
fcntl(sock,F_SETFL,fl|O_NONBLOCK);
}
六. log.hpp
cpp
#pragma once
using namespace std;
#include<iostream>
#include<time.h>
#include<stdarg.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
#include<string>
#include<cstdio>
#include<cerrno>
#include<cstddef>
#define SIZE 1024
//日志级别,按重要性划分,从普通信息到致命错误,数字是日志级别的标识实现高效的逻辑判断、传递和存储
#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4
//输出方式:控制日志输出到屏幕、单个文件或按级别分文件;
#define Screen 1
#define Onefile 2
#define Classfile 3
#define LogFile "log.txt"
class Log
{
public:
Log()
{
PrintMethod=Screen;// 默认输出到屏幕
path="./Log/"; // 默认日志路径
}
void Enable(int method)//决定输出方式
{
PrintMethod=method;
}
string levelToString(int level)//辅助转化函数
{//将表示日志级别的整数(如 0、1)转换为对应的字符(如"Info"、"Debug")
switch(level)
{
case Info:
return "Info";
case Debug:
return "Debug";
case Warning:
return "Warning";
case Error:
return "Error";
case Fatal:
return "Fatal";
default:
return "None";
}
}
//核心输出控制器,作用是根据当前配置的输出方式(PrintMethod),将格式化好的日志内容(logtxt)分发到不同的目标(屏幕 / 单个文件 / 按级别分类的文件)
void PrintLog(int level,const string &logtxt)
{
switch(PrintMethod)
{
case Screen:
cout<<logtxt<<endl;
break;
case Onefile:
PrintOneFile(LogFile,logtxt);
break;
case Classfile:
PrintClassFile(level,logtxt);
break;
default:
break;
}
}
//将日志内容(logtxt)追加写入到指定的日志文件(logname)中
void PrintOneFile(const string &logname,const string &logtxt)
{
string _logname=path+logname;
int fd=open(_logname.c_str(),O_WRONLY|O_CREAT|O_APPEND,0666);
if(fd<0)
{
perror("open: ");
return;}
write(fd,logtxt.c_str(),logtxt.size());
close(fd);
}
//作用是为不同级别的日志创建独立的文件名,然后调用 PrintOneFile 函数将日志写入对应文件,实现日志的分级管理
void PrintClassFile(int level,const string &logtxt)
{
string filename=LogFile;
filename+=".";
filename+=levelToString(level);
PrintOneFile(filename,logtxt);
}
~Log()//析构让这个类看起来完整一些
{
}
//日志生成函数,通过重载 () 操作符,让日志调用像函数一样简洁
void operator()(int level,const char *format,...)
{
time_t t=time(nullptr);// 获取当前时间戳(从1970年到现在的秒数)
struct tm *ctime=localtime(&t);
char leftbuffer[SIZE];// 缓冲区,存储级别和时间前缀
snprintf(leftbuffer,sizeof(leftbuffer),"[%s][%d-%d-%d %d:%d:%d]",levelToString(level).c_str(),
ctime->tm_year+1900,ctime->tm_mon+1,ctime->tm_mday,
ctime->tm_hour,ctime->tm_min,ctime->tm_sec);
va_list s;// 可变参数列表(用于接收用户传入的 format 后的参数)
va_start(s,format);// 初始化参数列表,绑定到 format 后的参数
char rightbuffer[SIZE];// 缓冲区,存储用户消息
vsnprintf(rightbuffer,sizeof(rightbuffer),format,s);
va_end(s);
// 格式:默认部分+自定义部分
char logtxt[SIZE*2];// 缓冲区,存储完整日志
snprintf(logtxt,sizeof(logtxt),"%s %s\n",leftbuffer,rightbuffer); // 拼接前缀和用户消息
PrintLog(level,logtxt); // 调用输出函数,根据配置输出到屏幕/文件
}
private:
int PrintMethod;
string path;
};
Log lg;
七. nocopy.hpp
cpp
#pragma once
class nocopy
{
public:
nocopy(){}
nocopy(const nocopy&)=delete;//禁用拷贝构造
const nocopy& operator=(const nocopy&)=delete;//禁用拷贝赋值运算符
};
八 .Protocol.hpp
cpp
#pragma once
#include<iostream>
#include<string>
#include <jsoncpp/json/json.h>
using namespace std;
#define Myself 1
const string blank_space_sep=" ";
const string protocal_sep="\n";
string Encode(string &content)
{//添加报头信息
string package=to_string(content.size());//有效载荷长度
package+=protocal_sep;
package+=content;
package+=protocal_sep;
return package;
}
bool Decode(string &package,string *content)
{
size_t pos=package.find(protocal_sep);
if(pos==string::npos) return false;
string len_str=package.substr(0,pos);
size_t len=stoi(len_str);
size_t total_len=len_str.size()+len+2;//判断整个长度是否符合协议
if(package.size()<total_len) return false;
*content=package.substr(pos+1,len);
//删除已解析的报文
package.erase(0,total_len);
return true;
}
class Request
{
public:
Request(int data1,int data2,char oper) :x(data1),y(data2),op(oper)
{}
Request()//适配反序列化场景,先创建一个空对象,接收读取的字节流
{}
~Request()
{}
public:
bool Serialize(string *out)
{
#ifdef MySelf//使用条件编译
//构建报文的有效载荷 "x op y
string s=to_string(x);
s+=blank_space_sep;
s+=op;
s+=blank_space_sep;
s+=to_string(y);
*out=s;
return true;
#else
Json::Value root;// 初始化一个空的 JSON 节点(默认是 null)
root["x"]=x;
root["y"]=y;
root["op"]=op;
//Json::FastWriter w;//将对象转换为 JSON 字符串的工具类(即 "序列化" 操作)
Json::StyledWriter w;// 创建格式化写入器
*out=w.write(root);// 将 root 转换为字符串,存入 out 指向的内存
return true;
#endif
}
bool Deserialize(const string &in)
{
#ifdef MySelf
size_t left=in.find(blank_space_sep);
if(left==string::npos) return false;
string part_x=in.substr(0,left);
size_t right=in.rfind(blank_space_sep);//反向查找
if(right==string::npos) return false;
string part_y =in.substr(right+1);
if(left+2!=right) return false;//判断字符串格式是否正确
op=in[left+1];
x=stoi(part_x);
y=stoi(part_y);
return true;
#else
Json::Value root;// 空容器,准备存储解析后的 JSON 数据
Json::Reader r;// 解析器实例,负责字符串到 JSON 结构的转换
r.parse(in,root);
x=root["x"].asInt();
y=root["y"].asInt();
op=root["op"].asInt();
return true;
#endif
}
void DebugPrint()
{
cout<<"新请求构建完成:"<<x<<op<<y<<"=?"<<endl;
}
public:
//序列化为 x op y的形式
int x;
int y;
char op;//+ - * / %
};
class Response
{
public:
Response(int res,int c) :result(res),code(c)
{}
Response() {}
~Response()
{}
public:
bool Serialize(string *out)
{
#ifdef MySelf
string s=to_string(result);
s+=blank_space_sep;
s+=to_string(code);
*out=s;
return true;
#else
Json::Value root;
root["result"]=result;
root["code"]=code;
//Json::FastWriter w;
Json::StyledWriter w;
*out=w.write(root);
return true;
#endif
}
bool Deserialize(string &in)
{
#ifdef MySelf
size_t pos=in.find(blank_space_sep);
if(pos==string::npos) return false;
string part_left=in.substr(0,pos);
string part_right=in.substr(pos+1);
result=stoi(part_left);
code=stoi(part_right);
return true;
#else
Json::Value root;
Json::Reader r;
r.parse(in,root);
result=root["result"].asInt();
code=root["code"].asInt();
return true;
#endif
}
void DebugPrint()
{
cout<<"结果响应完成,result:"<<result<<", code: "<<code<<endl;
}
public:
int result;
int code;
};
九.Socket.hpp
cpp
#pragma once
#include <iostream>
#include<string>
#include<unistd.h>
#include<cstring>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include "log.hpp"
enum{
SocketErr=2,
BindErr,
ListenErr,
};
const int backlog=10;
class Sock
{
public:
Sock()
{}
~Sock()
{}
public:
void Socket()
{
sockfd_=socket(AF_INET,SOCK_STREAM,0);
if(sockfd_<0)
{
lg(Fatal,"socker error,%s:%d",strerror(errno),errno);
exit(SocketErr);
}
}
void Bind(uint16_t port)
{
struct sockaddr_in local;
memset(&local,0,sizeof(local));
local.sin_family=AF_INET;
local.sin_port=htons(port);
local.sin_addr.s_addr=INADDR_ANY;
if(bind(sockfd_,(struct sockaddr*)&local,sizeof(local))<0)
{
lg(Fatal,"bind error,%s:%d",strerror(errno),errno);
exit(BindErr);
}
}
void Listen()
{
if(listen(sockfd_,backlog)<0)
{
lg(Fatal,"listen error,%s:%d",strerror(errno),errno);
exit(ListenErr);
}
}
int Accept(string *clientip,uint16_t *clientport)//输出型参数携带客户端信息
{
struct sockaddr_in peer;
socklen_t len=sizeof(peer);
int newfd=accept(sockfd_,(struct sockaddr*)&peer,&len);
if(newfd<0)
{
lg(Warning,"accept error,%s:%d",strerror(errno),errno);
return -1;
}
char ipstr[64];
inet_ntop(AF_INET,&peer.sin_addr,ipstr,sizeof(ipstr));
*clientip=ipstr;
*clientport=ntohs(peer.sin_port);
return newfd;
}
bool Connect(const string &ip,const uint16_t &port)
{
struct sockaddr_in peer;
memset(&peer,0,sizeof(peer));
peer.sin_family=AF_INET;
peer.sin_port=htons(port);
inet_pton(AF_INET,ip.c_str(),&(peer.sin_addr));
int n=connect(sockfd_,(struct sockaddr*)&peer,sizeof(peer));
if(n==-1)
{
cerr<<"connect to"<<ip<<":"<<port<<" error"<<endl;
return false;
}
return true;
}
void Close()
{
close(sockfd_);
}
int Fd()
{
return sockfd_;
}
private:
int sockfd_;
};
十. ClientCal.cc
cpp
#include <iostream>
#include <string>
#include <ctime>
#include<cassert>
#include<unistd.h>
#include"Socket.hpp"
#include"Protocol.hpp"
static void Usage(const string &proc)
{
cout<<"\nUsage"<<proc<<" serverip serverport\n"<<endl;
}
int main(int argc,char*argv[])
{
if(argc!=3)
{
Usage(argv[0]);
exit(0);
}
string serverip=argv[1];
uint16_t serverport=stoi(argv[2]);
Sock sockfd;
sockfd.Socket();
bool r=sockfd.Connect(serverip,serverport);
if(!r) return 1;
srand(time(nullptr));
int cnt=1;
const string opers="+-*/%=&^";
string inbuffer_stream;
while(cnt<=10)
{
cout<<"------------------"<<cnt<<"次测试"<<"--------------------"<<endl;
int x=rand()%100+1;//通过随机数来构建请求
int y=rand()%100;
char oper=opers[rand()%opers.size()];
Request req(x,y,oper);
req.DebugPrint();//打印新请求构建完成
string package;
req.Serialize(&package);
package=Encode(package);
write(sockfd.Fd(),package.c_str(),package.size());//向服务器发送请求
char buffer[128];
ssize_t n=read(sockfd.Fd(),buffer,sizeof(buffer));//接收处理过的信息
if(n>0)
{
buffer[n]=0;
inbuffer_stream+=buffer;//不断获取字节流信息,有可能是完整的报文或多个或不足
cout<<inbuffer_stream<<endl;
string content;
bool r=Decode(inbuffer_stream,&content);
assert(r);
Response resp;
r=resp.Deserialize(content);
assert(r);
resp.DebugPrint();//打印结果相应完成
}
cout<<"-----------------------------------------------"<<endl;
sleep(1);
cnt++;
}
sockfd.Close();
return 0;
}