You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++中基于epoll的Connection生命周期管理问题(参考Nginx)

正确管理epoll中Connection对象生命周期的几种方案

我太懂这种纠结了——用智能指针的时候转成void*丢进epoll_event里,拿回来的时候没法正确维护引用计数,直接裸指针new/delete又怕内存泄漏、野指针崩溃,确实头疼。结合Nginx的设计思路,给你几个符合现代C++风格的解决方案:

方案1:用std::shared_ptr+容器维护活跃连接(最推荐)

核心思路是:用容器持有所有活跃连接的shared_ptr,确保只要连接还在epoll监听中,引用计数就不会降到0;存入epoll的是Connection的原始指针,处理事件时从容器中取回shared_ptr来安全操作。

首先让Connection继承std::enable_shared_from_this(方便后续从原始指针获取shared_ptr):

#include <memory>
#include <unordered_map>
#include <mutex>
#include <sys/epoll.h>

class Connection : public std::enable_shared_from_this<Connection> {
public:
    int fd;
    // 你的其他成员:缓冲区、状态标记等
};

// 全局/类成员容器,管理所有活跃连接的shared_ptr,多线程环境必须加锁
std::unordered_map<int, std::shared_ptr<Connection>> active_connections;
std::mutex conn_mutex;

添加连接到epoll的逻辑

void add_connection_to_epoll(int epoll_fd, std::shared_ptr<Connection> conn) {
    std::lock_guard<std::mutex> lock(conn_mutex);
    // 先把shared_ptr存入容器,保证引用计数至少为1
    active_connections[conn->fd] = conn;

    epoll_event event{};
    event.events = EPOLLIN | EPOLLET; // 这里用边缘触发,和Nginx一致
    event.data.ptr = conn.get(); // 存入原始指针

    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn->fd, &event) == -1) {
        // 失败时要从容器移除,避免内存泄漏
        active_connections.erase(conn->fd);
        perror("epoll_ctl add failed");
    }
}

处理epoll事件的逻辑

void handle_epoll_events(int epoll_fd, int nfds, epoll_event* events) {
    for (int i = 0; i < nfds; ++i) {
        Connection* raw_conn = static_cast<Connection*>(events[i].data.ptr);
        if (!raw_conn) continue;

        std::shared_ptr<Connection> conn;
        {
            // 加锁从容器取回shared_ptr,此时引用计数+1
            std::lock_guard<std::mutex> lock(conn_mutex);
            auto iter = active_connections.find(raw_conn->fd);
            if (iter != active_connections.end()) {
                conn = iter->second;
            }
        }

        if (!conn) {
            // 连接已经被关闭,跳过处理
            continue;
        }

        // 处理读/写事件
        if (events[i].events & EPOLLIN) {
            // 比如调用conn->read_data()
        }
        if (events[i].events & EPOLLOUT) {
            // 比如调用conn->write_data()
        }

        // 判断是否需要关闭连接(比如客户端断开、出错)
        if (should_close_connection(conn)) {
            std::lock_guard<std::mutex> lock(conn_mutex);
            // 先从epoll移除fd,再从容器删除shared_ptr
            epoll_ctl(epoll_fd, EPOLL_CTL_DEL, conn->fd, nullptr);
            active_connections.erase(conn->fd);
            // 离开作用域后conn的引用计数-1,最后一个shared_ptr销毁时自动释放Connection
        }
    }
}

这个方案的优势:

  • 完全避免手动new/delete,内存安全由智能指针保证
  • 容器持有shared_ptr确保连接在epoll监听期间不会被意外销毁
  • 多线程环境下通过锁保证容器操作的原子性

方案2:自定义对象生命周期管理(模仿Nginx的内存池思路)

如果不想用智能指针,可以参考Nginx的做法:给Connection对象添加引用计数成员,自己实现增减逻辑:

class Connection {
public:
    int fd;
    int ref_count; // 自定义引用计数

    Connection() : ref_count(0) {}
    void inc_ref() { ref_count++; }
    void dec_ref() {
        if (--ref_count == 0) {
            delete this;
        }
    }
};
  • 存入epoll前调用conn->inc_ref()
  • 处理事件时从void*还原后调用conn->inc_ref()
  • 处理完事件或关闭连接时调用conn->dec_ref()

这种方式需要严格管理引用计数的增减,容易出错,不如智能指针省心,但更贴近Nginx的原生实现。

踩坑提醒

  1. 绝对不要直接把std::shared_ptr对象转成void*存入epoll——shared_ptr本身是个栈/堆对象,它的地址可能随时失效,还原时会导致未定义行为。
  2. 多线程环境下,容器的增删改查必须加锁,否则会出现线程安全问题。
  3. 关闭连接时一定要先从epoll移除fd,再销毁Connection,避免epoll后续触发已关闭fd的事件,导致野指针访问。

内容的提问来源于stack exchange,提问作者Trree

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 11:56:19