【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)。这样就可以在访问对象之前,先检查对象是否还存在,避免了访问已经销毁的对象而导致的未定义行为。
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;
}
相关推荐
爱吃面条的猿1 小时前
Python修改pip install 指定安装包的路径和默认镜像源
linux·python·pip
饭饭大王6661 小时前
Python 模块的概念与导入:从基础语法到高级技巧
java·服务器·python
代码AC不AC1 小时前
【Linux】版本控制器Git
linux·git·gitee
佐杰1 小时前
Jenkins备份管理
java·运维·jenkins
杨云龙UP1 小时前
【MySQL逻辑备份】基于mysqldump的MySQL 8.0全量逻辑备份脚本
linux·运维·数据库·sql·mysql·mssql
t***L2661 小时前
GitLab API使用实例
运维·gitlab
BS_Li1 小时前
【Linux系统编程】基础IO
linux·服务器·文件操作
沐怡旸1 小时前
【穿越Effective C++】23.宁以non-member、non-friend替换member函数
c++·面试
ALex_zry1 小时前
Docker容器运维与故障排查实战手册
运维·docker·容器