当前位置:首页 > 文章列表 > 文章 > linux > epoll EPOLLONESHOT 怎样正确重新武装事件

epoll EPOLLONESHOT 怎样正确重新武装事件

来源:17golang原创 2026-10-25 14:16:11 0浏览 收藏

使用 EPOLLONESHOT 后,描述符的一次事件被 epoll_wait 返回,相关通知就会被禁用。处理完成且连接仍需继续监听时,调用 epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &event),重新提交关注掩码与用户数据,才能恢复通知。

不要把“重新进入 epoll_wait”当成重武装。本文先给最小写法,再用事件数量和读取字节数说明差异;原创示例没有在本机执行,也不提供虚构的性能提升或延迟数据。

Linux man-pages 官方项目资料:https://man7.org/linux/man-pages/man7/epoll.7.html。本地连接对接口:https://man7.org/linux/man-pages/man2/socketpair.2.html。

先看计数:首次通知后是否还有后续事件

典型现象是首次收到可读事件后,连接后续仍有输入,却再也没有处理机会。诊断时把 epoll_wait 返回事件数、实际读取字节数和 MOD 调用结果分开记录。仅看“对端成功写入”,不能证明当前监听已经重新启用。

本例只关注一个本地连接、两个单字节写入和一次重武装。计数用于看清通知语义,不用于推断真实服务器能承载多少连接。正式负载还涉及调度、业务处理与缓冲管理,不能由这个短程序得出吞吐结论。

行为假设:ONESHOT 暂停的是通知

ONESHOT 并不替你读走数据,也不代表关闭 fd。它限制的是此项监听后续的通知。应用仍然拥有连接状态,必须决定这次处理后继续监听、等待写就绪,还是关闭连接。

把处理读到暂时无数据和恢复监听作为明确的交接点,有助于避免遗漏后续通知。多线程场景中,这个交接还必须配合连接所有权;ONESHOT 不能代替应用对对象生命期的管理。

图1:epoll 实例、连接描述符与关注事件的静态注册关系
图1:epoll 实例、连接描述符与关注事件的静态注册关系。静态结构说明图,不是执行时序或运行截图。

改动点:完整掩码配合 EPOLL_CTL_MOD

/* 工作线程完成当前连接处理后,更新完整的关注掩码。 */
struct epoll_event interest = {0};
interest.events = EPOLLIN | EPOLLONESHOT;
interest.data.fd = fd;
if (epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &interest) == -1) {
    /* 不忽略失败:由调用方记录并处理连接生命周期。 */
    perror("epoll_ctl MOD");
}

这个片段假设 epfd 和 fd 已经有效、fd 已在该 epoll 实例中注册。MOD 是更新已有注册项,不是再次 ADD。每次都构造本轮需要的完整掩码,并保留 EPOLLONESHOT;若原设计还采用 EPOLLET 等标志,也要按原有模式明确提交。

data.fd 是本例选择的事件识别字段。若实际项目使用 data.ptr 指向连接对象,应继续提交正确且有效的对象指针,不能照抄 fd 字段后丢掉应用的分发信息。失败时要记录并进入明确的恢复或关闭路径,不能假设重武装总会成功。

复现方法:用 socketpair 隔离外部网络

下面的完整程序使用非阻塞 AF_UNIX socketpair,避开远端服务和监听端口。第一轮读到 EAGAIN 后写入第二个字节,先做一次零超时等待观察未武装状态,再 MOD 并观察已有可读数据。本例采用水平触发加 ONESHOT,刻意不混入 EPOLLET。

#include 
#include 
#include 
#include 
#include 

static int wait_event(int epfd, struct epoll_event *event, int timeout) {
    int count;
    do {
        count = epoll_wait(epfd, event, 1, timeout);
    } while (count == -1 && errno == EINTR); /* 中断后重新等待。 */
    return count;
}

static int drain_read(int fd, int *bytes) {
    char buffer[64];
    for (;;) {
        ssize_t n = read(fd, buffer, sizeof buffer);
        if (n > 0) {
            *bytes += (int)n;
            continue;
        }
        if (n == 0) return 0; /* 对端关闭,不再继续重武装读取。 */
        if (errno == EINTR) continue;
        if (errno == EAGAIN || errno == EWOULDBLOCK) return 1;
        return -1;
    }
}

int main(void) {
    int pair[2] = {-1, -1}, epfd = -1, result = 1, bytes = 0;
    struct epoll_event interest = {0}, event = {0};
    /* 本地连接对均为非阻塞,不依赖监听端口或远程服务。 */
    if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, pair) == -1)
        goto error;
    epfd = epoll_create1(EPOLL_CLOEXEC);
    if (epfd == -1) goto error;
    interest.events = EPOLLIN | EPOLLONESHOT; /* 本例不使用 EPOLLET。 */
    interest.data.fd = pair[0];
    if (epoll_ctl(epfd, EPOLL_CTL_ADD, pair[0], &interest) == -1)
        goto error;
    if (write(pair[1], "A", 1) != 1) goto error;
    int first = wait_event(epfd, &event, 1000);
    if (first == -1) goto error;
    if (first != 1 || !(event.events & EPOLLIN)) goto unexpected;
    if (drain_read(pair[0], &bytes) != 1) goto unexpected;

    /* 写入第二个字节时,ONESHOT 的通知尚未恢复。 */
    if (write(pair[1], "B", 1) != 1) goto error;
    int disabled = wait_event(epfd, &event, 0);
    if (disabled == -1) goto error;
    /* MOD 重新提交掩码和 data,恢复后续就绪通知。 */
    if (epoll_ctl(epfd, EPOLL_CTL_MOD, pair[0], &interest) == -1)
        goto error;
    int rearmed = wait_event(epfd, &event, 1000);
    if (rearmed == -1) goto error;
    if (rearmed != 1 || !(event.events & EPOLLIN)) goto unexpected;
    if (drain_read(pair[0], &bytes) != 1) goto unexpected;
    printf("first=%d disabled=%d rearmed=%d bytes=%d\n",
           first, disabled, rearmed, bytes);
    result = 0;
    goto cleanup;
error:
    perror("演示系统调用失败");
    goto cleanup;
unexpected:
    fprintf(stderr, "未观察到本例要求的可读状态,请检查运行环境\n");
cleanup:
    /* 所有出口都释放本例拥有的描述符。 */
    if (epfd != -1) close(epfd);
    if (pair[0] != -1) close(pair[0]);
    if (pair[1] != -1) close(pair[1]);
    return result;
}

保存为 oneshot_demo.c,在 Linux 环境中可使用以下命令自行运行。本文未执行这些命令,图片也不作为运行证据。

# 编译原创示例,打开常用警告。
cc -std=c11 -Wall -Wextra -O2 oneshot_demo.c -o oneshot_demo
# 运行后查看事件数与读取总字节数。
./oneshot_demo
图2:MOD 调用、完整事件掩码与用户数据字段的静态关系
图2:MOD 调用、完整事件掩码与用户数据字段的静态关系。静态结构说明图,不是执行时序或运行截图。

结果对比:缺少重武装与恢复通知

观察项本例正常条件下的语义预期说明
first1首次就绪被返回
disabled0未重武装时没有后续通知
rearmed1MOD 后再次取得可读通知
bytes2两个单字节写入均由读循环消费

这些数字是示例输入与 API 语义推导,不是实测结果。若删掉 MOD,不能继续期待第二次等待必然获得输入通知。若连接在实际业务中已关闭,则不应机械重武装;先判断连接是否继续存在和需要哪些方向的事件。

读取循环处理 EINTR、EAGAIN 与 EOF,避免把暂时无数据当成断连。此处的缓冲只做计数,没有实现应用协议;真实程序还要把字节组装成消息,而不是认为一次可读通知对应一条完整消息。

边界条件和常见问题

必须把 EPOLLONESHOT 和 EPOLLET 一起使用吗?

不是同一个开关。本文没有使用 EPOLLET。若采用边沿触发,非阻塞读写与处理到 EAGAIN 尤其重要,不能只增加 ONESHOT 就忽略原有读写约定。

为什么不先重武装再让工作线程处理?

如果当前线程仍在修改连接缓冲和状态,过早恢复可能让别的等待线程取得后续事件。应先定义谁拥有连接和何时交还监听,再安排 MOD,而不是把它当作随处可调用的解锁操作。

EPOLLONESHOT 是否保证一个 fd 永远只由一个线程接触?

不能。其他应用代码仍可能直接读写、关闭或重复提交这个连接。对象释放、fd 复用和陈旧事件都需要自己的同步与身份管理。

每次都监听 EPOLLOUT 会更稳吗?

不应为重武装无条件添加写关注。根据实际待发送缓冲决定是否需要写就绪,避免没有业务工作却频繁处理可写状态。重武装的目标是恢复正确的关注集合。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Linux cgroup v2 memory.stat 怎样区分匿名页和文件页Linux cgroup v2 memory.stat 怎样区分匿名页和文件页
上一篇
Linux cgroup v2 memory.stat 怎样区分匿名页和文件页
Linux nsenter 怎么同时进入目标进程的 network 和 mount namespace
下一篇
Linux nsenter 怎么同时进入目标进程的 network 和 mount namespace
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    686次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    701次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    757次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    719次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    557次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码