头像


🎬 个人主页艾莉丝努力练剑

专栏传送门:《C语言》《数据结构与算法》《C/C++干货分享&学习过程记录
Linux操作系统编程详解》《笔试/面试常见算法:从基础到进阶》《Python干货分享

⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平

🎬 艾莉丝的简介:

在这里插入图片描述


文章目录


在这里插入图片描述


前言

一、 开头部分(框架引入)

1. 整体学习框架导入语

本文围绕 Linux 高性能网络服务器的核心架构 ——Reactor 反应堆模式展开,覆盖单 Reactor 模型的代码收尾、连接全生命周期管理、异常边界处理,以及多进程 / 多线程架构的改造方案。全篇基于 epoll 的 ET 边缘触发模式,深度拆解读写事件动态使能、内核事件同步、连接销毁时序、连接保活、跨执行流事件通知等工程级细节,同时对原代码中的遗留缺陷、边界隐患进行逐一审计与修正,形成逻辑闭环的复习体系,是网络服务端开发的核心考核内容。

2. 结构化知识导图

Reactor代码收尾与多执行流改造
+- 核心接口:EnableReadWrite
|  |- 接口功能:动态控制套接字读写事件关心
|  |- Reactor层:存在性校验 + 事件位运算 + 内核同步
|  `- Poller层:ModEvents + EpollCtlHelper公共封装
+- 协议与异常处理修正
|  |- 协议层:解析失败退出进程→返回空串+错误码
|  `- IO层:错误码判定→触发连接异常销毁
+- 连接删除机制
|  |- 销毁时序:epoll移除 → 关闭fd → 移除管理节点
|  |- Reactor层:DelConnection接口
|  `- Poller层:DelEvents + EPOLL_CTL_DEL规范
+- 核心代码细节
|  |- 写触发:直接调用Sender / 使能EPOLLOUT自动触发
|  |- 时序保证:单Reactor下请求应答严格有序
|  `- 事件派发:LoopOnce超时机制 + 错误事件转换
+- 连接保活机制
|  |- 时间戳字段:_lasttimestamp
|  |- 更新时机:读写事件触发时调用Update
|  `- 淘汰逻辑:定时遍历 + 超时连接销毁
+- Reactor模式理论
|  |- 本质:事件驱动 + 回调分发
|  |- 特性:半同步半异步
|  `- 分类:单Reactor(Redis) / 多Reactor(Nginx)
+- 多进程Reactor改造
|  |- 架构:Master-Slaver进程池
|  |- 核心思想:One Thread One Loop
|  `- 分发:管道通知 + 子进程自主accept
`- 多线程Reactor改造
   |- 方案1:管道通知分发连接fd
   |- 方案2:eventfd轻量事件通知
   |  |- eventfd特性:计数器、原子性、支持epoll
   |  `- 两种模式:普通模式 / 信号量模式
   `- 架构:Master线程accept + 任务队列 + eventfd唤醒

1 ~> 核心接口实现:EnableReadWrite

1.1 接口设计背景与核心逻辑

在非阻塞网络编程中,发送缓冲区的状态是动态变化的:当数据一次性无法全部发送完毕时,需要让 epoll 开始关心 EPOLLOUT 事件,等待发送缓冲区空闲后自动继续发送;当数据全部发送完成后,需要取消对 EPOLLOUT 的关心,避免频繁触发无效的写就绪事件。 EnableReadWrite 接口的核心作用,就是动态调整指定套接字在 epoll 上的事件监听状态,实现发送流程的自动化事件驱动。

1.2 Reactor 层接口实现

接口接收三个参数:套接字 sockfd、是否使能读事件 enableread、是否使能写事件 enablewrite。 执行流程:

  1. 鲁棒性校验:如果该套接字对应的连接不存在,直接返回,避免非法访问。
  2. 事件组装:通过位运算生成最终事件集合,读事件对应 EPOLLIN,写事件对应 EPOLLOUT,强制开启 EPOLLET 边缘触发模式。
  3. 状态更新:先更新 Connection 对象内部的_events 字段。
  4. 内核同步:调用 Poller 的 ModEvents 接口,将事件变更写透到内核 epoll 中。
void EnableReadWrite(int sockfd, bool enableread, bool enablewrite)
{
    if(!IsConnectionExists(sockfd))
        return;
    
    uint32_t events = ((enableread ? EPOLLIN : 0) | (enablewrite ? EPOLLOUT : 0) | EPOLLET);
    _connections[sockfd]->SetEvents(events);
    // 事件变更写透到内核
    _epoller->ModEvents(sockfd, events);
}

[批注]

原笔记中存在函数名大小写不一致、sockfd 笔误为 sockta 的问题,此处已统一修正。同时补充:在 AddConnection 流程中,必须设置回指指针conn->_R = this,让 Connection 对象能够反向调用 Reactor 的接口,这是连接自主修改事件的前提,属于典型的双向关联设计。

1.3 Poller 层能力支撑:ModEvents 与 EpollCtlHelper

1.3.1 ModEvents 接口初始实现

ModEvents 对应系统调用 epoll_ctl 的 EPOLL_CTL_MOD 操作,用于修改内核中指定套接字的监听事件。初始实现与 AddEvents 高度相似,仅操作码不同,存在大量冗余代码。

1.3.2 EpollCtlHelper 公共封装

为消除代码冗余,抽取公共辅助函数 EpollCtlHelper,接收套接字、事件集合、操作码三个参数,统一执行 epoll_ctl 调用。AddEvents、ModEvents、后续的 DelEvents 均可复用该函数。

private:
    int EpollCtlHelper(int sockfd, uint32_t events, int oper)
    {
        struct epoll_event ev;
        ev.events = events;
        ev.data.fd = sockfd;
        return epoll_ctl(_epfd, oper, sockfd, &ev);
    }

public:
    void AddEvents(int sockfd, uint32_t events)
    {
        int n = EpollCtlHelper(sockfd, events, EPOLL_CTL_ADD);
        if(n < 0)
        {
            LOG(LogLevel::FATAL) << "AddEvents error!";
        }
    }

    void ModEvents(int sockfd, uint32_t events)
    {
        int n = EpollCtlHelper(sockfd, events, EPOLL_CTL_MOD);
        if(n < 0)
        {
            LOG(LogLevel::FATAL) << "ModEvents error!";
        }
    }

[批注]

辅助函数仅负责执行系统调用,错误处理由上层业务接口负责,是合理的分层设计。同时需要注意:EPOLL_CTL_MOD 操作必须保证套接字已被添加至 epoll,否则会返回系统错误,这也是 Reactor 层先做存在性校验的核心原因。

1.4 发送逻辑与接口的联动

在 Sender 发送完成后,根据输出缓冲区_outbuffer 是否为空判断发送状态,自动调用 EnableReadWrite 调整事件:

  • 缓冲区为空:数据全部发送完毕,关闭写事件关心,仅保留读事件。
  • 缓冲区非空:本次发送未全部完成,开启写事件关心,等待下一次写就绪时自动继续发送。
// 发送完成后状态判断
if(_outbuffer.empty())
    _R->EnableReadWrite(_sockfd, true, false);
else
    _R->EnableReadWrite(_sockfd, true, true);

整个发送流程完全由 epoll 事件驱动,无需上层业务手动循环重试,实现了 IO 事件与业务逻辑的解耦。

1.5 EnableReadWrite知识图谱

在这里插入图片描述


2 ~> 协议层与异常处理修正

2.1 协议解析失败的逻辑修正

早期多进程版本的协议处理中,报文解析失败会直接调用 exit (1) 终止进程。但在单 Reactor 模型中,所有连接共享同一个进程,单个连接的协议错误不能导致整个服务退出,必须修正该逻辑。

修正后的 HandlerRequest 执行逻辑:

  1. 循环从输入流中解包完整报文
  2. 解包返回 0:当前无完整报文,设置错误码为 0,返回已组装的响应
  3. 解包返回 - 1:协议解析失败,设置错误码为 - 1,返回空字符串,终止当前连接处理
std::string HandlerRequest(std::string &streamstr, int *code)
{
    LOG(LogLevel::DEBUG) << "Enter HandlerRequest";
    std::string resp_package;
    // 1. 检测报文完整性
    while(true)
    {
        std::string jsonstring;
        int n = UnPackage(streamstr, &jsonstring);
        if(n == 0)
        {
            *code = 0;
            LOG(LogLevel::DEBUG) << "解析完毕";
            return resp_package;
        } 
        else if(n == -1)
        {
            *code = -1;
            LOG(LogLevel::DEBUG) << "协议解析失败";
            return std::string();
        }
        // 反序列化、业务计算、封装响应
        // ...
    }
}

[批注]

原笔记中遗留的 exit (1) 是典型的多进程到单 Reactor 迁移的适配遗漏。单 Reactor 模型下,单个连接的异常必须只影响自身,必须通过连接销毁的方式处理,绝不能终止整个服务进程,这是服务端高可用的基础原则。

2.2 接收端异常处理链路

在 IOHandler 的 Recver 函数中,调用协议处理函数后,根据返回的错误码分支处理:

  • 错误码为 0:业务处理正常,将响应追加到输出缓冲区
  • 错误码非 0:协议或业务异常,调用 Excepter 执行连接销毁流程

同时,recv 系统调用返回 0(对端关闭连接)、返回负值且错误码不是 EAGAIN/EWOULDBLOCK 时,也都会触发 Excepter 异常处理。


3 ~> 连接生命周期:删除机制实现

3.1 删除操作的时序原则

连接销毁必须严格遵循执行顺序,否则会触发内核错误或野指针问题:

  1. 先从 epoll 内核中移除:必须保证文件描述符有效时执行 epoll_ctl 的 DEL 操作,若先关闭 fd 再移除,epoll 会因找不到有效 fd 而报错。
  2. 再关闭文件描述符:从 epoll 移除后,调用 close 释放系统套接字资源。
  3. 最后从管理映射表中移除:确保整个销毁过程中都能通过_connections 找到连接对象,避免野指针访问。

3.2 Reactor 层 DelConnection 实现

DelConnection 是连接销毁的统一入口,首先做存在性校验,然后严格按照上述时序执行销毁。

void DelConnection(int sockfd)
{
    if (!IsConnectionExists(sockfd))
        return;

    LOG(LogLevel::INFO) << "delete sockfd: " << sockfd;
    // 1. 从epoll中移除
    _epoller->DelEvents(sockfd);
    // 2. 关闭文件描述符
    _connections[sockfd]->Close();
    // 3. 从连接管理表中移除
    _connections.erase(sockfd);
}

[批注]

原笔记中该行日志误加了std::endl,而自定义日志类未重载 endl 的处理,会导致模板参数推导失败、编译报错。正确做法是直接输出字符串与整数,换行由日志类内部统一处理,这是自定义日志组件的常见踩坑点。

同时,Connection 基类需要定义纯虚函数 Close,由派生类分别实现:

  • IOHandler::Close:直接调用 close 关闭业务套接字
  • Listener::Close:调用封装好的 TcpSocket::Close 关闭监听套接字

3.3 Poller 层 DelEvents 实现

3.3.1 EPOLL_CTL_DEL 的系统调用规范

epoll_ctl 的 EPOLL_CTL_DEL 操作,在 Linux 2.6.9 之后的内核中,event 参数可以传 nullptr,内核会忽略该参数。传入无效的事件结构体虽多数情况下不会报错,但属于不规范写法。

3.3.2 代码实现与边界修正

在 EpollCtlHelper 中增加对 DEL 操作的特殊处理,DEL 操作直接传 nullptr,不填充 event 结构体:

int EpollCtlHelper(int sockfd, uint32_t events, int oper)
{
    if (oper == EPOLL_CTL_DEL)
    {
        return epoll_ctl(_epfd, oper, sockfd, nullptr);
    }

    struct epoll_event ev;
    ev.events = events;
    ev.data.fd = sockfd;
    return epoll_ctl(_epfd, oper, sockfd, &ev);
}

void DelEvents(int sockfd)
{
    EpollCtlHelper(sockfd, 0, EPOLL_CTL_DEL);
}

[批注]

原笔记中直接传 0 作为 events 参数给 DEL 操作,虽功能上不会出错,但不符合系统调用最佳实践。标准做法是 DEL 操作直接传入 nullptr 作为 event 参数,避免无意义的参数传递,同时兼容所有内核版本。


4 ~> 核心代码细节与边界场景

4.1 写事件使能的两种触发方案

当业务层产生待发送数据时,有两种方式触发发送流程:

  • 方案一:直接调用 Sender:数据写入输出缓冲区后,立刻调用 Sender 尝试发送。一次性发完则关闭写事件,没发完则自动开启写事件等待下一次就绪。优点是延迟低,数据产生后立即尝试发送。
  • 方案二:仅使能写事件:数据写入输出缓冲区后,不直接调用发送,仅调用 EnableReadWrite 开启写事件。由于 epoll 的特性,使能 EPOLLOUT 时会立刻触发一次写就绪事件,由事件循环自动调用 Sender。优点是代码统一,所有发送都由事件循环驱动。
// 方案一:直接触发发送
if(!_outbuffer.empty())
    Sender();

// 方案二:仅使能写事件,由事件循环触发
if(!_outbuffer.empty())
    _R->EnableReadWrite(_sockfd, true, true);

[批注]

epoll 的 EPOLLOUT 事件在使能时默认触发一次就绪,本质是因为写缓冲区默认处于空闲状态,只要使能写事件就满足就绪条件。这一特性是方案二能够成立的底层基础,也是 epoll 写事件的核心特性,属于高频考点。

4.2 报文时序保证

单 Reactor 模型下,所有 IO 事件和业务处理都在同一个线程 / 进程中串行执行,因此请求的接收、处理、响应的顺序严格一致:先到达的请求先处理,先处理的请求先响应,不存在时序错乱问题。 这也是单 Reactor 模型的核心优势之一:无需考虑并发时序问题,业务逻辑开发简单,无锁开销。

4.3 事件派发 LoopOnce 逻辑

LoopOnce 是 Reactor 的单次事件循环,核心流程:

  1. 调用 epoll_wait 等待事件就绪,超时时间由参数控制
  2. 遍历所有就绪事件
  3. 对 EPOLLERR、EPOLLHUP 错误事件,将其转换为读写事件,统一走读写的异常处理分支
  4. 读事件就绪且连接存在,调用对应连接的 Recver
  5. 写事件就绪且连接存在,调用对应连接的 Sender
void LoopOnce(int timeout)
{
    int n = _epoller->WaitEvents(revs, gnum, timeout);
    for(int i = 0; i < n; i++)
    {
        int sockfd = revs[i].data.fd;
        uint32_t revents = revs[i].events;

        if ((revents & EPOLLERR) | (revents & EPOLLHUP))
            revents = (EPOLLIN | EPOLLOUT); // 错误转换为读写事件,交由后续逻辑处理异常
        
        if ((revents & EPOLLIN) && IsConnectionExists(sockfd))
            _connections[sockfd]->Recver();
        if ((revents & EPOLLOUT) && IsConnectionExists(sockfd))
            _connections[sockfd]->Sender();
    }
}

[批注]

将错误事件转换为读写事件是非常巧妙的设计:错误发生时,读写操作必然会返回错误,进而触发 Excepter 销毁连接。这样就不需要单独编写错误事件的处理逻辑,所有异常都收敛到读写的错误分支中,简化了代码结构。


5 ~> 连接保活机制

5.1 连接活跃时间戳设计

网络服务中存在大量半打开连接、静默连接,长期占用文件描述符资源,需要通过保活机制淘汰超时不活跃的连接。 实现基础是在 Connection 基类中增加_lasttimestamp字段,记录连接最近一次活跃的时间戳,同时提供 Update 方法更新时间戳。

class Connection
{
    // 其他成员省略
protected:
    uint64_t _lasttimestamp; // 最近活跃时间戳
public:
    void Update()
    {
        _lasttimestamp = CurrentTimeStamp();
    }
};

其中 CurrentTimeStamp 通过系统调用 time 获取当前秒级时间戳,返回 uint64_t 类型避免溢出。

5.2 保活检测的执行时机

时间戳更新时机:每次 Recver、Sender 被调用时,开头先调用 Update () 更新时间戳,代表该连接当前处于活跃状态。 检测时机:将 Dispatcher 的 epoll_wait 超时设置为固定值(如 1000ms),每次 LoopOnce 结束后,执行一次 ConnectionKeepAlive 检测。

void Dispatcher()
{
    int timeout = 1000;
    while(true)
    {
        LoopOnce(timeout);
        ConnectionKeepAlive();
    }
}

5.3 淘汰策略的工程化扩展

基础实现可以遍历_connections 映射表,逐个比较时间戳,超时则调用 DelConnection 销毁。 工程级优化方案:使用最小堆管理连接的超时时间,堆顶就是最早超时的连接,每次只需要检查堆顶是否超时即可,将检测复杂度从 O (n) 降到 O (1)。需要注意:连接每次活跃时都要更新堆中的位置,保证堆的有序性。


6 ~> Reactor 模式核心理论

6.1 反应堆模式的本质

Reactor 反应堆模式是一种事件驱动的 IO 处理架构,核心思想是:

  • 预先为每个文件描述符注册对应的事件回调函数
  • 通过 IO 多路复用(epoll/select/poll)统一监听事件
  • 当某个文件描述符的事件就绪时,触发对应的回调函数执行对应的 IO 操作

可以类比为 “打地鼠” 模型:每个连接是一个地洞,事件就绪就是地鼠露头,回调函数就是锤子,哪个地鼠露头就打哪个。

6.2 半同步半异步特性

Reactor 是典型的半同步半异步架构:

  • 异步:事件的到来是异步的,连接建立、数据到达的时间完全不可预知,由多路复用器统一监听。
  • 同步:事件触发后的 IO 操作、业务处理是同步执行的,在当前执行流中完成,不会异步转交。

6.3 单 Reactor 与多 Reactor 的典型应用

  • 单 Reactor:所有事件监听、IO 处理、业务逻辑都在同一个执行流中完成。典型代表是 Redis,因为 Redis 是内存数据库,业务处理极快,瓶颈不在 CPU,单 Reactor 足以支撑高并发,且避免了锁的开销。
  • 多 Reactor:将监听、IO 处理、业务拆分到多个执行流中,充分利用多核 CPU。典型代表是 Nginx,采用多进程 Reactor 架构。

[批注]

存在一种常见错误方案:单 Reactor + 后端线程池。该方案将业务处理丢给线程池,但会导致请求应答的时序无法保证:线程调度顺序不可控,后到的请求可能先处理完,造成响应乱序。因此该方案在严格时序要求的场景下不推荐使用。


7 ~> 多进程 Reactor 改造方案

7.1 核心架构:Master-Slaver 进程池

多进程 Reactor 采用 Master-Slaver 架构:

  1. Master 进程负责创建监听套接字,创建多个 Slaver 子进程,以及与每个子进程建立匿名管道通信。
  2. 监听套接字由所有子进程继承,但只有 Master 进程将其加入自己的 Reactor 进行监听。
  3. 当有新连接到来时,Master 进程不执行 accept,而是通过管道向选中的 Slaver 子进程发送通知。
  4. Slaver 子进程的 Reactor 监听管道读端,收到通知后,执行 accept 获取新连接,将连接加入自己的 Reactor 进行管理。

7.2 One Thread One Loop 设计思想

每个 Slaver 进程都有自己独立的 Reactor、独立的事件循环,从连接建立到连接销毁全生命周期都在同一个进程内完成,这就是 “One Thread One Loop”(一个执行流一个事件循环),是服务器领域公认的优秀设计。 该设计的核心优势:

  • 无锁:每个进程只管理自己的连接,没有共享资源,不需要锁
  • 时序保证:单个连接的所有操作都在同一个进程内,时序严格有序
  • 故障隔离:单个子进程崩溃不影响其他子进程,服务整体可用

7.3 负载均衡与连接分发逻辑

Master 进程分发连接时,可以采用轮询、随机等负载均衡策略,选择一个 Slaver 子进程,向其管道写入数据,唤醒对应的子进程执行 accept。 子进程被唤醒后:

  1. 读取管道中的数据并清空
  2. 调用 accept 从监听套接字获取新连接
  3. 将新连接封装后加入自己的 Reactor 整个过程中,监听套接字由所有进程共享,但通过管道通知的方式避免了多个进程同时 accept 的惊群问题。

8 ~> 多线程 Reactor 改造方案

8.1 方案一:管道通知式多线程

该方案与多进程架构逻辑完全一致,将进程替换为线程:

  • 主线程作为 Master,监听 listen 套接字
  • 多个工作线程作为 Slaver,每个线程拥有独立的 Reactor
  • 主线程与工作线程之间通过管道通信,分发连接

与多进程的区别:线程共享文件描述符表,因此主线程可以先 accept 获取新连接的 fd,再通过管道将 fd 数值传递给工作线程,工作线程直接将 fd 加入自己的 Reactor 即可。 该方案的缺点:管道属于内核级通信,需要经历用户态到内核态的数据拷贝,对于多线程场景来说开销偏大,不够轻量化。

8.2 方案二:eventfd 事件通知机制

8.2.1 eventfd 核心特性

eventfd 是 Linux 提供的轻量级事件通知机制,本质是内核维护的一个 64 位计数器,以文件描述符的形式提供给用户态。 核心特性:

  • 低开销:仅维护计数器,内核成本远低于管道
  • 支持多路复用:可以加入 epoll、poll、select 进行监听
  • 原子性:读写操作都是原子的,适合高并发场景
  • 仅通知不传输数据:只能传递事件信号,不能传递具体消息内容

8.2.2 eventfd 两种工作模式

  1. 普通模式(默认):读取操作会一次性清空计数器的值,返回累加后的总和。适合单纯的事件通知场景,一次读取处理所有待处理事件。
  2. 信号量模式(EFD_SEMAPHORE):每次读取只会将计数器减 1,返回值固定为 1。适合需要精确统计事件次数的场景。
// 普通模式创建
int efd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
// 信号量模式创建
int efd = eventfd(0, EFD_SEMAPHORE | EFD_NONBLOCK | EFD_CLOEXEC);

[批注]

原笔记中对普通模式的读取描述存在偏差:普通模式下,read 会将计数器的当前值全部读出,同时将计数器清零,而不是 “读取一次减 1”。信号量模式才是每次读取减 1,两者的语义区别是核心考点,必须严格区分。

8.2.3 基于 eventfd 的多线程 Reactor 架构

架构设计:

  1. 主线程作为 Master,负责监听 listen 套接字,accept 获取新连接
  2. 每个工作线程有独立的 Reactor、独立的 eventfd、独立的任务队列
  3. eventfd 的读端加入工作线程的 Reactor 进行监听
  4. 主线程获取新连接后,选择一个工作线程,将 fd 放入对应任务队列,然后向该线程的 eventfd 写入数据触发通知
  5. 工作线程的 eventfd 就绪后,从任务队列中取出所有新连接,加入自己的 Reactor 管理

该方案结合了用户态任务队列和内核态事件通知:

  • 任务队列用于传递具体的连接 fd,在用户态完成,开销低
  • eventfd 仅用于唤醒工作线程,解决了 “工作线程在 epoll_wait 阻塞,无法感知任务队列有新数据” 的问题 完美兼顾了性能和事件驱动的统一性。

9 ~> 知识图谱部分

9.1 补充:实现 ModEvents(Poller.hpp)(内部实现一个EpollCtlHelper函数,方便使用这个方法和AddEvents,因为代码都差不多)

在这里插入图片描述

9.2 测试:客户端直接拿网络版本计算器里面实现过的(OnlineCalClient.cc)

在这里插入图片描述

9.3 补充:HandlerRequest里面小修改(Protocol.hpp)

在这里插入图片描述

9.4 补充:改一下客户端(有问题,会刷屏)

在这里插入图片描述

9.5 补充:测试一下走到异常处理(telnet模拟错误的请求)

在这里插入图片描述

9.6 补充:实现 DelConnection(Reactor.hpp)&& 实现DelEvents(Poller.hpp)

在这里插入图片描述

9.7 测试

在这里插入图片描述

9.8 细节问题

在这里插入图片描述

9.9 单Reactor模式收尾

在这里插入图片描述

9.10 多Reactor模式

在这里插入图片描述


10 ~> 补足细节之后的单Reactor的代码部分说明

在这里插入图片描述

光是目录结构就已经如此夸张了,本文字数已经达到12000字了,再放这几乎相当于一个标准项目的代码行数的“基于单Reactor的网络版本计算器”到本文的正文中就有点太长了,大家不用担心,艾莉丝会专门更新一篇【Reactor模式】的代码篇来作为Linux网络部分多路转接、也是整个Linux部分的收官之作,不过Linux学习并没有到此为止,远远没有结束!还得继续努力复习巩固、查漏补缺才行!


结尾部分:核心总结

1. 接口实现类核心考点

  • EnableReadWrite 的位运算事件组装逻辑,ET 模式的强制开启规则
  • epoll_ctl 三种操作(ADD/MOD/DEL)的使用场景与参数规范
  • EpollCtlHelper 封装的设计思想,EPOLL_CTL_DEL 传 nullptr 的标准写法
  • 连接销毁的严格时序:先 epoll 移除,再关 fd,最后移除管理节点;顺序颠倒会导致内核报错或野指针问题
  • Connection 回指指针的作用:让连接对象能够主动调用 Reactor 接口修改自身事件状态

2. 异常与边界类高频坑点

  • 单 Reactor 下禁止单个连接异常终止整个进程,协议解析失败必须走连接销毁流程,这是多进程迁移到单 Reactor 的典型适配错误
  • 自定义日志类不可直接使用 std::endl,会导致模板参数推导失败,属于 C++ 自定义流输出的常见踩坑点
  • EPOLLERR/EPOLLHUP 的标准处理方式:转换为读写事件统一收敛异常逻辑,避免重复编写错误处理代码
  • ET 模式下必须循环读写直到返回 EAGAIN/EWOULDBLOCK,确保本次事件触发的所有数据全部处理完毕

3. 事件机制核心原理

  • EPOLLOUT 使能即触发就绪的底层原因:写缓冲区默认空闲,使能即满足就绪条件
  • 两种写触发方案的优劣对比:直接调用延迟低,使能触发代码统一
  • 单 Reactor 的时序保证原理:串行执行无并发调度干扰,请求与应答严格按顺序对应
  • 单 Reactor + 线程池方案的本质缺陷:线程调度不可控导致响应时序错乱,严格时序场景禁用

4. 连接保活机制考点

  • 时间戳的更新时机与检测时机的设计逻辑
  • 超时检测的两种实现方案:遍历法复杂度 O (n),最小堆法复杂度 O (1)
  • epoll_wait 超时参数的双重作用:避免无限阻塞 + 定时执行保活等后台任务

5. 多执行流改造核心架构

  • One Thread One Loop 的核心思想与三大优势:无锁、时序保证、故障隔离
  • Master-Slaver 架构的职责划分:Master 仅负责分发,Slaver 负责连接全生命周期管理
  • 多进程与多线程 Reactor 的分发差异:多进程由子进程自主 accept,多线程由主线程 accept 后传递 fd
  • eventfd 与管道的优劣对比:eventfd 开销更低、仅支持事件通知、无法传输数据;管道支持数据传输、开销更高
  • eventfd 两种模式的语义区别:普通模式一次性清零,信号量模式每次减 1,适用场景不同
  • eventfd + 任务队列架构解决的核心问题:同时支持 epoll 事件等待与用户态任务分发,兼顾性能与事件驱动统一性

6. Reactor 模式理论考点

  • 半同步半异步的具体含义:事件到来是异步,IO 与业务处理是同步
  • 单 Reactor 的典型代表与适用场景:Redis、CPU 非瓶颈的纯 IO 业务
  • 多 Reactor 的典型代表与适用场景:Nginx、CPU 密集型高并发业务
  • Reactor 模式的核心价值:用单执行流支撑大量并发连接,适配互联网 “连接多、活跃少” 的典型场景

结尾

uu们,本文的内容到这里就全部结束了,艾莉丝在这里再次感谢您的阅读!

艾莉丝努力练剑

C/C++ & Linux 底层探索者 | 一个正在努力练剑的技术博主


👀 【关注】 跟随我一起深耕技术领域,见证每一次成长。
❤️ 【点赞】 让优质内容被更多人看见,让知识传递更有力量。
【收藏】 把核心知识点存好,在需要时随时查、随时用。
💬 【评论】 分享你的经验或疑问,评论区一起交流避坑!

不要忘记给博主“一键四连”哦!

“今日练剑达成!”

“技术之路难免有困惑,但同行的人会让前进更有方向。”

结语:希望对学习Linux相关内容的uu有所帮助,不要忘记给博主“一键四连”哦!

往期回顾

【Linux网络】多路转接epoll(四)Reactor模式:基于epoll的高性能网络服务器设计与实现(中)代码细节

🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡
૮₍ ˶ ˊ ᴥ ˋ˶₎ა

在这里插入图片描述

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐