epoll 的常用宏和API汇总 - 详解

一、epoll 是干嘛的

Linux 下做高并发 I/O,用 epoll 的套路是:

  1. 建一个“事件池”:epoll_create1 得到 epfd

  2. 把你关心的 fd(socket)注册进去:epoll_ctl(EPOLL_CTL_ADD, fd, events)

  3. 在主循环里阻塞等事件:epoll_wait(epfd, evlist, maxevents, timeout)

  4. 等内核告诉你:“这几个 fd 有事件了(EPOLLIN/EPOLLOUT/错误等)”

二、最重要的三个系统调用

1) epoll_create1

int epfd = epoll_create1(EPOLL_CLOEXEC);
epoll_create1(0) = 正常创建
epoll_create1(EPOLL_CLOEXEC) = 正常创建 + 子进程 exec 时自动关闭,安全一些
  • 返回一个 epoll 的“句柄”(其实就是 fd)

  • 以后所有 epoll_ctl / epoll_wait 都用它

宏:EPOLL_CLOEXEC

  • 定义:一个标志位(int),告诉内核:

    “如果以后这个进程 exec() 启动别的程序,请自动关闭这个 epfd”

  • 作用:防止 fd 泄露给子进程

  • 现在的代码:

    epfd_ = ::epoll_create1(EPOLL_CLOEXEC);

2) epoll_ctl

int epoll_ctl(int epfd, int op, int fd, struct epoll_event* ev);

第二个参数 op 用的是这几个宏:

  • EPOLL_CTL_ADD
    把一个新的 fd 加入 epoll 监控集合(开始被盯着)

  • EPOLL_CTL_MOD
    修改已经在 epoll 里的这个 fd 的“关注事件”(比如加上 EPOLLOUT)

  • EPOLL_CTL_DEL
    从 epoll 里移除,不再监控(一般 fd 关闭前调用)

现在代码:

epoll_ctl(epfd_, EPOLL_CTL_ADD, fd, &ev);
epoll_ctl(epfd_, EPOLL_CTL_MOD, fd, &ev);
epoll_ctl(epfd_, EPOLL_CTL_DEL, fd, &ev);

这些就是实际用到的三个 op 宏。

3) epoll_wait

int n = epoll_wait(epfd, evlist, maxevents, timeout);
  • evlistepoll_event 数组,内核往里面填“就绪事件”

  • maxevents:最多返回多少个

  • timeout

    • -1:一直阻塞

    • 0:不阻塞,立刻返回

    • >0:最多等这么久(毫秒)

现在就是用 -1 无限等:

int n = ::epoll_wait(epfd_, evlist_.data(),
                     static_cast(evlist_.size()), -1);

三、struct epoll_event 和 “事件宏”

epoll 事件结构:

struct epoll_event {
    uint32_t     events;  // 事件标志(下面这些 EPOLLxxx)
    epoll_data_t data;    // 用户数据(你随便放,fd 或 ptr)
};

events 是一个“按位或的标志位”,也就是:

ev.events = EPOLLIN | EPOLLOUT | EPOLLET;

我的项目里常用的“事件宏”有哪些?

1) 可读 / 可写类

EPOLLIN
  • 定义:一个 bit 标志

  • 作用:

    • 对于 listenfd:表示有新连接可以 accept()

    • 对于普通 socket:表示这个 fd 上有数据可以 read(),不会阻塞

现在用它:

  • 注册监听:addFd(listenfd_, EPOLLIN, this)

  • 普通连接可读时:if (events & EPOLLIN) onConnRead(c);

EPOLLOUT
  • 定义:一个 bit 标志

  • 作用:fd 可写(发送缓冲区有空间),写不会阻塞

代码里用它:

reactor_.modFd(fd, EPOLLIN | EPOLLOUT, &c);
...
if (events & EPOLLOUT) onConnWrite(c);

即:

  • 没数据要写 → 只关心 EPOLLIN

  • 有数据要写 → 再加 EPOLLOUT,等内核告诉你“可以写了”

2) 异常 / 关闭类

EPOLLERR
  • 有错误(例如写到一个已 reset 的 socket)

EPOLLHUP
  • 挂起(通常是对端关闭,或者某种异常断开)

现在在 onEvent 里这样用:

if (events & (EPOLLERR | EPOLLHUP)) {
    closeConn(fd);
    return;
}

意思就是:

如果这个 fd 出错或挂了,就直接关掉连接。

3) 行为控制类

EPOLLET —— 边缘触发(Edge Triggered)
  • 定义:一个 bit 标志,表示 epoll 在这个 fd 上用“边缘触发”

  • 默认是 “电平触发”(LT:Level Triggered),只要缓冲区还有数据,每次 epoll_wait 都会返回

  • 加上 EPOLLET 后:只有从“无到有”那一刻会触发一次

结果:

  • LT:可以适当懒一点,read 一次不读完,下次还会通知

  • ET:必须循环 read / write 到 EAGAIN / EWOULDBLOCK,否则可能永远收不到下一次通知

现在:

ev.events = events | (useET_ ? EPOLLET : 0);

配合 onConnRead/onConnWrite 里 for(;;) + EAGAIN,这个用法是标准 ET 写法。


EPOLLONESHOT(你暂时没用,知道一下)
  • 定义:一次性事件

  • 作用:事件触发一次后,自动“冻结”这个 fd,不会再给这个 fd 推事件,除非你再次 EPOLL_CTL_MOD 激活

  • 多线程框架里经常用它保证“一个 fd 的某次事件只被一个线程处理一次”


4) epoll_data 的用法(和 void* user 有关)

data 是一个 union:

union epoll_data {
    void    *ptr;
    int      fd;
    uint32_t u32;
    uint64_t u64;
};

现在的策略是:

  • ev.data.fd = fd;

  • 真正的 user 指针放在 users_[fd] 这个 map 里

loop() 里:

int fd = evlist_[i].data.fd;
...
void* user = nullptr;
{
    std::lock_guard lk(users_mtx_);
    auto it = users_.find(fd);
    if (it != users_.end()) user = it->second;
}
dispatcher_(fd, ev, user);

四、配套的几个常见“宏和标志”

虽然不是 epoll_ 开头,但跟它一起用得非常多:

1) O_NONBLOCK(非阻塞)

  • 用在 fcntl(fd, F_SETFL, ...)

  • 作用:让 fd 变成非阻塞

  • 非阻塞 + epoll 才是“正确姿势”

setNonBlock 里这样用:

int fl = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, fl | O_NONBLOCK);

2) FD_CLOEXEC

  • 用在 fcntl(fd, F_SETFD, flags | FD_CLOEXEC)

  • 作用:exec 新程序时自动关闭这个 fd,防止泄漏到子进程


3) EAGAIN / EWOULDBLOCK

这两个不是宏常量,而是 errno 错误码,用在你循环读 / 写时判断“已经读完/写完了”:

if (errno == EAGAIN || errno == EWOULDBLOCK) break;
  • 对非阻塞 fd 来说:

    • read 返回 -1 且 errno = EAGAIN/EWOULDBLOCK → 现在没有数据,再读会阻塞

    • write 同理 → 发送缓冲区暂时满了

五、总表

宏名类别用法位置作用简记
EPOLL_CTL_ADDepoll_ctl opaddFd()加入监控
EPOLL_CTL_MODepoll_ctl opmodFd()修改事件
EPOLL_CTL_DELepoll_ctl opdelFd()删除监控
EPOLLINevents 标志addFd / onEvent可读/有新连接
EPOLLOUTevents 标志modFd / onEvent可写
EPOLLERRevents 标志onEvent出错
EPOLLHUPevents 标志onEvent挂起/关闭
EPOLLETevents 标志addFd / modFd边缘触发
EPOLL_CLOEXECcreate 标志epoll_create1()exec 时自动关闭 epfd

再配合:

  • O_NONBLOCK:非阻塞 IO

  • FD_CLOEXEC:exec 时自动关闭 fd

  • EAGAIN / EWOULDBLOCK:非阻塞读写结束条件

posted @ 2026-01-29 17:56  clnchanpin  阅读(0)  评论(0)    收藏  举报