io多路复用中的epoll函数详解

IO多路复用之epoll函数详解 epoll   在linux的网络编程中,很长的时间都在使用select来做事件触发。在linux新的内核中,有了一种替换它的机制,就是epoll。   相比于select,epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。因为在内核中的select实现中,它是采用轮询来处理的,轮询的fd数目越多,自然耗时越多。   相对于select和poll来说,epoll更加灵活,没有描述符 阅读详情

epoll介绍

epoll提供了三个函数,epoll_create、epoll_ctl和epoll_wait。
1 首先创建一个epoll对象,
2 然后使用epoll_ctl对这个对象进行操作(添加、删除、修改),把需要监控的描述符加进去,这些描述符将会以epoll_event结构体的形式组成一颗红黑树
3 接着阻塞在epoll_wait,进入大循环,当某个fd上有事件发生时,内核将会把其对应的结构体放入一个链表中,返回有事件发生的链表。

epoll的三个函数介绍

    int epoll_create(int size);
    - 参数:
    size : 目前没有意义了。随便写一个数,必须大于0
    - 返回值:
    -1 : 失败
    > 0 : 文件描述符,操作epoll实例的


    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 
    };

    常见的Epoll检测事件:
        - EPOLLIN
        - EPOLLOUT
        - EPOLLERR
    // 对epoll实例进行管理:添加文件描述符信息,删除信息,修改信息
    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    - 参数:
        - epfd : epoll实例对应的文件描述符
        - op : 要进行什么操作
            EPOLL_CTL_ADD: 添加
            EPOLL_CTL_MOD: 修改
            EPOLL_CTL_DEL: 删除
        - fd : 要检测的文件描述符
        - event : 检测文件描述符什么事情
    // 检测函数
    int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
    - 参数:
        - epfd : epoll实例对应的文件描述符
        - events : 传出参数,保存了发送了变化的文件描述符的信息
        - maxevents : 第二个参数结构体数组的大小
        - timeout : 阻塞时间
            - 0 : 不阻塞
            - -1 : 阻塞,直到检测到fd数据发生变化,解除阻塞
            - > 0 : 阻塞的时长(毫秒)
    - 返回值:
        - 成功,返回发送变化的文件描述符的个数 > 0
        - 失败 -1

使用epoll函数监听多个文件描述符的服务端程序

水平触发模式

#include<stdio.h>
#include<stdlib.h>
#include<arpa/inet.h>
#include<string.h>
#include<unistd.h>
#include<sys/epoll.h>
int main(char* argc,int argv){
    //创建socket
    int lfd=socket(PF_INET,SOCK_STREAM,0);

    //绑定
    struct sockaddr_in saddr;
    saddr.sin_port=htons(9999);
    saddr.sin_family=AF_INET;
    inet_pton(AF_INET,"127.0.0.1",&saddr.sin_addr.s_addr);
    bind(lfd,(struct sockaddr*)&saddr,sizeof(saddr));

    //监听
    listen(lfd,8);

    //使用epoll_creat()创建epoll实例
    int epfd=epoll_create(1);

    //将监听的的文件描述符添加到epoll实例中
    struct epoll_event epev;
    epev.events=EPOLLIN;
    epev.data.fd=lfd;
    epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&epev);

    struct epoll_event epevs[100];//100个epoll_event
    while(1){
        //调用poll系统函数,让内核帮忙检测文件描述符是否有数据
        int ret=epoll_wait(epfd,epevs,sizeof(epevs)/sizeof(struct epoll_event),-1);//阻塞
        if(ret==-1){
            perror("epoll_wait");
            exit(0);
        }else if(ret==0){
            //超时时间到了   没有检测到  就为0  
            //程序中设置为永久阻塞  所以不会出现这种情况
            continue;
        }else{
            printf("检测到可读的文件描述符个数:%d\n",ret);
            for(int i=0;i<ret;++i){
                if(epevs[i].data.fd==lfd){
                    // 表示有新的客户端连接进来了
                    struct sockaddr_in cliaddr;
                    int len = sizeof(cliaddr);
                    int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len);

                    //获取客户端信息
                    char clientIp[16];
                    inet_ntop(AF_INET,&cliaddr.sin_addr.s_addr,clientIp,sizeof(clientIp));
                    unsigned int clientPort=ntohs(cliaddr.sin_port);
                    printf("客户端ip:%s,端口:%d\n",clientIp,clientPort);

                    //将通信的文件描述符添加到epoll实例中去
                    struct epoll_event epev;
                    epev.events=EPOLLIN;
                    epev.data.fd=cfd;
                    epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&epev);
                }else if(epevs[i].events&EPOLLIN){
                    //有数据
                    char recvBuf[100]={0};
                    int len=read(epevs[i].data.fd,recvBuf,sizeof(recvBuf));
                    if(len>0){
                        printf("read buf = %s\n", recvBuf);
                        write(epevs[i].data.fd, recvBuf, strlen(recvBuf) + 1);
                    }else if(len==0){
                        printf("client close...\n");
                        epoll_ctl(epfd,EPOLL_CTL_DEL,epevs[i].data.fd,NULL);//将该文件描述符从epoll实例中删除
                        close(epevs[i].data.fd);
                    }else{
                        perror("read");
                        exit(-1);
                    }
                }
            }
        }
    }
    close(lfd);
    close(epfd);
    return 0;
}

边沿触发模式

#include<stdio.h>
#include<stdlib.h>
#include<arpa/inet.h>
#include<string.h>
#include<unistd.h>
#include<sys/epoll.h>
#include <fcntl.h>
#include <errno.h>
//et模式的epoll  默认模式
int main(char* argv,int argc){
    int lfd=socket(PF_INET,SOCK_STREAM,0);

    struct sockaddr_in saddr;
    saddr.sin_port=htons(9999);
    saddr.sin_addr.s_addr=INADDR_ANY;
    saddr.sin_family=AF_INET;

    bind(lfd,(struct sockaddr*)&saddr,sizeof(saddr));

    listen(lfd,8);

    int epfd=epoll_create(100);
    struct epoll_event epev;
    epev.data.fd=lfd;
    epev.events=EPOLLIN;
    epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&epev);

    struct epoll_event epevs[128];
    while(1){
         //调用poll系统函数,让内核帮忙检测文件描述符是否有数据
        int ret=epoll_wait(epfd,epevs,128,-1);//阻塞
        if(ret==-1){
            perror("epoll_wait");
            exit(-1);
        }else if(ret==0){
            //超时时间到了   没有检测到  就为0  
            //程序中设置为永久阻塞  所以不会出现这种情况
            continue;
        }else{
            printf("检测到可读的文件描述符个数:%d\n",ret);
            for(int i=0;i<ret;++i){
                if(epevs[i].data.fd==lfd){
                     // 表示有新的客户端连接进来了
                    struct sockaddr_in cliaddr;
                    int len = sizeof(cliaddr);
                    int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len);

                    //获取客户端信息
                    //char clientIp[16];
                    //inet_ntop(AF_INET,&cliaddr.sin_addr.s_addr,clientIp,sizeof(clientIp));
                    //unsigned int clientPort=ntohs(cliaddr.sin_port);
                    //printf("客户端ip:%s,端口:%d\n",clientIp,clientPort);

                    //设置cfd属性非阻塞
                    int flag=fcntl(cfd,F_GETFL);
                    flag|=O_NONBLOCK;
                    fcntl(cfd,F_SETFL,flag);

                    
                    //将通信的文件描述符添加到epoll实例中去
                    epev.data.fd=cfd;
                    epev.events=EPOLLIN|EPOLLET;//设置边沿触发
                    epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&epev);
                }else if(epevs[i].events&EPOLLIN){
                    //有数据
                    char recvBuf[5]={0};
                    int len=0;
                    while((len=read(epevs[i].data.fd,recvBuf,sizeof(recvBuf)))>0){
                        //printf("read buf = %s\n", recvBuf);
                        write(STDOUT_FILENO, recvBuf, len);
                        write(epevs[i].data.fd, recvBuf, len);
                    }
                    if(len==0){
                        printf("client close...\n");
                        epoll_ctl(epfd,EPOLL_CTL_DEL,epevs[i].data.fd,NULL);//将该文件描述符从epoll实例中删除
                        close(epevs[i].data.fd);
                    }else if(len==-1){
                        if(errno == EAGAIN) {//在非阻塞的时候 表示没有读到数据  应该忽略  在阻塞的时候是读写超时
                            printf("data over.....\n");
                        }else {
                            perror("read");
                            exit(-1);
                        }
                    }
                }
            }
        }
    }
    close(lfd);
    close(epfd);
    return 0;
}

Epoll 的两种工作模式:

LT 模式 (默认水平触发)

假设委托内核检测读事件 -> 检测fd的读缓冲区
读缓冲区有数据 - > epoll检测到了会给用户通知
a.用户不读数据,数据一直在缓冲区,epoll 会一直通知
b.用户只读了一部分数据,epoll会通知
c.缓冲区的数据读完了,不通知
LT(level - triggered)是缺省的工作方式,并且同时支持 block 和 no-block socket。在这
种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的 fd 进行 IO 操
作。如果你不作任何操作,内核还是会继续通知你的。

ET 模式(边沿触发)

假设委托内核检测读事件 -> 检测fd的读缓冲区
读缓冲区有数据 - > epoll检测到了会给用户通知
a.用户不读数据,数据一致在缓冲区中,epoll下次检测的时候就不通知了
b.用户只读了一部分数据,epoll不通知
c.缓冲区的数据读完了,不通知

ET(edge - triggered)是高速工作方式,只支持 no-block socket。在这种模式下,当描述
符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,
并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述
符不再为就绪状态了。但是请注意,如果一直不对这个 fd 作 IO 操作(从而导致它再次变成
未就绪),内核不会发送更多的通知(only once)。
ET 模式在很大程度上减少了 epoll 事件被重复触发的次数,因此效率要比 LT 模式高。epoll
工作在 ET 模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写
操作把处理多个文件描述符的任务饿死。

ET模式下每次wrile或read需要循环write或read直到返回EAGAIN错误。以读操作为例,这是因为ET模式只在socket描述符状态发生变化时才触发事件,如果不一次把socket内核缓中区的数据读完,会导致socket内核缓冲区中即使还有一部分数据,该socke的可读事件也不会被触发,根据上面的讨论若ET模式下使用阻塞IO,则程序一定会阻塞在最后一次write或read操作,因此说ET模式下一定要使用非阻塞IO

Epoll的优点

1:select,poll实现在用户程序在遍历哪些文件描述符就绪,而epoll会返回就绪链表,直接访问即可,这节省了CPU时间。

2:select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,而epoll只要一次拷贝,这也能节省不少的开销。

Linux epoll 机制的核心控制函数——`epoll_ctl` epoll_ctl是Linux epoll机制的核心函数,用于动态管理epoll实例监控的文件描述符集合。它支持三种操作:EPOLL_CTL_ADD添加监听、EPOLL_CTL_MOD修改事件类型、EPOLL_CTL_DEL移除监控。使用时需指定epoll实例描述符、操作类型、目标文件描述符及事件配置结构体指针。函数成功返回0,失败返回-1并设置errno。典型应用包括:服务器启动时添加监听socket,连接建立后修改客户端socket事件类型,连接关闭时移除描述符。需注意操作顺序、错误处理及边缘触发模式的 阅读详情

相关推荐

Linux 高性能 I/O 事件通知机制的核心系统调用—— `epoll_ctl`

epoll是Linux高效处理大量I/O事件的核心机制,epoll_ctl作为其关键操作函数,用于动态管理被监视的文件描述符集合。与传统的select/poll相比,epoll通过内核持久化存储和增量操作大幅提升了性能。epoll_ctl支持添加(EPOLL_CTL_ADD)、修改(EPOLL_CTL_MOD)和删除(EPOLL_CTL_DEL)操作,通过epoll_event结构体精细控制事件类型(如可读/可写)和关联用户数据。典型应用场景如高并发TCP服务器,通过非阻塞socket和边缘触发模式实现高效

weixin_42108533的博客 1281

epoll学习:epoll_ctl函数详解

epoll_ctl函数:向文件描述符epfd引用的epoll实例执行控制操作(op:添加、修改或者删除)。

个人工作中学到的专业技术与职业发展知识笔记 9589

Linux网络编程(epoll函数的使用)

本篇文章我们讲解epoll函数的使用方法,epoll相比于poll来说性能方面有所提升和改进。本篇文章就讲解到这里,下篇文章继续讲解Linux网络编程的知识。

m0_49476241的博客 2451

epoll详解_Linux下的I/O复用与epoll详解

前言 I/O多路复用有很多种实现。在linux上,2.4内核前主要是select和poll,自Linux 2.6内核正式引入epoll以来,epoll已经成为了目前实现高性能网络服务器的必备技术。尽管他们的使用方法不尽相同,但是本质上却没有什么区别。本文将重点探讨将放在EPOLL的实现与使用详解。参考资料:嵌入式网络编程 - linux下的IO模型嵌入式应用层开发 - Linux开发必备...

weixin_39874881的博客 733

✔B【AD623】【单运放】仪表放大器 AD623电压放大器模块可调 单电源 单端/差分 微小信号

【】仪表放大器 AD623电压放大器模块可调 单电源 单端/差分 微小信号

童话与梦的博客 8279

Cadence OrCAD: 层次化设计 Hierarchical Design

详细介绍OrCAD的原理图层次化设计方法。

Fantasy237的博客 1万+

redis高级篇 redis的IO多路复用与Epoll函数

一 IO多路复用 1.1 IO多路复用作用

健康平安的活着的专栏 846

VizCut 全自动批量视频二次去重处理!支持视频无水印解析下载和批量剪辑,完全免费,使用简单!

可制作和分享剪辑模板,已提供20+种剪辑方案,内置众多扫光蒙版素材。支持二次去重批量处理,完全免费,无广告,且支持视频无水印解析下载,非常强大。如果说我们处理的视频需要进行一样的参数设置的话,就可以把我,1设置的参数设置为模板,后面就可以一键调用。1. 本地CPU处理,可设置线程数,可开启GPU加速,无任何限制,全看机器性能。适用场景:自媒体搬运、短视频二创、批量去重等,完全免费,无广告干扰!3. 20+种参数可调可选,内置很多相关素材。可保存为模板,可复用、可分享、可导入。2. 所有参数的设置,

goodfat的博客 5320

IO多路复用,使用select,poll,epoll三个函数分别实现

有关IO多路复用的select,poll,epoll三个函数的基本操作

m0_68210771的博客 397

[misc]智能车协议分析:2020网鼎杯青龙组misc_teslaaaaa_wp

[misc]智能车协议分析:2020网鼎杯青龙组misc_teslaaaaa_wp 文章目录[misc]智能车协议分析:2020网鼎杯青龙组misc_teslaaaaa_wp分析log文件格式分析协议组成UDS网络传输层UDS统一诊断服务协议分析文件提取文件逆向flag计算参考链接: teslaaaaa这道题是青龙组被做出来的题目中做出来人数最少的,直到比赛快结束才有第一个人做出来,怎么说,难并不是因为脑洞大,讲道理这道题是一道好题,我当时也是肝了一天没做出来,我倒是把文件提取出来了,奈何比赛时间已经接近尾

Breeze的博客 1万+

UG NX 12.0零基础实战:从草图绘制到三维建模全流程解析

本文详细解析UG NX 12.0从草图绘制到三维建模的全流程,适合零基础用户快速掌握这款工业设计软件。内容涵盖界面设置、基础操作、草图技巧、拉伸旋转建模、布尔运算等核心功能,并通过机械零件实战案例演示完整设计流程,帮助读者系统学习UG NX 12.0的工业设计应用。

weixin_29261711的博客 1172

【linux系统】IO多路复用的三个函数select、poll和epoll

IO多路复用的三个函数select、poll和epoll1.引出IO多路复用2.select函数——同步IO多路复用3.poll函数——等待一个fd上的events4.epoll函数——IO事件通知5.三个函数的比较 1.引出IO多路复用 以下内容参考了https://blog.youkuaiyun.com/SkydivingWang/article/details/74917897 我们都知道unix(...

moX980的博客 770

YOLO11有效改进专栏目录

YOLO11改进专栏导航

A1983Z的博客 3675

IO多路复用概念以及所需要用到的函数select、poll、epoll

IO多路复用概念以及所需要用到的函数select、poll、epoll

y_bra_ke的博客 262

【实战项目开发技术分享】ROS1.0、ROS 2.0和CyberRT开源通信框架对比

随着机器人和自动驾驶技术的飞速发展,开源通信框架在机器人、自动驾驶行业扮演着重要的角色。ROS1.0是一个广泛应用于机器人领域的开源框架,ROS 2.0是其改进版本,而CyberRT是Baidu Apollo自动驾驶平台的通信框架。ROS和ROS 2.0都注重灵活性和共享性,适用于各种机器人应用。CyberRT则专注于实时性和高性能,适用于自动驾驶等对实时性要求较高的场景。选择适合的框架取决于具体应用需求和开发者的偏好。

xiewf的博客 2万+

Apollo Planning2.0决策规划算法代码详细解析 (1):环境搭建

1、官方提供连接:git clone https://github.com/ApolloAuto/application-pnc.git。此外,也更新了dreamview对pnc的支持,使得调试更加方便。apollo开源团队近期更新了planning版本,对代码进行了一定程度上的重构。(2)进入docker,并编译整个工程:./apollo.sh build_dbg。下载项目环境后使用aem进行环境配置;2、有一定基础建议直接拉取最新代码,自己手动进行编译。(1)环境安装: ./dev_start.sh。

自动驾驶Player的博客 1840

Linux下epoll函数的解析(IO多路复用)

epoll实现IO多路复用

m0_69038505的博客 501

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

IO多路复用[select,poll,epoll]常用函数,概念,详解

本文摘要: IO多路复用技术通过单线程管理多个IO通道,提高并发处理效率。核心概念包括注册关注事件、等待事件发生和分发处理。主要方法对比: select - 跨平台但效率低,使用位图管理fd,有1024限制; poll - 改进select的fd数量限制,但仍需O(n)扫描; epoll - Linux高效方案,O(1)复杂度,支持边缘触发和水平触发模式。 关键区别:select/poll每次调用需重建fd集合,epoll则在内核维护兴趣列表。epoll适合高并发场景。

多多爱学习 919

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

关于IO多路复用函数(select、poll、epoll)的一些理解

关于IO多路复用函数(select、poll、epoll)的一些理解 一、阻塞现象 什么是阻塞现象? ​ 当一个数据流中再也没有数据,read的时候,或者,我们的流中写满了数据在去write的时候,即无数据可读和无空间可写的尴尬现象,称为阻塞现象 阻塞等待,非阻塞忙轮询 比如这个例子,送快递与收快递的例子 阻塞等待相当于快递员在送快递的过程中,客户可以空出大脑睡大觉(不占用CPU的宝贵时...

qq_40028201的博客 486

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

操作系统- IO多路复用 epoll函数

IO多路复用是什么的解释请查看上一篇文章 操作系统- IO多路复用_an13654067079的博客-优快云博客 本文重点讲解epoll函数的应用 2 epoll函数解决了什么问题,存在的价值是什么 函数存在的意义是更快的通知线程来处理这次事件 ...

an13654067079的博客 263

Redis IO多路复用底层原理详解,Select,poll,epoll函数三者的区别?

本文摘要:文章详细介绍了五种UNIX网络I/O模型,重点分析了阻塞I/O、非阻塞I/O和I/O多路复用的实现原理。通过对比用户空间、内核空间与硬件交互的过程,解释了不同I/O模型的阻塞机制。特别深入剖析了I/O多路复用的三种实现方式:select函数存在的1024限制和两次拷贝问题,poll函数改进后的无上限链表结构,以及epoll函数采用红黑树和链表的高效机制(包括LT和ET两种事件通知模式)。最后以Web服务为例,展示了epoll实现I/O多路复用的完整流程,体现了其在处理高并发连接时的优势。

m0_70325779的博客 494

IO多路复用(select函数、poll函数、epoll函数)

【代码】IO多路复用(select函数、poll函数、epoll函数)

qq_41878292的博客 186

IO复用之epoll

IO多路复用

Daisy_G_Jasmine的博客 773
上一篇: io多路复用中的poll函数详解
下一篇: 网络通信中的五种IO模型
nuaa蟑螂恶霸
博客等级 码龄8年 21粉丝 22原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值