epoll ET服务器(Reactor模式)

核心设计模式:Reactor(反应器)模式。

构建一个单线程、非阻塞、边缘触发(ET)、事件驱动的 TCP 服务器框架。该框架能够同时处理数千个客户端连接,支持自定义应用层协议(我们以计算器为例,使用 长度\r\nJSON\r\n 格式),并实现业务逻辑与网络 IO 的完全解耦。

一、角色构成

角色名称 在代码中的对应 职责描述
事件源 (Event Source) 文件描述符(socket) 产生事件的对象,如监听套接字(有新连接)、普通套接字(可读/可写/出错)。
事件分离器 (Event Demultiplexer) Epoller 类 等待事件的发生,并返回就绪的事件列表。这里使用 epoll 作为同步 I/O 多路复用器。
反应器 (Reactor) Reactor 类 注册/注销事件源及其处理器,持有事件分离器,在事件循环中获取就绪事件并分发给相应的处理器。
事件处理器 (Event Handler) Connection 派生类(Listener, Channel) 定义处理特定事件的方法:Recver()(可读)、Sender()(可写)、Excepter()(异常/关闭)。
具体事件处理器 (Concrete Handler) Listener, Channel 实现具体的读写、接受连接等业务逻辑。

二、工作流程(以单线程 Reactor 为例)

  1. 初始化阶段
  • 创建 Reactor 对象(内部创建 Epoller)。

  • 创建 Listener(监听套接字),设置其回调(协议解析函数)。

  • 调用 Reactor::AddConnection(listener),将 listener 的 fd 和事件(EPOLLIN | EPOLLET)注册到 Epoller 中。

  1. 事件循环(Reactor::Dispatcher)
c 复制代码
while (true) {
    LoopOnce(timeout);   // 等待并分发事件
    CheckTimeout();      // 处理超时连接
}
  1. 事件等待与分发(Reactor::LoopOnce)
  • 调用 _epoller->Wait(_revs, max, timeout),阻塞等待就绪事件。

  • 返回后,遍历 _revs 数组:

  • 根据 data.fd 从 _conns 映射表中找到对应的 Connection 对象。

  • 若事件包含 EPOLLHUP 或 EPOLLERR,调用 conn->Excepter()。

  • 若事件包含 EPOLLIN,调用 conn->Recver()。

  • 若事件包含 EPOLLOUT,调用 conn->Sender()。

  1. 事件处理器执行(以普通连接 Channel 为例)
  • 可读事件(Recver):
  • 循环 recv 直到 EAGAIN,数据追加到 _inbuffer。

  • 调用业务回调 _cb(_inbuffer)(即 Parser::Parse),后者会解析 _inbuffer 中的完整报文,生成响应,并 消费掉已处理的报文。

  • 响应存入 _outbuffer。

  • 若 _outbuffer 非空,调用 Reactor::EnableReadWrite(fd, true, true),即向 Epoller 注册 EPOLLOUT 事件。

  • 可写事件(Sender):
  • 循环 send 直到 EAGAIN 或 _outbuffer 空。

  • 发送完毕后,若 _outbuffer 为空,调用 Reactor::EnableReadWrite(fd, true, false),移除 EPOLLOUT 事件。

  • 异常处理(Excepter):
  • 调用 Reactor::DelConnection(fd),从 Epoller 中删除 fd,关闭连接,从映射表中移除。
  1. 超时检查(CheckTimeout)
  • 遍历所有 Connection,若当前时间与 LastActive() 之差超过阈值,则调用 DelConnection 清理。

设计要点总结

  • 事件源与处理器一一对应:每个 fd 对应一个 Connection 子类实例,Reactor 只负责调度,不关心具体处理逻辑。

  • 分离事件注册与等待:Epoller 只提供系统调用封装,Reactor 管理生命周期和事件修改。

  • 边缘触发(ET)与循环读写:要求处理器内部循环处理所有数据,直至 EAGAIN,这是高性能的保证。

  • 写事件按需注册:仅在 _outbuffer 有数据时才添加 EPOLLOUT,避免无效唤醒。

  • 回调分层:网络层(Channel)调用协议层(Parser),协议层调用业务层(Calculator),各层职责清晰。这个在main.cc中会重点提到

三、模块划分

1. Util.hpp

职责:提供通用的底层工具函数。

核心函数:

  • SetNonBlock(int fd):使用 fcntl 设置文件描述符为非阻塞模式。这是 ET 模式的前提。

  • Event2String(uint32_t events):将 epoll 事件掩码转为可读字符串(用于调试)。

cpp 复制代码
#pragma once
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <sys/epoll.h>

void SetNonBlock(int fd)
{
    int fl = fcntl(fd, F_GETFL);//获取文件描述符的当前状态标
    if(f1<0)
    {
        return;
    }
    fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}

std::string Event2String(uint32_t events)
{
    std::string s;
    if (events & EPOLLIN)
    {
        s = "EPOLLIN";
    }
    if (events & EPOLLOUT)
    {
        s += "|EPOLLOUT";
    }
    if (events & EPOLLET)
    {
        s += "|EPOLLET";
    }
    return s;
}

2. InetAddr.hpp

职责:封装 sockaddr_in,统一处理网络地址和主机地址的转换。

提供接口:

  • 构造函数(端口、IP)。

  • Ip()、Port()、ToString()。

  • Addr() 返回 struct sockaddr*,Length() 返回长度。

意义:让上层代码不再直接操作 sockaddr_in,提高可读性。

cpp 复制代码
#pragma once

// 这个类,描述client socket信息的类
// 方便我们后续用它来管理客户端

#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#define Conv(addr) ((struct sockaddr*)&addr)

class InetAddr
{
private:
    void Net2Host()
    {
        _port = ntohs(_addr.sin_port);
        // _ip = inet_ntoa(_addr.sin_addr);
        char ipbuffer[64];
        inet_ntop(AF_INET, &(_addr.sin_addr.s_addr), ipbuffer, sizeof(ipbuffer));
        _ip = ipbuffer;
    }
    void Host2Net()
    {
        memset(&_addr, 0, sizeof(_addr));
        _addr.sin_family = AF_INET;
        _addr.sin_port = htons(_port);
        // _addr.sin_addr.s_addr = inet_addr(_ip.c_str());
        inet_pton(AF_INET, _ip.c_str(), &(_addr.sin_addr.s_addr));
    }

public:
    InetAddr()
    {}
    InetAddr(const struct sockaddr_in &addr)
        : _addr(addr)
    {
        Net2Host();
    }
    InetAddr(uint16_t port, const std::string &ip = "0.0.0.0")
        : _port(port), _ip(ip)
    {
        Host2Net();
    }
    void Init(const struct sockaddr_in &addr)
    {
        _addr = addr;
        Net2Host();
    }
    std::string Ip()
    {
        return _ip;
    }
    uint16_t Port()
    {
        return _port;
    }
    struct sockaddr* Addr()
    {
        return Conv(_addr);
    }
    socklen_t Length()
    {
        return sizeof(_addr);
    }
    std::string ToString()
    {
        return _ip + "-" + std::to_string(_port);
    }
    bool operator==(const InetAddr &addr)
    {
        return (_ip == addr._ip && _port == addr._port);
        // return (_ip == addr._ip);
    }

    ~InetAddr()
    {
    }

private:
    struct sockaddr_in _addr; // 网络风格地址
    // 主机风格地址
    std::string _ip;
    uint16_t _port;
};

3. Socket.hpp

职责:封装基本的 socket 系统调用,并强制设置非阻塞和地址重用。

设计:定义抽象基类 Socket,实现类 TcpSocket。

核心方法:

  • Create:调用 socket(),设置 SO_REUSEADDR 和 SO_REUSEPORT,并设为非阻塞。

  • Bind、Listen。

  • Accept:返回新连接 fd,同时通过参数返回客户端地址和错误码(用于区分 EAGAIN、EINTR)。

为什么单独封装:方便后期扩展(如增加 UDP 支持),且集中处理非阻塞设置。

cpp 复制代码
#ifndef __SOCKET_HPP__
#define __SOCKRT_HPP__

#include <iostream>
#include <string>
#include <unistd.h>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <memory>
#include "Logger.hpp"
#include "InetAddr.hpp"
#include "Util.hpp"

enum
{
    OK,
    CREATE_ERR,
    BIND_ERR,
    LISTEN_ERR,
};

static int gbacklog = 16;
static const int gsockfd = -1;

class Socket
{
public:
    virtual ~Socket() {}
    virtual void CreateSocketOrDie() = 0;
    virtual void BindSocketOrDie(int port) = 0;
    virtual void ListenSocketOrDie(int backlog) = 0;
    // virtual std::shared_ptr<Socket> Accept(InetAddr *clientaddr) = 0;
    virtual int Accept(InetAddr *clientaddr, int *error) = 0;
    virtual int SockFd() = 0;
    virtual void Close() = 0;
    virtual ssize_t Recv(std::string *out) = 0;
    virtual ssize_t Send(const std::string &in) = 0;
    virtual bool Connect(InetAddr &peer) = 0;

    // 其他接口
public:
    void BuildListenSocketMethod(int _port)
    {
        CreateSocketOrDie();
        BindSocketOrDie(_port);
        ListenSocketOrDie(gbacklog);
    }
    void BuildClientSocketMethod()
    {
        CreateSocketOrDie();
    }

    // void BuildUdpSocketMethod()
    // {
    //     CreateSocketOrDie();
    //     BindSocketOrDie();
    // }
};


class TcpSocket : public Socket
{
public:
    TcpSocket() : _sockfd(gsockfd)
    {}
    TcpSocket(int sockfd): _sockfd(sockfd)
    {}
    void CreateSocketOrDie() override
    {
        _sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_sockfd < 0)
        {
            LOG(LogLevel::FATAL) << "create socket error!";
            exit(CREATE_ERR);
        }
        SetNonBlock(_sockfd);
        int opt = 1;
        // 地址复用 --> socket addr 重复使用
        setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));
        LOG(LogLevel::INFO) << "create socket success!";
    }
    void BindSocketOrDie(int port) override
    {
        InetAddr local(port);
        if (bind(_sockfd, local.Addr(), local.Length()) != 0)
        {
            LOG(LogLevel::FATAL) << "bind socket error!";
            exit(BIND_ERR);
        }
        LOG(LogLevel::INFO) << "bind socket success!";
    }
    void ListenSocketOrDie(int backlog) override
    {
        if(listen(_sockfd, backlog) != 0)
        {
            LOG(LogLevel::FATAL) << "listen socket error!";
            exit(LISTEN_ERR);
        }
        LOG(LogLevel::INFO) << "listen socket success!";
    }
    // std::shared_ptr<Socket> Accept(InetAddr *clientaddr) override
    int Accept(InetAddr *clientaddr, int *error) override
    {
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);

        int fd = accept(_sockfd, (struct sockaddr*)&peer, &len);
        *error = errno;
        if(fd < 0)
        {
            LOG(LogLevel::WARNING) << "accept socket Done!";
            return -1;
        }
        LOG(LogLevel::INFO) << "accept socket success!";
        clientaddr->Init(peer);
        return fd;
        // return std::make_shared<TcpSocket>(fd);
    }
    int SockFd() override
    {
        return _sockfd;
    }
    void Close() override
    {
        if(_sockfd >= 0)
            close(_sockfd);
    }
    
    ssize_t Recv(std::string *out) override
    {
        char buffer[1024];
        ssize_t n = recv(_sockfd, buffer, sizeof(buffer)-1, 0);
        if(n > 0)
        {
            buffer[n] = 0;
            *out += buffer; 
        }
        return n;
    }
    ssize_t Send(const std::string &in)
    {
        //也有问题
        return send(_sockfd, in.c_str(), in.size(), 0);
    }
    bool Connect(InetAddr &peer) override
    {
        int n = connect(_sockfd, peer.Addr(), peer.Length());
        if(n >= 0)
            return true;
        else
            return false;
    }
    ~TcpSocket()
    {
    }

private:
    int _sockfd;
};

// class UdpSocket : public Socket
// {
// };

#endif

4. Epoller.hpp

职责:纯 epoll 系统调用的封装,不涉及业务逻辑。

核心方法:

  • AddEvent(EPOLL_CTL_ADD)

  • ModEvent(EPOLL_CTL_MOD)

  • DelEvent(EPOLL_CTL_DEL)

  • Wait:返回就绪事件数组和个数。

设计要点:只操作 struct epoll_event,上层(Reactor)负责管理 data.fd 的关联。

cpp 复制代码
#pragma once

#include <iostream>
#include <sys/epoll.h>
#include "Util.hpp"
#include "Logger.hpp"

class Epoller
{
private:
    //OperEventHelper 是 epoll 事件管理的核心辅助函数,封装了 epoll_ctl 的调用细节
    int OperEventHelper(int sockfd, uint32_t events, int oper)
    {
        sturct epoll_event ev;
        ev.events = events;
        ev.data.fd = sockfd;
        return epoll_ctl(_epfd, oper, sockfd, &ev);
    }

public:
    Epoller()
    {
        _epfd = epoll_create(128);
        if(_epfd < 0)
        {
            LOG(LogLevel::FATAL) << "epoll create fatal";
            exit(1);
        }
        LOG(LogLevel::INFO) << "create epoll fd success: "<< _epfd;
    }

    void AddEvent(int sockfd, uint32_t events)
    {
        int n = OperEventHelper(sockfd, events, EPOLL_CTL_ADD);
        if (n != 0)
        {
            LOG(LogLevel::INFO) << "add: " << sockfd << " events: "
                                << Event2String(events) << " to epoller failed";
            return;
        }
        LOG(LogLevel::INFO) << "add: " << sockfd << " events: "
                            << Event2String(events) << " to epoller success";
    }

    void DelEvent(int sockfd)
    {
        int n = epoll_ctl(_epfd, EPOLL_CTL_DEL, sockfd, nullptr);
        if (n != 0)
        {
            LOG(LogLevel::INFO) << "Del: " << sockfd << " from epoller failed";
            return;
        }
        LOG(LogLevel::INFO) << "Del: " << sockfd << " from epoller success";
    }

    void ModEvent(int sockfd, uint32_t events)
    {
        int n = OperEventHelper(sockfd, events, EPOLL_CTL_MOD);
        if (n != 0)
        {
            LOG(LogLevel::INFO) << "Mod: " << sockfd << " events: "
                                << Event2String(events) << " to epoller failed";
            return;
        }
        LOG(LogLevel::INFO) << "Mod: " << sockfd << " events: "
                            << Event2String(events) << " to epoller success";
    }

    int Wait(struct epoll_event revs[], int num, int timeout)
    {
        int n = epoll_wait(_epfd, revs[], num, timeout);
        //TODO
        return n;
    }

     ~Epoller()
    {
        if (_epfd >= 0)
        {
            close(_epfd);
        }
    }

private:
    int _epfd;
};
}

5. Connection.hpp -- 抽象基类

职责:定义服务器中所有"连接"的统一接口,并持有每个连接独有的数据。

数据成员:

  • _fd、_events(当前 epoll 关心的事件,用于同步修改)。

  • _inbuffer、_outbuffer(应用层读写缓冲区,解决 TCP 粘包)。

  • _peer(对端地址)。

  • _owner(指向所属 Reactor 的指针,用于回调 reactor 的方法)。

  • _cb(业务回调,std::function<string(string&)>,由协议层提供)。

  • _lastActive(用于超时检查)。

cpp 复制代码
#pragma once

#include <iostream>
#include <string>
#include <functional>
#include "InetAddr.hpp"

class Reactor;

using callback_t = std::function<std::string(std::string &inbuffer)>;

//connection :标识服务器收到的一条连接
class Connection
{
public:
    Connection()
    : _events(0),
      _owner(nullptr)
      {

      }
    
    virtual void Recver() = 0;
    virtual void Sender() = 0;
    virtual void Excepter() = 0;
    virtual ~Connection() {};

    int Sockfd() { return _sockfd;}
    void SetSocketfd(int sockfd) { _sockfd = sockfd;}
    void SetEvents(uint32_t events) {_events = events;}
    uint32_t Events() {return _events;}
    void SetAddr(const InetAddr &addr) {_peer = addr;}
    Reactor *Owner() { return _owner;}
    void SetOwner(Reactor *r) {_owner = r;}
    void SetCallback(callback_t cb) { _cb = cb;}

protected:
    int _sockfd;
    uint32_t _events;
    std::string _inbuffer;
    std::string _outbuffer;
    uint64_t lasttimestamp;

    InetAddr _peer;
    Reactor *owner; //回指指针,方便我们使用Reactor中的方法

    callback_t _cb;
}

6. Listener.hpp -- 监听套接字

继承:Connection。

  • 构造函数:创建 TcpSocket,执行 Create/Bind/Listen,设置 _events = EPOLLIN | EPOLLET。

  • 实现 Recver:

    循环调用 _sock->Accept 直到返回 -1 且 errno == EAGAIN。

  • 对于每个新连接 fd,调用 SetNonBlock。

  • 创建 Channel 对象(std::make_shared(fd, clientaddr))。

  • 将 Listener 自己的 _cb(即协议解析回调)传递给 Channel。

  • 调用 Owner()->AddConnection(channel) 将其加入 Reactor。

  • Sender 和 Excepter 为空实现(监听套接字不需要发送和主动错误处理)。

cpp 复制代码
#pragma once

#include <iostream>
#include <memory>
#include "InetAddr.hpp"
#include "Epoller.hpp"
#include "Connection.hpp"
#include "Socket.hpp"
#include "Channel.hpp"
#include "Util.hpp"

//链接管理器: 创建listen,获取新链接
class Listener : public Connection
{
public:
    Listener(uint16_t port)
        : _listensockfd(std::make_unique<TcpSocket>()),
          _port(port)
    {
        _listensockfd->BuildListenSocketMethod(_port);
        SetSocketfd(_listensockfd->SockFd());
        SetEvents(EPOLLIN | EPOLLET);
    }

    void Recver() override
    {
        //获取新连接,非阻塞循环获取,因为我们是ET模式,保证本次获取连接,都必须处理完
        while(true)
        {
            InetAddr clientaddr;
            int error = 0;
            int sockfd = _listensockfd->Accept(&clientaddr, &error);
            if(sockfd < 0)
            {
                // 处理接受失败的情况
                if (errno == EAGAIN)
                // 非阻塞模式下没有更多待接受的连接,正常退出循环
                break;
                else if (errno == EINTR)
                // 被系统信号中断,继续尝试接受
                continue;
                else
                // 其他错误(如系统资源不足),退出循环
                break;
            }
        }
        //获取新链接成功,不可以直接读,应添加到Reactor中!
        //1.构建新的连接
        SetNonBlock(sockfd);
        std::shared_ptr<Connection> conn = std::make_shared<Channel>();
        conn->SetCallback(_cb);
        //添加到Reactor中
        Owner()->AddConnection(conn);
    }

    void Sender() override
    {
        // empty
    }
    void Excepter() override
    {
        // empty
    }
    ~Listener() {}



private:
    std::unique_ptr<Socket> _listensockfd;
    uint16_t _port;
}

7. Channel.hpp -- 普通数据连接

继承:Connection。

实现 Recver:

  • 更新活跃时间。
  • 循环调用 recv 直到返回 EAGAIN(非阻塞),数据追加到 _inbuffer。
  • 若 recv 返回 0 或错误,调用 Excepter()。
  • 调用 _cb(_inbuffer) 获取响应字符串(该回调内部会解析 _inbuffer 并消费已处理部分)。
  • 将响应追加到 _outbuffer。
  • 若 _outbuffer 非空,调用 Owner()->EnableReadWrite(_fd, true, true) 添加 EPOLLOUT 事件。

实现 Sender:

  • 更新活跃时间。
  • 循环调用 send 直到 _outbuffer 为空或返回 EAGAIN。
  • 发送成功后从 _outbuffer 中 erase 已发部分。
  • 若 _outbuffer 为空,调用 Owner()->EnableReadWrite(_fd, true, false) 移除 EPOLLOUT;否则保留(等待下一次写事件)。

实现 Excepter:

调用 Owner()->DelConnection(_fd),清理 epoll、关闭 fd、从 map 中移除。

为什么用 _inbuffer 累积数据:TCP 是流式,一次 recv 可能只收到半个报文,需要缓存到完整报文出现。

cpp 复制代码
#pragma once

#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include "Epoller.hpp"
#include "Connection.hpp"

// 关不关心数据是什么?
class Channel : public Connection
{
    static const int buffersize = 1024;

public:
    Channel(int sockfd, InetAddr &addr)
    {
        SetSocketfd(sockfd);
        SetAddr(addr);
        SetEvents(EPOLLIN | EPOLLET);
    }

void Recver() override
{
    // 更新连接的最后活跃时间(用于超时检测)
    Update();
    
    // ET模式要求:必须循环读取完所有可用数据
    while (true)
    {
        char buffer[buffersize];  // 临时缓冲区
        
        // 非阻塞读取数据(_sockfd已设置为非阻塞模式)
        // sizeof(buffer) - 1 留一个字节给字符串结束符
        ssize_t n = recv(_sockfd, buffer, sizeof(buffer) - 1, 0);
        
        if (n > 0)
        {
            // 成功读取数据
            
            // 添加字符串结束符,确保可以作为C字符串处理
            buffer[n] = 0;
            
            // 将读取的数据追加到输入缓冲区
            _inbuffer += buffer;
        }
        else if (n == 0)
        {
            // n == 0 表示客户端主动关闭连接
            
            LOG(LogLevel::INFO) << "client quit, client is : " << _peer.ToString();
            
            // 处理连接关闭(清理资源、从Reactor中移除等)
            Excepter();
            
            // 退出函数,不再继续处理
            return;
        }
        else  // n < 0,表示读取失败
        {
            // 通过 errno 判断失败原因(errno 是标准库提供的全局错误码变量)
            
            if (errno == EAGAIN)
            {
                // EAGAIN:非阻塞模式下没有更多数据可读
                // 这是正常情况,退出循环
                break;
            }
            else if (errno == EINTR)
            {
                // EINTR:读取操作被系统信号中断
                // 继续循环,尝试再次读取
                continue;
            }
            else
            {
                // 其他错误:如连接异常断开等
                
                LOG(LogLevel::INFO) << "recv error, client is : " << _peer.ToString();
                
                // 处理异常情况
                Excepter();
                
                // 退出函数
                return;
            }
        }
    }

    // 循环结束,本轮数据已全部读完
    
    // 调用协议解析回调处理数据
    // 注意:回调函数负责判断数据是否完整,不完整时返回空字符串
    // _inbuffer 会保留未解析的数据,等待下次数据到达后继续解析
    _outbuffer += _cb(_inbuffer);
    
    // 打印输出缓冲区内容(调试用)
    std::cout << "_outbuffer: " << _outbuffer << std::endl;
    
    // 如果有响应数据,启用写事件监听
    // 当套接字可写时,Reactor会触发Sender()方法发送数据
    if (!_outbuffer.empty())
        Owner()->EnableReadWrite(_sockfd, true, true);  // 启用读和写事件
    
  
}

void Sender() override
{
    // 步骤1: 更新连接的最后活跃时间(用于超时检测)
    Update();
    
    // 步骤2: 非阻塞循环发送数据
    while (true)
    {
        // 发送数据(_sockfd已设置为非阻塞模式)
        ssize_t n = send(_sockfd, _outbuffer.c_str(), _outbuffer.size(), 0);
        
        if (n > 0) {
            // 成功发送了 n 字节
            _outbuffer.erase(0, n);  // 从缓冲区头部移除已发送的数据
            
            if (_outbuffer.size() == 0) {
                // 数据全部发送完毕,退出循环
                break;
            }
            // 否则继续循环发送剩余数据
        }
        else if (n == 0) {
            // 连接关闭(很少见,通常通过 recv 返回 0 判断)
            break;
        }
        else if (n < 0) {
            // 发送失败,通过 errno 判断原因
            if (errno == EAGAIN) {
                // 发送缓冲区已满,稍后再试
                break;
            }
            else if (errno == EINTR) {
                // 被系统信号中断,继续尝试发送
                continue;
            }
            else {
                // 其他错误(如连接断开)
                Excepter();  // 处理异常(清理资源、移除连接等)
                return;      // 退出函数
            }
        }
    }

    // 步骤3: 根据缓冲区状态调整写事件监听
    if (!_outbuffer.empty()) {
        // 数据未发完(发送缓冲区满),继续监听写事件
        Owner()->EnableReadWrite(_sockfd, true, true);
    }
    else {
        // 数据已全部发完,关闭写事件监听
        Owner()->EnableReadWrite(_sockfd, true, false);
    }
}


 void Excepter() override
    {
        // 统一错误处理!
        Owner()->DelConnection(_sockfd);
    }
};

8. Protocol.hpp -- 协议与序列化(与具体业务相关,但保持独立)

定义:Request、Response 结构体,包含序列化/反序列化方法(使用 JSON)。

  • 静态工具类 Protocol:

  • Package(const string& json):返回 长度\r\njson\r\n。

  • Unpack(string& buf, string* out):

  • 从 buf 中解析出一个完整报文,提取 JSON 存入 out。

  • 关键:解析成功后从 buf 中删除该报文(通过 erase)。

  • 返回报文长度,0 表示不完整,-1 表示协议错误。

设计要点:解包函数直接修改输入缓冲区,这是"谁解析谁清理"原则的体现,避免上层重复解析。

cpp 复制代码
#pragma once

#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>



class Request
{
public:
    Request()
    {
        _x = _y = _oper = 0;
    }
    // 序列号对象
    bool Serialize(std::string *out)
    {
        //1. 手写 "_x" "_oper" "_y" --- 字符串拼接转换
        //2. 现成工具

        Json::Value root;
        root["x"] = _x;
        root["y"] = _y;
        root["oper"] = _oper;

        Json::StyledWriter writer;
        *out = writer.write(root);
        if(out->empty())
            return false;
        return true;
    }

    // 反序列化对象
    bool Deserialize(std::string &in)
    {
        Json::Reader reader;
        Json::Value root;
        bool ret = reader.parse(in, root);
        if(!ret)
            return false;

        _x = root["x"].asInt();
        _y = root["y"].asInt();
        _oper = root["oper"].asInt();

        return true;
    }
    int X()
    {
        return _x;
    }
    int Y()
    {
        return _y;
    }
    char Oper()
    {
        return _oper;
    }
    ~Request()
    {
    }

// private:
public: // 加快速度
    // x oper y -- 约定1
    int _x;
    int _y;
    char _oper;
};



class Response
{
public:
    Response():_result(0), _code(0)
    {
    }
      // 序列号对象
    bool Serialize(std::string *out)
    {
        //1. 手写 "_x" "_oper" "_y" --- 字符串拼接转换
        //2. 现成工具

        Json::Value root;
        root["result"] = _result;
        root["code"] = _code;

        Json::StyledWriter writer;
        *out = writer.write(root);
        if(out->empty())
            return false;
        return true;
    }

    // 反序列化对象
    bool Deserialize(std::string &in)
    {
        Json::Reader reader;
        Json::Value root;
        bool ret = reader.parse(in, root);
        if(!ret)
            return false;

        _result = root["result"].asInt();
        _code = root["code"].asInt();

        return true;
    }
    void SetResult(int r)
    {
        _result = r;
    }
    void SetCode(int c)
    {
        _code = c;
    }
    void Print()
    {
        std::cout << _result << "[" << _code << "]" << std::endl;
    }
    ~Response()
    {
    }

private:
    int _result; //
    int _code;   // 可信度
};

static const std::string sep = "\r\n";

class Protocol
{
public:
    static std::string Package(const std::string &jsonstr)
    {
        // jsonstr -> len\r\njsonstr\r\n
        if(jsonstr.empty())
            return std::string();
        std::string json_length = std::to_string(jsonstr.size());
        return json_length + sep + jsonstr + sep;
    }
    static bool DigitSafeCheck(const std::string str)
    {
        for(int i = 0 ;i < str.size(); i++)
        {
            if(!(str[i] >= '0' && str[i] <= '9'))
                return false;
        }
        return true;
    }

    
    static int Unpack(std::string &origin_str, std::string *package)
    {
        if(!package)
            return 0;
        auto pos = origin_str.find(sep); // 从左向右
        if(pos == std::string::npos)
            return 0;
        //至少说明,我们收到了一个报文的长度
        std::string len_str = origin_str.substr(0, pos);
        if(!DigitSafeCheck(len_str))
        {
            return -1;
        }
        int digit_len = std::stoi(len_str);
        // 如果我得到了当前报文的长度
        // 根据协议,我可以推测出,一个完整报文的长度是多少
        int target_len = len_str.size() + digit_len + 2 * sep.size();
        if(origin_str.size() < target_len)
            return 0;
        
        ///////////////上面的逻辑,我们对origin_string都没做/////////////////
        // 我保证,origin_str内部,一定有一个完整的报文请求!
        *package = origin_str.substr(pos + sep.size(), digit_len);
        origin_str.erase(0, target_len);
        return package->size();
    }
};

9. Parser.hpp -- 协议解析器 + 业务调度器

职责:将 Protocol::Unpack 与业务回调(如 Calculator::Exec)串联起来。

构造函数:接收一个 handler_t(std::function<Response(Request&)>)。

方法 Parse:

  • 输入参数:std::string &inbuffer(引用)。

  • 循环调用 Protocol::Unpack(inbuffer, &jsonstr),直到返回 0 或 -1。

  • 对每个完整报文:反序列化为 Request,调用业务 handler 得到 Response,序列化,调用 Protocol::Package 打包。

  • 将所有打包后的响应拼接成一个字符串返回。

注意:inbuffer 会通过 Unpack 被修改(已处理的报文被删除)。

为什么单独抽取 Parser:让网络层(Channel)不关心协议细节,只需要调用一个回调即可。

cpp 复制代码
#pragma once

#include "Protocol.hpp"
#include "Logger.hpp"
#include <iostream>
#include <string>
#include <functional>

using handler_t = std::function<Response (Request &req)>;
// 只负责对报文进行各种解析工作
// 对解析出来的请求和应答,Parser应该归你处理吗??
class Parser
{
public:
    Parser(handler_t handler):_handler(handler)
    {}
    std::string Parse(std::string &inbuffer)
    {
        LOG(LogLevel::DEBUG) << "inbuffer: \r\n" << inbuffer;
        std::string send_str;
        while (true)
        {
            std::string jsonstr;
            // 1. 解析报文
            int n = Protocol::Unpack(inbuffer, &jsonstr);
            if (n < 0)
                break;
            else if (n == 0)
                break; // 我已经将inbuffer所有完整报文处理完毕
            else
            {
                LOG(LogLevel::DEBUG) << "jsonstr: \r\n" << jsonstr;
                Request req;
                // 2. 反序列化
                if (!req.Deserialize(jsonstr))
                {
                    return std::string();
                }
                // 3. 根据req->response, 具体的业务
                Response resp = _handler(req);

                // 4. 对resp在进行序列化
                std::string resp_json;
                if (!resp.Serialize(&resp_json))
                {
                    return std::string();
                }

                // 5. 打包
                send_str += Protocol::Package(resp_json);
            }
        }
        return send_str;
    }
    ~Parser() {}
private:
    handler_t _handler;
};

10. Reactor.hpp -- 事件驱动核心

成员:

  • std::unique_ptr _epoller。

  • std::unordered_map<int, std::shared_ptr> _conns。

  • struct epoll_event _revs[MAX_EVENTS](用于存放就绪事件)。

核心方法:

  • AddConnection(shared_ptr conn):
  • 设置 conn->SetOwner(this)。

  • 插入 _conns 表。

  • 调用 _epoller->AddEvent(conn->Fd(), conn->Events())。

  • DelConnection(int fd):
  • 调用 _epoller->DelEvent(fd)。

  • 从 _conns 中移除。

  • close(fd)。

  • EnableReadWrite(int fd, bool read, bool write):
  • 查找对应的 Connection。

  • 根据 read/write 重新计算 events(保留 EPOLLET)。

  • 更新 Connection::_events。

  • 调用 _epoller->ModEvent(fd, new_events)。

  • LoopOnce(int timeout):
  • 调用 _epoller->Wait(_revs, MAX_EVENTS, timeout),获取就绪事件数量 n。

  • 遍历 _revs[0...n-1]:

  • 根据 data.fd 查找 Connection。

  • 若事件包含 EPOLLHUP|EPOLLERR,调用 conn->Excepter()。

  • 若包含 EPOLLIN,调用 conn->Recver()。

  • 若包含 EPOLLOUT,调用 conn->Sender()。

  • Dispatcher():无限循环调用 LoopOnce 和超时检查。

CheckTimeout():遍历 _conns,删除超时连接(通过 LastActive 时间戳)。

cpp 复制代码
pragma once
#include <iostream>
#include <string>
#include <unordered_map>
#include <memory>
#include "Util.hpp"
#include "Epoller.hpp"
#include "Connection.hpp"

// Reactor : Reactor是一个反应堆,本质是利用多路转接,激活自己所管理的一个一个节点Connection
// Reactor : 容器!!!增删查改Connection
// 在Reactor角度,需要区分你是Listenersockfd,还是普通sockfd??不区分!
class Reactor
{
    static const int size =128;

private:
    bool IsExist(std::shared_ptr<Connection> &conn)
    {
        auto iter = _connections.find(conn->SockFd());
        return iter != _connections.end();
    }

    bool IsExist(int sockfd)
    {
        auto iter = _connections.find(sockfd);
        return iter != _connections.end();
    }

public:
    Reactor()
    : _epoller(std::make_unique<Epoller>())
    {

    }

    void AddConnection(std::make_ptr<Connection> &conn)
    {
        if(IsExist(conn))
        {
            LOG(LogLevel::INFO) << conn->Sockfd() << " conn in Reactor!";
            return;
        }
        //0.让conn回指当前reactor
        conn->SetOwner(this);
        //1.conn插入到_connections
        _connections.insert(std::make_pair(conn->SockFd(), conn));
        //2.conn->sockfd,event 写透到内核里
        _epoller->AddEvent(conn->Sockfd(), conn->Events())
        LOG(LogLevel::INFO) << conn->Sockfd() << " conn add to Reactor";
    }

    void EnableReadWrite(int sockfd, bool enableread, bool enablewrite)
    {
        if (!IsExist(sockfd))
        {
            LOG(LogLevel::WARNING) << sockfd << " conn not in Reactor, bug!";
            return;
        }
        //1.修改connection对象
        uint32_t events = (enableread? EPOLLIN : 0) | (enablewrite ? EPOLLOUT : 0) | EPOLLET;
        _connections[sockfd]->SetEvents(events);

        //2.写透到内核
        _epoller->ModEvent(scokfd, events);
    }

    void DelConnection(int sockfd)
    {
        if (!IsExist(sockfd))
        {
            LOG(LogLevel::WARNING) << sockfd << " conn not in Reactor[delete]!";
            return;
        }
        //1.从内核中移除对sockfd的关心
        _epoller->DelEvent(sockfd);

        // 2. _connections移除到kv
        _connections.erase(sockfd);

        // 3. close关闭fd
        close(sockfd);

        LOG(LogLevel::INFO) << "remove conn " << sockfd << " from reactor success";

    }

    void LoopOnce(int timeout)
    {
        int n = _epoller->Wait(revs, szie, timeout);
        for(int i = 0; i<n; i++)
        {
            int sockfd = revs[i].data.fd; //就绪的sockfd
            uint32_t events = revs[i].events;

            // 统一报错的处理方法!
            if (events & EPOLLHUP)
                events = (EPOLLIN | EPOLLOUT);
            if (events & EPOLLERR)
                events = (EPOLLIN | EPOLLOUT);

            // 增加代码的鲁棒性!
            if ((events & EPOLLIN) && IsExist(sockfd))
            {
                _connections[sockfd]->Recver();
            }
            if ((events & EPOLLOUT) && IsExist(sockfd))
            {
                _connections[sockfd]->Sender();
            }
        }
    }

    void Dispatcher()
    {
        int timeout = 1000;
        while(true)
        {
            //处理事件
            LoopOnce(timeout);
            ShowConnection();
            CheckTimeOut();
        }
    }

     void CheckTimeOut()
    {
        uint64_t currtime = time(nullptr);
        for(auto &conn: _connections)
        {
            uint64_t t = currtime - conn.second->LastActive();
            if(t > 5*60*1000)
            {
                // 随便写的
                DelConnection(conn.second->Sockfd());
            }
        }
    }
    void ShowConnection()
    {
        std::cout << "#############################" << std::endl;
        for(auto &conn: _connections)
        {
            std::cout << conn.second->Sockfd() << " : " 
                << Event2String(conn.second->Events()) << std::endl;
        }
        std::cout << "#############################" << std::endl;
    }
    ~Reactor()
    {
    }




private:
    std::unordered_map<int, std::shared_ptr<Connection>> _connections;
    std::unique_ptr<Epoller> _epoller;

    struct epoll_event revs[size];
}

11. main.cc -- 组装

  • 创建业务对象(如 Calculator)。

  • 创建 Parser,绑定业务回调。

  • 创建 Listener,设置其回调为 Parser::Parse。

  • 创建 Reactor,调用 AddConnection(listener)。

  • 启动 Reactor::Dispatcher()。

cpp 复制代码
#include <iostream>
#include "Calculator.hpp"
#include "Protocol.hpp"
#include "Parser.hpp"
#include "Logger.hpp"
#include "Reactor.hpp"
#include "Listener.hpp"

void Usage(std::string proc)
{
    std::cerr << "Usage: " << proc << " localport" << std::endl;
}

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(0);
    }
    uint16_t serverport = std::stoi(argv[1]);
    

    //日志服务
    EnableConsoleLogStrategy();

     1. 业务对象
    std::unique_ptr<Calculator> cal = std::make_unique<Calculator>();

     2. 协议和解析协议对象
    std::unique_ptr<Parser> parse_protocol = std::make_unique<Parser>(
        [&cal](Request &req)->Response{
            return cal->Exec(req);
        }
    );

     3. 连接管理器 - Listener
    std::shared_ptr<Connection> listener = std::make_shared<Listener>(serverport);
    listener->SetCallback([&parse_protocol](std::string &inbuffer)->std::string{
        return parse_protocol->Parse(inbuffer);
    });

     4. 构建一个Reactor容器
    std::unique_ptr<Reactor> R = std::make_unique<Reactor>();

     给reactor中,把连接管理器添加到Reactor
    R->AddConnection(listener);

     启动Reactor
    R->Dispatcher();

    return 0;
}

三者如何联动(一次请求的处理流程)

  1. 新客户端连接到达:
  • Listener::Recver 被 epoll 触发,循环 accept,创建 Channel 对象。

  • Channel 被添加到 Reactor,其回调被设置为 listener 的回调(即上面那个 lambda)。

  1. 客户端发送请求数据:
  • Channel::Recver 循环读取数据,存入 _inbuffer。

  • 调用 _cb(_inbuffer),即执行 [&parse_protocol](std::string &inbuffer) { return parse_protocol->Parse(inbuffer); }。

  1. 协议解析:
  • parse_protocol->Parse(inbuffer) 被调用,它内部:

  • 循环调用 Protocol::Unpack 提取完整 JSON 报文。

  • 反序列化为 Request 对象。

  • 调用自身构造时传入的 handler_t 回调,即 [&cal](Request &req) -> Response { return cal->Exec(req); }。

  • 得到 Response,序列化为 JSON,打包成 长度\r\njson\r\n 格式。

  • 返回拼接后的响应字符串。

  1. 响应发送:
  • _cb 返回的响应字符串被 Channel::Recver 追加到 _outbuffer。

  • Channel 启用写事件,最终通过 Channel::Sender 发送给客户端。

  1. 强制非阻塞 + 边缘触发(ET)
    所有 socket 必须设为非阻塞,并采用 ET 模式。非阻塞是 ET 的前提,ET 可减少 epoll 的触发次数,提升高并发效率;代价是用户必须循环读写直到 EAGAIN。
  • 监听套接字循环 accept 直到 EAGAIN

    在 ET 模式下,监听套接字只通知一次新连接。必须循环调用 accept 直至返回 EAGAIN,才能将已完成队列中的所有连接取走,避免连接丢失。

  • 普通连接循环 recv 直到 EAGAIN

    ET 模式下可读事件只触发一次,因此必须在 Recver 中循环接收数据,直到内核返回 EAGAIN,确保接收缓冲区内的所有数据都被读取。

  1. 写事件按需注册,避免空转

    仅在 _outbuffer 非空时才注册 EPOLLOUT 事件;发送完毕后立即注销。这样能防止 epoll 在无数据可写时频繁触发写事件,有效降低 CPU 消耗。

  2. 协议解包直接消费输入缓冲区(引用传递 + 修改)

    协议解析层(Unpack 和 Parse)通过引用修改输入缓冲区,在处理完完整报文后立即从缓冲区中删除。这避免了上层重复解析,也防止缓冲区无限增长。

  3. 超时检查:定期清理不活跃连接

    在 Reactor 事件循环中定期遍历所有连接,检查其最后活跃时间。超过阈值的连接将被关闭并清理,以此作为兜底机制,防止客户端异常断开造成的资源泄漏。

  4. 事件分发:根据 fd 查找对应的 Connection

    epoll_event.data.fd 直接存放文件描述符,Reactor 通过 fd 在映射表中查找对应的 Connection 对象。这实现了 O(1) 的事件分发,并利用多态将具体处理交给派生类的 Recver/Sender/Excepter。

    Reactor 不需要关心每个 fd 具体是什么类型的连接(监听套接字或普通数据套接字),也不需要遍历所有连接去匹配 fd。fd 就是索引,直接定位到对应的处理器对象。

  5. 回调链:业务逻辑与网络 IO 解耦

    通过三层回调将业务(Calculator)、协议(Parser)、网络(Listener/Channel)完全分离。每层只通过回调接口通信,更换任意一层均不影响其他层,极大提高了可维护性和可扩展性。

相关推荐
丁劲犇5 小时前
使用TraeAI开发Web页面测试MSYS2 ucrt64 Qt MCP服务器
服务器·前端·c++·qt·mcp
菜鸡儿齐5 小时前
Future接口学习
java·服务器·开发语言
牛奔5 小时前
codebuddy 桌面版 如何配置自己的模型
运维·服务器·开发语言·php
跨境数据猎手5 小时前
代购系统技术选型全复盘:Laravel / Go / 自研 / SaaS 怎么选
爬虫·php·laravel
SilentSamsara6 小时前
日志与可观测性:logging 进阶配置与结构化日志实战
运维·开发语言·python·青少年编程
学困昇6 小时前
Linux IPC 详解:匿名管道、命名管道、共享内存与信号量
linux·运维·服务器·c语言·c++·人工智能
TYKJ0237 小时前
服务器带宽的"独享"和"共享"到底差在哪?从原理到实测讲清楚
运维·服务器·后端
祁白_7 小时前
PHP无参读取文件与RCE总结
安全·php·writeup·总结·rce
XiYang-DING7 小时前
【Java EE】IPv6
java·java-ee·php