完整教程:【Linux】Reactor

  • 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)。这样就可以在访问对象之前,先检查对象是否还存在,避免了访问已经销毁的对象而导致的未定义行为。
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 对象是有效且安全的。
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模式,一次监听注册对应一次触发,触发后监听自动失效。所以发送完数据后需要检擦缓冲区,若有数据剩余需要重新开启写事件监听,等待下次写事件触发
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)清理完,再删除映射表中的引用。
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

事件监听控制

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个就绪事件,依次进行遍历对异常进行/转化(后续回调时再处理,实现解耦),再对读写事件分别调用回调

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
void Loop()
{
_quit=false;
while(!_quit)
{
Dispatcher(-1);//阻塞等到事件就绪,节省cpu资源
PrintConnection();
}
_quit=true;
}

整体代码

#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 接收客户端数据后,会自动调用它,触发业务处理,作为网络层到业务层的桥梁
#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指令
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

#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

#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

#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

#pragma once
class nocopy
{
public:
nocopy(){}
nocopy(const nocopy&)=delete;//禁用拷贝构造
const nocopy& operator=(const nocopy&)=delete;//禁用拷贝赋值运算符
};

八 .Protocol.hpp

#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

#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

#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;
          }
posted @ 2025-12-16 15:53  clnchanpin  阅读(26)  评论(0)    收藏  举报