【Linux高级IO】多路转接(poll epoll)

news2025/5/11 6:44:16

目录

1. poll

2. epoll

 2.1 epoll_ctl

2.2 epoll_wait

 2.3 epoll原理

2.4 epoll的工作模式

2.5 epoll的惊群效应

 使用建议

总结


在这里插入图片描述

1. poll

        poll也是实现 I/O 多路复用的系统调用,可以解决select等待fd上限的问题,将输入输出参数分离,不需要每次对参数重置;

 原型:

#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

struct pollfd {
    int   fd;         /* file descriptor */
    short events;     /* requested events */
    short revents;    /* returned events */
};

参数:

  • fds是一个poll函数监听的结构列表, 每一个元素中,包含了三部分内容:文件描述符,监听的事件集合, 返回的事件集合;
  • nfds表示fds数组的长度.(由此可以看出,fds其实就是一个数组)
  • timeout:指定 poll 函数的超时时间,单位为毫秒。其取值有以下几种情况:
    timeout > 0:poll 函数会阻塞,直到指定的文件描述符上有事件发生或者超时时间到达。
    timeout == 0:poll 函数会立即返回,不会阻塞,用于非阻塞检查文件描述符的状态。
    timeout == -1:poll 函数会一直阻塞,直到指定的文件描述符上有事件发生。

 调用时:调用poll时,用户->内核,OS帮用户关心fd,上面的events事件;

 返回时:poll返回时,内核->用户,用户关心的fd上面,有哪些的revents事件准备就绪;

返回结果:

  • 大于 0:表示发生事件的文件描述符的数量。
  • 等于 0:表示超时,即在指定的时间内没有文件描述符发生事件。
  • 等于 -1:表示发生错误,同时会设置 errno 来指示具体的错误类型。

 events和revents的取值

 示例:

#include <stdio.h>
#include <poll.h>
#include <unistd.h>

int main() {
    struct pollfd fds[1];
    fds[0].fd = 0;  // 监控标准输入
    fds[0].events = POLLIN;    // 监控可读事件

    int ret = poll(fds, 1, -1);  // 一直阻塞,直到有事件发生
    if (ret == -1) {
        perror("poll");
        return 1;
    }

    if (fds[0].revents & POLLIN) {
        char buf[1024];
        ssize_t n = read(STDIN_FILENO, buf, sizeof(buf));
        if (n > 0) {
            buf[n] = '\0';
            printf("Read from stdin: %s", buf);
        }
    }

    return 0;
}

pollserver示例:poll_server

 优点:

  • 可以等待多个fd,效率高
  • 输入,输出参数分离(events和revents),不用频繁对poll参数进行重置
  • poll关心的fd没上限(内部动态申请空间)

缺点:

  • 用户到内核要进行数据拷贝(struct pollfd 结构体数组传递给内核空间,这涉及到用户空间和内核空间之间的数据拷贝操作)
  • 应用层使用时,仍然需要遍历,在内核层面,要遍历检测,关心的fd是否有对应的事件就绪;在应用层,当 poll 函数返回后,需要遍历 struct pollfd 结构体数组,检查每个 fd 的 revents 成员,以确定哪些文件描述符上发生了事件。在内核层监控依然是线性遍历;

2. epoll

 epoll在man 手册中是这样描述的:是为处理大批量句柄而作了改进的poll;并且解决了poll遗留下来的问题;

解决 poll 遍历开销大的问题:epoll 的改进:epoll 采用红黑树来管理需要监控的文件描述符。

epoll相对于select、poll、提供了更多的操作接口:

int epoll_create(int size);

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

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

 2.1 epoll_ctl

用于控制 epoll 实例,可以添加、修改或删除要监控的文件描述符及其事件;

接口原型:

#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

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

struct epoll_event {
    uint32_t     events;    /* Epoll events */
    epoll_data_t data;      /* User data variable */
};
  • events:表示要监听的事件类型,如 EPOLLIN(可读)、EPOLLOUT(可写)等。
  • data:是一个联合体,通常使用 fd 成员来存储关联的文件描述符,也可以用 ptr 指向自定义的数据。 

参数:

  • epfd:epoll_create 返回的 epoll 实例的文件描述符。
  • op:操作类型,有 EPOLL_CTL_ADD(添加)、EPOLL_CTL_MOD(修改)、EPOLL_CTL_DEL(删除)。
  • fd:要监控的文件描述符。
  • event:指向 epoll_event 结构体的指针,指定要监控的事件类型和关联的数据。

返回值:成功返回 0,失败返回 -1

2.2 epoll_wait

等待 epoll 实例中注册的文件描述符上的事件发生;

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

 参数:

  • epfd:epoll 实例的文件描述符。
  • events:用于存储发生事件的 epoll_event 结构体数组。
  • maxevents:events 数组的最大元素个数。
  • timeout:超时时间,单位为毫秒。-1 表示阻塞等待,0 表示立即返回。

返回值:成功返回发生事件的文件描述符数量,超时返回 0,失败返回 -1

events:

  1. EPOLLIN : 表示对应的文件描述符可以读 (包括对端SOCKET正常关闭);
  2. EPOLLOUT : 表示对应的文件描述符可以写;
  3. EPOLLPRI : 表示对应的文件描述符有紧急的数据可读 (这里应该表示有带外数据到来);
  4. EPOLLERR : 表示对应的文件描述符发生错误;
  5. EPOLLHUP : 表示对应的文件描述符被挂断;
  6. EPOLLET : 将EPOLL设为边缘触发(Edge Triggered)模式, 这是相对于水平触发(Level Triggered)来说的.
  7. EPOLLONESHOT:只监听一次事件, 当监听完这次事件之后, 如果还需要继续监听这个socket的话, 需要 再次把这个socket加入到EPOLL队列里 

 简单使用示例:

#include <stdio.h>
#include <sys/epoll.h>
#include <unistd.h>

#define MAX_EVENTS 10

int main() {
    // 创建 epoll 实例
    int epoll_fd = epoll_create1(0);
    if (epoll_fd == -1) {
        perror("epoll_create1");
        return 1;
    }

    // 定义要监控的事件
    struct epoll_event ev, events[MAX_EVENTS];
    ev.events = EPOLLIN;
    ev.data.fd = STDIN_FILENO;

    // 添加标准输入到 epoll 实例中进行监控
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, STDIN_FILENO, &ev) == -1) {
        perror("epoll_ctl: STDIN_FILENO");
        close(epoll_fd);
        return 1;
    }

    while (1) {
        // 等待事件发生
        int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
        if (nfds == -1) {
            perror("epoll_wait");
            close(epoll_fd);
            return 1;
        }

        // 处理事件
        for (int i = 0; i < nfds; i++) {
            if (events[i].data.fd == STDIN_FILENO) {
                char buf[1024];
                ssize_t n = read(STDIN_FILENO, buf, sizeof(buf));
                if (n > 0) {
                    buf[n] = '\0';
                    printf("Read from stdin: %s", buf);
                }
            }
        }
    }

    // 关闭 epoll 实例
    close(epoll_fd);
    return 0;
}

 epollserver的示例:epoll_server

 2.3 epoll原理

        前边提到epoll是采用红黑树来解决遍历效率低的问题, 而epoll_create的功能其实就是构建红黑树等一系列操作;具体流程:申请struct file,创建epoll模型:构建红黑树,构建就绪队列,设置回调机制;把struct file设置到文件描述符表,返回fd;

        epoll_ctl的选项操作,其实就是对epoll模型中的红黑树进行增删改;设置进去的fd和event就会添加到红黑树中(要关心的fd上的哪些事件)

         epoll_wait获取准备就绪的fd ,可以直接到就绪队列获取;(内核告诉用户哪些fd的哪些事件就绪);结合epoll_wait参数,"数组的首元素地址""数组大小",epoll会从就绪队列获取节点,会按照顺序严格的放在 events *中(select和poll中的数组中间会出现空缺的情况),并用返回值表明就绪事件个数;

  •  epoll_wait检测是否有事件就绪,时间复杂度O(1);检查就绪链表是否为空即可判断;
  • 获取所有就绪事件,事件复杂度O(N)——这个是必然,无法优化;遍历就绪链表,将N个节点添加到 event* 数组中;

 如果 events * 数组满了怎么办?满了就返回;

struct eventpoll{ 
 .... 
 /*红黑树的根节点,这颗树中存储着所有添加到epoll中的需要监控的事件*/ 
 struct rb_root rbr; 
 /*双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件*/ 
 struct list_head rdlist; 
 .... 
};

        网卡从网络中读取到数据后,通过硬件中断将数据读到网卡驱动,网卡驱动允许注册相应的回调方法,红黑树中每添加一个节点,就会为该节点注册一个相应的回调方法;这个回调方法主要完成两个工作:

  1. 在内核中确认发生的事件(是否关心)
  2. 向ready_queue中形成新节点,表明哪个fd上的哪些事件已经发生

 这整个过程都是依靠单线程/进程完成的,那如果是多线程/进程呢?

        epoll模型创建返回值是一个文件描述符,只需对文件描述符表的管理,就可以做到对epoll模型进行管理;

epoll的优点:

  • 接口使用方便:虽然拆分成了三个函数, 但是反而使用起来更方便高效.
  • 不需要每次循环都设置关注的文件描述符,也做到了输入输出参数分离开
  • 数据拷贝轻量:只在合适的时候调用 EPOLL_CTL_ADD 将文件描述符结构拷贝到内核中,这个操作并不频繁(而select/poll都是每次循环都要进行拷贝)
  • 事件回调机制:避免使用遍历,而是使用回调函数的方式,将就绪的文件描述符结构加入到就绪队列中,epoll_wait 返回直接访问就绪队列就知道哪些文件描述符就绪. 这个操作时间复杂度O(1). 即使文件描述 符数目很多, 效率也不会受到影响.
  • 没有数量限制: 文件描述符数目无上限.

2.4 epoll的工作模式

 epoll有两种工作模式:水平触发、边缘触发;它是epoll提供给用户的一种通知事件就绪的策略;

epoll默认是LT模式;如果数据就绪,但上层并不做处理,epoll就会一直通知——LT模式;

ET策略:底层有数据,只通知一次,就不再通知,直到下次数据发生变化的时候(底层收到新的数据),才通知;

LT模式和ET模式相比,ET模式更高效;因为在通知策略中,没有无效的通知,全部都是有效的;

ET模式只会通知一次,倒逼上层(程序员),要取数据就要把本轮的数据取完;

在TCP服务中,这样的话,底层也会给发送方告知窗口大小,会通告一个更大的窗口;发送方的滑动窗口大小就会变得更大,从概率上,也可以提高双方的通信效率;

 ET模式下要求数据一次读完,那如何判断数据是否一次读完了?循环读取,直到缓冲区没有数据,就会阻塞;

但是,epoll一般是单线程/进程,一旦阻塞就没法继续后续的操作了,所以ET模式下必须以非阻塞状态进行IO操作;

 epoll的高性能,是有一定的特定场景的。如果场景选择的不适宜,epoll的性能可能适得其反;

对于多连接,且多连接中只有一部分连接比较活跃时,比较适合使用epoll;

2.5 epoll的惊群效应

        在多线程或者多进程环境下,有些人为了提高程序的稳定性,往往会让多个线程或者多个进程同时在epoll_wait监听的socket描述符。当一个新的链接请求进来时,操作系统不知道选派那个线程或者进程处理此事件,则干脆将其中几个线程或者进程都给唤醒;而实际上只有其中一个进程或者线程能够成功处理accept事件,其他线程都将失败,这种现象称为惊群效应,结果是肯定的,惊群效应肯定会带来资源的消耗和性能的影响;

 解决办法:

  1. 不建议让多个线程同时在epoll_wait监听的socket,而是让其中一个线程epoll_wait监听的socet,当有新的链接请求进来之后,由epoll_wait的线程调用accept,建立新的连接,然后交给其他工作线程处理后续的数据读写请求;
  2. 每个子进程仍然管自己在监听的socket上调用epoll_wait,当有新的链接请求发生时,操作系统仍然唤醒其中部分的子进程来处理该事件,仍然只有一个子进程能够成功处理此事件,那么其他被惊醒的子进程捕获EAGAIN错误,并无视;
  3. 创建一个全局的pthread_mutext,在子进程进行epoll_wait前,先获取锁,在同一时刻,永远都只有一个子讲程在监听的sacket 上epoll_wait;

 使用建议

发数据问题:

什么时候不能发?—— 发送缓冲区被写满的时候;

什么时候能发?—— 缓冲区不满,刚开始的时候可以直接发(大部分情况下发送缓冲区不会被写满);不能发的时候,把fd交给epoll,让epoll关心什么时候可以发(写事件就绪);

怎么知道发送缓冲区写满?

一直循环的去写;直到发送条件不具备,再交给epoll处理;

对于多路转接而言:

  • 一般对于任何fd,EPOLLIN事件,常设关心;
  • 对于写事件,按需关心即可 

总结

        以上便是本文的全部内容,希望对你有所帮助,感谢阅读!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2308742.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

供应链管理系统--升鲜宝门店收银系统功能解析,登录、主界面、会员 UI 设计图(一)

供应链管理系统--升鲜宝门店收银系统功能解析&#xff0c;登录、主界面 会员 UI 设计图&#xff08;一&#xff09;

【Linux系统编程】基础IO--磁盘文件

目录 前言 磁盘的机械构成 盘片介绍 盘片与磁头 数据的存储&#xff08;硬件&#xff09; 磁盘的物理存储 逻辑结构&#xff1a;磁道/柱面、扇面、扇区 磁盘I/O的基本单位与扇区的存储密度 CHS定位法&#xff1a;数据的查找 磁盘的逻辑存储 扇区的抽象结构(数据…

C# .NET Core HttpClient 和 HttpWebRequest 使用

HttpWebRequest 这是.NET创建者最初开发用于使用HTTP请求的标准类。HttpWebRequest是老版本.net下常用的&#xff0c;较为底层且复杂&#xff0c;访问速度及并发也不甚理想&#xff0c;但是使用HttpWebRequest可以让开发者控制请求/响应流程的各个方面&#xff0c;如 timeouts,…

[3/11]C#性能优化-实现 IDisposable 接口-每个细节都有示例代码

[3]C#性能优化-实现 IDisposable 接口-每个细节都有示例代码 前言 在C#开发中&#xff0c;性能优化是提升系统响应速度和资源利用率的关键环节。 当然&#xff0c;同样是所有程序的关键环节。 通过遵循下述建议&#xff0c;可以有效地减少不必要的对象创建&#xff0c;从而减…

1.C语言初识

C语言初识 C语言初识基础知识hello world数据类型变量、常量变量命名变量分类变量的使用变量的作用域 常量字符字符串转义字符 选择语句循环语句 函数&#xff1b;数组函数数组数组下标 操作符操作符算术操作符移位操作符、位操作符赋值操作符单目操作符关系操作符逻辑操作符条…

软件测试中的BUG

文章目录 软件测试的生命周期BugBug 的概念描述 Bug 的要素案例Bug 级别Bug 的生命周期与开发产生争执怎么办&#xff1f;【高频面试题】先检查自身&#xff0c;Bug 是否描述的不清楚站在用户角度考虑并抛出问题Bug 的定级要有理有据提⾼自身技术和业务水平&#xff0c;做到不仅…

TinyEngine v2.2版本发布:支持页面嵌套路由,提升多层级路由管理能力开发分支调整

2025年春节假期已过&#xff0c;大家都带着慢慢的活力回到了工作岗位。为了让大家在新的一年继续感受到 Tiny Engine 的成长与变化&#xff0c;我们很高兴地宣布&#xff1a;TinyEngine v2.2版本正式发布&#xff01;本次更新带来了重要的功能增强------页面支持嵌套路由&#…

Web自动化之Selenium添加网站Cookies实现免登录

在使用Selenium进行Web自动化时&#xff0c;添加网站Cookies是实现免登录的一种高效方法。通过模拟浏览器行为&#xff0c;我们可以将已登录状态的Cookies存储起来&#xff0c;并在下次自动化测试或爬虫任务中直接加载这些Cookies&#xff0c;从而跳过登录步骤。 Cookies简介 …

Storm实时流式计算系统(全解)——中

storm编程的基本概念-topo-spout-bolt 例如下&#xff1a; storm 编程接口-spout的结构及组件实现 storm编程案例-spout组件-实现 这是我的第一个组件&#xff08;spout组件继承BaseRichSput&#xff09;所有重写内部的三个方法&#xff0c;用于接收数据&#xff08;这里数据是…

让deepseek更专业的提示词教程

一、明确需求和目标 在使用DeepSeek之前&#xff0c;首先要明确你的需求和目标。例如&#xff0c;你是要生成一篇学术论文的摘要&#xff0c;还是一个商业文案的大纲&#xff0c;亦或是一段技术分析。明确的目标可以帮助你更有针对性地编写提示词。 二、使用专业术语和结构化…

《Python实战进阶》No 9:使用 Celery 实现异步任务队列

第9集&#xff1a;使用 Celery 实现异步任务队列 引言 在现代 Web 应用中&#xff0c;许多操作&#xff08;如发送邮件、处理文件上传、执行复杂计算等&#xff09;可能需要耗费较长时间。如果这些操作直接在主线程中执行&#xff0c;会导致用户请求阻塞&#xff0c;降低用户体…

【Mark】记录用宝塔+Nginx+worldpress+域名遇到的跨域,301,127.0.0.1,CSS加载失败问题

背景 想要用宝塔搭建worldpress&#xff0c;然后用域名直接转https&#xff0c;隐藏掉ipport。 结果被折磨了1天&#xff0c;一直在死活在301&#xff0c;127.0.0.1打转 还有css加载不了的情况 因为worldpress很多是301重定向的&#xff0c;所以改到最后我都不知道改了什么&am…

Linux | Ubuntu 与 Windows 双系统安装 / 高频故障 / UEFI 安全引导禁用

注&#xff1a;本文为 “buntu 与 Windows 双系统及高频故障解决” 相关文章合辑。 英文引文&#xff0c;机翻未校。 How to install Ubuntu 20.04 and dual boot alongside Windows 10 如何将 Ubuntu 20.04 和双启动与 Windows 10 一起安装 Dave’s RoboShack Published in…

计算机毕业设计SpringBoot+Vue.js手机商城 (源码+文档+PPT+讲解)

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

CSS—隐藏元素:1分钟掌握与使用隐藏元素的方法

个人博客&#xff1a;haichenyi.com。感谢关注 1. 目录 1–目录2–display:none3–visibility: hidden4–opacity: 05–position: absolute;与 left: -9999px;6–z-index 和 position7–clip-path: circle(0%) 2. display:none 标签会挂载在html中&#xff0c;但是不会在页面上…

EtherCAT总线学习笔记

一、EtherCAT的概述&#xff1a; EtherCAT是由德国BECKHOFF自动化公司于2003年提出的 实时工业以太网技术。它具有高速和高数据有效率的特点&#xff0c;支持多种设备连接拓扑结构。其 从站节点使用专用控制芯片&#xff0c;主站使用标准的以太网控制器。 EtherCAT的主要特点如…

WebRTC与PJSIP:呼叫中心系统技术选型指南

助力企业构建高效、灵活的通信解决方案 在数字化时代&#xff0c;呼叫中心系统的技术选型直接影响客户服务效率和业务扩展能力。WebRTC与PJSIP作为两大主流通信技术&#xff0c;各有其核心优势与适用场景。本文从功能、成本、开发门槛等维度为您深度解析&#xff0c;助您精准匹…

Vue-Flow绘制流程图(Vue3+ElementPlus+TS)简单案例

本文是vue3Elementplusts框架编写的简单可拖拽绘制案例。 1.效果图&#xff1a; 2.Index.vue主代码&#xff1a; <script lang"ts" setup> import { ref, markRaw } from "vue"; import {VueFlow,useVueFlow,MarkerType,type Node,type Edge } fro…

如何通过 LlamaIndex 将数据导入 Elasticsearch

作者&#xff1a;来自 Elastic Andre Luiz 逐步介绍如何使用 RAG 和 LlamaIndex 提取数据并进行搜索。 在本文中&#xff0c;我们将使用 LlamaIndex 来索引数据&#xff0c;从而实现一个常见问题搜索引擎。 Elasticsearch 将作为我们的向量数据库&#xff0c;实现向量搜索&am…

Boosting

Boosting 学习目标 知道boosting集成原理和实现过程知道bagging和boosting集成的区别知道AdaBoost集成原理 Boosting思想 Boosting思想图 每一个训练器重点关注前一个训练器不足的地方进行训练通过加权投票的方式&#xff0c;得出预测结果串行的训练方式 1 什么是boosting 随着…