I/O复用--浅谈epoll

news2025/1/13 14:25:02

我们聊了聊select和poll知道:

  • 它们都是采取轮询的方式查找是否有就绪描述符。
  • 都有数据结构从用户态拷贝到内核态,内核态拷贝到用户态这个过程。

为了针对许多大量连接,高并发的的场景下大量的资源消耗,效率低的问题,这一节就浅浅来聊一下epoll,epoll是之前的select和poll的增强版本,是linux操作系统独有的I/O复用技术。

对于epoll来说他更灵活,解决了select和poll的弊端,使用起来也更加方便顺手,他不像select和poll那样只提供了一个方法,epoll提供了一组方法。

本节呢就是聊聊epoll的使用和一些优点,对于epoll的两种触犯机制ET和LT的探讨会放在下一节去聊聊,注意select和poll只是LT。

好了~言归正传

目录

epoll的工作原理:

epoll的API:

epoll的特点:

用epoll实现tcp服务器的并发


epoll的工作原理:

  • 1. 内核中保存一份文件描述符集合,无需用户每次都重新传入,只需告诉内核修改的部分即可。
  • 2. 内核不再通过轮询的方式找到就绪的文件描述符,而是通过异步 IO 事件唤醒。
  • 3. 内核仅会将有 IO 事件的文件描述符返回给用户,用户也无需遍历整个文件描述符集合。

用大白话去理解就是,比如你是老师,你给同学们布置了作业,你在班里要检查作业的时候 不是你一个个去问,“啊,你写完了没有啊?”,而是你坐在讲台上,谁写完了,谁把作业拿上来给你,比方说有三个人写完了,这三个人就把作业交给你,你就会知道“哦,有三个人写完了”,至于剩下没交给你的那就是没写完的,你也不需要去问~  

大概就是这样了,关键就是不用你去一个个问,而是谁完了,谁通知你

前面说了epoll提供了一组api,方便我们去使用,下来我们看看~

epoll的API:

epoll的核心是3个API,核心数据结构是:1个红黑树和1个链表组成。

【1. 创建内核事件表:】

函数原型为:

#include <sys/epoll.h>
int epoll_create(int size)
          //成功返回内核事件表的标识符,失败返回-1

功能:该函数 创建内核事件表用于存放描述符和关注的事件。调用这个函数的时候,在内核cache里建立了红黑树struct rb_root用于存储以后epoll_ctl传来的socket,也就是内核事件表;还建立了一个双向链表struct list_headrdllidt用于存储就绪事件。 当epoll_wait调用时,仅仅观察这个双向链表里有没有数据即可,有数据表示有就绪事件,直接返回。
size参数:表示创建的事件表需要多大,记住最后要close关闭,如果不关闭,那么就会导致fd被耗尽

注意:

size参数告诉内核这个epoll对象会处理的事件⼤致数量,⽽不是能够处理的事件的最⼤数(同时,size不要传0,会报invalid argument错误)。
在现在linux版本中,这个size参数已经没有意义了;
返回: epoll对象句柄;之后针对该epoll的操作需要通过该句柄来标识该epoll对象;

【2. 管理内核事件表:】

可以对要监听的事件进行操作:注册,删除,修改

返回: 0表示成功, -1表示错误,根据errno错误码判断错误类型。

原型如下:

#include <sys/epoll.h>
int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event);
                 //成功返回0,失败返回-1.

 参数:

  • epfd:内核事件表的标识符。
  • op:标识操作,有:添加,删除,修改。不用自己写了,根据第二个op参数来选择
EPOLL_CTL_ADD //注册新的文件描述符到内核事件表epfd中
EPOLL_CTL_DEL //从内核事件表中删除文件描述符
EPOLL_CTL_MOD //修改文件描述符
  • fd:要操作的文件描述符。
  • events:保存事件类型,用户填充告诉内核要对哪种事件进行操作。所以需要先定义event结构体,然后再进行操作,表示对何种事件类型进行何种操作
struct epoll_event
{
   short events;//事件类型:在每一个poll的事件类型标识前加个‘E’
   Union epoll_data_t data(联合体,用到其中的fd成员即文件描述符,其他的不用)
}
typedef union epoll_data {
    void *ptr; /* 指向用户自定义数据 */
    int fd; /* 注册的文件描述符 */
    uint32_t u32; /* 32-bit integer */
    uint64_t u64; /* 64-bit integer */
} epoll_data_t;

event.events 取值:

EPOLLIN 表示该连接上有数据可读(tcp连接远端主动关闭连接,也是可读事件,因为需要处理发送来的FIN包; FIN包就是read 返回 0)
EPOLLOUT 表示该连接上可写发送(主动向上游服务器发起⾮阻塞tcp连接,连接建⽴成功事件相当于可写事件)
EPOLLRDHUP 表示tcp连接的远端关闭或半关闭连接
EPOLLPRI 表示连接上有紧急数据需要读
EPOLLERR 表示连接发⽣错误
EPOLLHUP 表示连接被挂起
EPOLLET 将触发⽅式设置为边缘触发,系统默认为⽔平触发
EPOLLONESHOT 表示该事件只处理⼀次,下次需要处理时需重新加⼊epoll

假如现在我们将监听文件描述符添加到内核事件表中:

struct epoll_event event;
event.events=EPOLLIN;//监听读事件
event.data.fd=listenfd;//被监听的文件描述符
int res=epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&event);//将event结构体中存储的事件类型添加到内核事件表中。

【3. 开始监听内核事件表的事件】

int epoll_wait(int epfd,struct epoll_event events[],int maxevents,int timeout)
              // 成功返回就绪个数,失败-1,超时0

参数:

  • epfd:内核事件表;
  • events[]:events的数据是 由内核在epoll_wait返回时填充的,有事件就绪的文件描述符和就绪的事件类型;
  • maxevents:数组的长度,表示一次epoll_wait最多返回多少个就绪的文件描述符。
  • timeout:监听时间。

收集 epoll 监控的事件中已经发⽣的事件,如果 epoll 中没有任何⼀个事件发⽣,则最多等待timeout 毫秒后返回。
返回:表示当前发⽣的事件个数
返回0表示本次没有事件发⽣;
返回-1表示出现错误,需要检查errno错误码判断错误类型。

epoll的特点:

【1. 速度快:】

  • select中为fd_set,poll 为 struct pollfd fds[],它们都是用户创建的,用户态存在,每调用一次select或者poll,都会存在两次用户态->内核,内核->用户的数据拷贝,一次是在调用时,一次是在返回时。这样数据结构越大,则越慢。
  • epoll直接就在内核中记录,将其都记录在内核事件表上,在监听时不需要进行拷贝,所以速度比他俩快.

【 2. 查找时间复杂度低:】

  • select,poll返回就绪事件的个数,但不知道在哪,所以要轮询找,每次用户检索就绪事件的时间复杂度为O(N)。
  • epoll直接返回就绪事件链表,链表中有值就有就绪事件,所以每次都是就绪的不用找,每次用户检索就绪事件的时间复杂度为O(1)。

【3. 采取回调函数方式处理就绪事件】

  • select,poll采用轮询方式检测是否有事件发生,循环检测是否有事件就绪,直到找不到。适合关注的文件描述每次都有很大的机率就绪,1000个有900多个就绪,那么就很适合,但是1000个只有一个事件发生,还是要进行轮询1000多次,函数栈帧空间频繁,影响效果;
  • epoll的内核采用的是回调的方式检测文件描述符是否有事件发生。假设1000个,把文件描述符添加到事件表上,文件描述符在红黑树上挂载,除了值,事件类型,还带有一个回调函数,如果文件描述符有事件发生,它就调用回调函数,回调函数的作用是有事件就绪时就把文件描述符添加到双向链表中,返回时把链表的值拷贝到用户态。1000个一个事件发生只会触发一次回调,不用多次循环。适合很多文件描述符,就绪事件描述符少。

用epoll实现tcp服务器的并发

因为epoll封装了很多函数,所以操作起来比起selet和poll代码简洁了很多

服务器代码如下:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
//网络头文件
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/epoll.h>
#define MAX 10//定义最大连接数为10个

int InitSocket()
{
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd == -1) return -1;
    struct sockaddr_in ser;//指明地址信息,是一种通用的套接字地址
    memset(&ser,0,sizeof(ser));
    ser.sin_family = AF_INET;//设置地址家族
    ser.sin_port = htons(6000);//设置端口
    ser.sin_addr.s_addr = inet_addr("127.0.0.1");//设置地址
    int res = bind(sockfd,(struct sockaddr*)&ser,sizeof(ser));//绑定端口号和地址
    if(res == -1)   return -1;
    res = listen(sockfd,5);
    if(res == -1) return -1;
    return sockfd;
}
void epoll_add(int epfd,int fd)
{
    struct epoll_event ev;
    ev.events=EPOLLIN;//读
    ev.data.fd=fd;
    if(epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev)==-1)
    {
        printf("epoll add failed\n");
    }
}
void epoll_del(int epfd,int fd)
{
    if(epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL)==-1)
    {
        printf("epoll del failed\n");
    }

}
void accept_client(int epfd,int sockfd)
{
    struct sockaddr_in caddr;
    int len=sizeof(caddr);
    int c=accept(sockfd,(struct sockaddr*)&caddr,&len);
    if(c<0)
    {
        return ;
    }
    printf("accpet c=%d ip=%s\n",c,inet_ntoa(caddr.sin_addr));
    epoll_add(epfd,c);
}
void recv_data(int epfd,int c)
{
    char buff[128]={0};
    int num=recv(c,buff,127,0);
    if(num<=0)//如果num==0说明客户端结束了描述符号
    {
        epoll_del(epfd,c);//移除改客户端对应的描述符
        close(c);
        printf("client close\n");
        
        return ;
    }
    printf("buff (%d)=%s\n",c,buff);
    send(c,"ok",2,0);

}

 int main()
{
    int sockfd = InitSocket();//监听套接字,有客户端链接时就会触发读事件。
    assert(sockfd != -1);
    //创建内核事件表
    int epfd=epoll_create(MAX);//底层,红黑树
    if(epfd==-1)
    {
        exit(1);
    }
    epoll_add(epfd,sockfd);//将监听套接子添加到内核事件表
    struct epoll_event evs[MAX];//用来接收就绪的文件描述符
    while(1)
    {
        int n=epoll_wait(epfd,evs,MAX,5000);
        if(n==-1)
        {
            printf("err\n");
        }
        else if(n==0)
        {
            printf("time out\n");
        }
        else
        {//前n个元素是数据就绪的
            for(int i=0;i<n;++i)
            {
                int fd=evs[i].data.fd;
                if(evs[i].events&EPOLLIN)//看读事件是不是就绪
                {
                    if(fd==sockfd)
                    {
                        accept_client(epfd,sockfd);
                    }
                    else
                    {
                        recv_data(epfd,fd);

                    }

                }
                //if(evs[i].events&EPOLLOUT)
            }

        }
    }
    close(sockfd);
}

客户端的代码都是一样的,这里我就不粘了

ヾ(◍°∇°◍)ノ゙

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

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

相关文章

网络安全实战:记一次比较完整的靶机渗透

0x01信息搜集 nmap -sC -sV -p- -A 10.10.10.123 -T4 -oA nmap_friendzone访问80端口的http服务只发现了一个域名。 0x02 DNS区域传输 因为我们前面扫描的时候发现了53端口是开着一个dns服务的&#xff0c;所以尝试使用friendzoneportal.red进行axfr&#xff0c;它的具体含…

《软件测试》实验2:嵌入式软件测试实验报告

文章目录实验目的温度控制器需求文档及测试要求环境搭建实验内容温度采集处理功能测试加热棒输出电压测试散热风扇温度传感器输入接口&#xff08;Senser_JK&#xff09;控制加热棒输出接口&#xff08;Heater_JK&#xff09;控制散热风扇输出接口&#xff08;Fan_JK&#xff0…

目标检测 YOLOv5 - 模型推理预处理 letterbox

目标检测 YOLOv5 - 模型推理预处理 letterbox flyfish 版本&#xff1a;YOLOv5 6.2 假如图片大小是1080 * 1920 &#xff08;height * width &#xff09; width 1920 height 1080 当模型输入是 640 * 640时 shapes (1080, 1920), (0.33, 0.33), (0.0, 140.0) 640/ 1920…

c++11 右值引用和移动语义

文章目录1. 左值引用和右值引用2. 左值引用与右值引用比较3. 右值引用使用场景和意义3.1左值引用的使用场景3.2左值引用的短板&#xff1a;3.3 移动构造3.4 移动赋值3.5 STL中的容器3.6 右值引用引用左值及其一些更深入的使用场景分析3.7 STL容器插入接口函数也增加了右值引用版…

Springboot 使用装饰器模式,快看,它装起来了

前言 小飞棍来咯&#xff01; 本篇文章跟小飞棍一毛钱关系没有。 本篇内容&#xff1a; 就是配合实战案例介绍我们平时 在springboot项目里面 怎么去用 装饰器模式、多层装饰怎么玩。 正文 首先先说下装饰器模式是什么 官方&#xff1a; 装饰器模式&#xff08;Decorator Pa…

Python3,5行代码,制作Gif动图,太简单了。

gif动图制作1、引言2、代码实战2.1 安装2.2 代码3、总结1、引言 小屌丝&#xff1a;鱼哥&#xff0c; 你能不能帮我找一个动图&#xff0c; 小鱼&#xff1a;啥动图&#xff0c;你自己百度不就行了。 小屌丝&#xff1a;我这不是没找到吗&#xff0c; 不然我就自己来找了。 小…

Oracle数据库:net configureation assistant工具配置监听listener,配置本地网络访问服务器上的数据库

Oracle数据库&#xff1a;net configureation assistant工具配置监听listener&#xff0c;配置本地网络访问服务器上的数据库 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;可能很多算法学生都得去找开发&#xff0c;测开…

图像类找工作面试题(二)——常见问题大总结

文章目录一、深度学习问题1、目标检测系列&#xff08;1&#xff09;介绍目标检测网络YOLO以及SSD系列原理。&#xff08;2&#xff09;YOLO对小目标检测效果不好的原因&#xff0c;怎么改善&#xff1f;&#xff08;3&#xff09;怎么防止过拟合&#xff08;4&#xff09;Drop…

Ubuntu18.04LTS环境下创建OpenCV4.x-Android库

1 背景 1.1 java.lang.UnsatisfiedLinkError: dlopen failed: library “libc_shared.so” not found libc_shared.so 之前默认集成在 opencv_java3.so&#xff0c;但是在OpenCV4.x以后&#xff0c;该动态库默认不集成在opencv_java4.so。 1.2 E/OpenCV/StaticHelper: OpenC…

NLP学习之:Bert 模型复现(1)任务分析 + 训练数据集构造

​ 文章目录代码资源原理学习任务代码讲解代码重写说明代码资源 Bert-pytorch 原理 学习任务 Bert 本质上是 Transformer 的 Encoder 端&#xff0c;Bert 在预训练时最基本的任务就是&#xff1a; 判断输入的两个句子是否真的相邻预测被 [MASK] 掉的单词 通过这两种任务的约…

面了个腾讯拿28k跳槽出来的,真正见识到了跳槽天花板

最近内卷严重&#xff0c;各种跳槽裁员&#xff0c;相信很多小伙伴也在准备金九银十的面试计划。 作为一个入职5年的老人家&#xff0c;目前工资比较乐观&#xff0c;但是我还是会选择跳槽&#xff0c;因为感觉在一个舒适圈待久了&#xff0c;人过得太过安逸&#xff0c;晋升涨…

ToDesk使用

现在的终端产品种类非常的多&#xff0c;常见的包括tablet, 手机&#xff0c;笔记本 ,ipod...等等&#xff0c;这些终端带屏产品连同台式机&#xff0c;智能电视等固定设备占据了我们的工作和生活中的大部分时间&#xff0c;不知道你发现没有&#xff0c;使这些不同种类的产品之…

[机器学习、Spark]Spark MLlib实现数据基本统计

&#x1f468;‍&#x1f393;&#x1f468;‍&#x1f393;博主&#xff1a;发量不足 &#x1f4d1;&#x1f4d1;本期更新内容&#xff1a;Spark MLlib基本统计 &#x1f4d1;&#x1f4d1;下篇文章预告&#xff1a;Spark MLlib的分类&#x1f525;&#x1f525; 简介&…

Java多线程【锁优化与死锁】

Java多线程【锁优化与死锁】&#x1f352;一.synchronized的优化&#x1f34e;1.1基本特点&#x1f34e;1.2加锁工作过程&#x1f34e;1.3优化操作&#x1f352;二.死锁&#x1f34e;2.1什么是死锁&#x1f34e;2.2 一对一死锁&#x1f34e;2.3 二对二死锁&#x1f34e;2.4 N对…

Web 1.0、Web 2.0 和 Web 3.0 之间的比较

&#x1f482; 个人网站:【海拥】【摸鱼小游戏】【神级源码资源网站】&#x1f91f; 风趣幽默的前端学习课程&#xff1a;&#x1f449;28个案例趣学前端&#x1f485; 想寻找共同学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】&#x1f4ac; 免费且实用的 P…

目前最先进的神经网络算法,神经网络算法发展

1、神经网络的发展趋势如何&#xff1f; 神经网络的云集成模式还不是很成熟&#xff0c;应该有发展潜力&#xff0c;但神经网络有自己的硬伤&#xff0c;不知道能够达到怎样的效果&#xff0c;所以决策支持系统中并不是很热门&#xff0c;但是神经网络无视过程的优点也是无可替…

HTML5新特性

H5新增 1) 新增选择器 document.querySelector、document.querySelectorAll doucment.querySelector(#sampel) // 选择id doucment.querySelector(.sampel) // 选择出现的第一个类 doucment.querySelectorAll(.sampel)[0] // 选择出现的第一个类2)拖拽释放(Drag and drop) AP…

2022年江西省赣育杯网络安全大赛学生组WebMisc Writeup

文章目录WEB签到easyzphpezpyMISCbyteMuisc有趣的PDFWEB 签到 ?id-1union select 1,(select group_concat(schema_name) from information_schema.schemata),3,4,5--?id-1union select 1,(select group_concat(table_name) from information_schema.tables where table_sch…