Linux: I/O多路转接之epoll(有图有代码有真相!!!)

一、基本概念

epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。

另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。epoll除了提供select/poll那种IO事件的

水平触发(Level Triggered)外,还提供了边缘触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。

二、epoll函数解析

epoll过程分为三个接口

#include <sys/epoll.h>

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


1、epoll_create(int size):

创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,

在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。


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

epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。

第一个参数是epoll_create()的返回值;

第二个参数表示动作,用三个宏来表示:

EPOLL_CTL_ADD:注册新的fd到epfd中;

EPOLL_CTL_MOD:修改已经注册的fd的监听事件;

EPOLL_CTL_DEL:从epfd中删除一个fd;

第三个参数是需要监听的fd;

第四个参数是告诉内核需要监听什么事,struct epoll_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 :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

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

等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个maxevents的值不

能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。



三、epoll 的工作原理

epoll同样只告知那些就绪的⽂文件描述符,⽽而且当我们调⽤用epoll_wait()获得就绪⽂文件描述符时,返回的不是实际的描述符,⽽而是⼀一个代表就绪描述符数量的值,

你只需要去epoll指定的⼀个数组中依次取得相应数量的⽂文件描述符即可,这⾥里也使⽤用了内存映射(mmap)技术,这样便彻底省掉了这些⽂文件描述符在系统调⽤用时复制的开销。

另⼀一个本质的改进在于epoll采⽤用基于事件的就绪通知⽅方式。在select/poll中,进程只有在调⽤用⼀一定的⽅方法后,内核才对所有监视的⽂文件描述符进⾏行扫描,

⽽而epoll事先通过epoll_ctl()来注册⼀一个⽂文件描述符,⼀一旦基于某个⽂文件描述符就绪时,内核会采⽤用类似callback的回调机制,迅速激活这个⽂文件描述符,

当进程调⽤用epoll_wait()时便得到通知。


Epoll的2种⼯工作⽅方式-⽔水平触发(LT)和边缘触发(ET):  

假如有这样一个例子:

1. 我们已经把一个用来从管道中读取数据的文件句柄(RFD)添加到epoll描述符

2. 这个时候从管道的另一端被写入了2KB的数据

3. 调用epoll_wait(2),并且它会返回RFD,说明它已经准备好读取操作

4. 然后我们读取了1KB的数据

5. 调用epoll_wait(2)......


Edge Triggered 工作模式:

如果我们在第1步将RFD添加到epoll描述符的时候使用了EPOLLET标志,那么在第5步调用epoll_wait(2)之后将有可能会挂

起,因为剩余的数据还存在于文件的输入缓冲区内,而且数据发出端还在等待一个针对已经发出数据的反馈信息。只有在监视

的文件句柄上发生了某个事件的时候 ET 工作模式才会汇报事件。因此在第5步的时候,调用者可能会放弃等待仍在存在于文

件输入缓冲区内的剩余数据。在上面的例子中,会有一个事件产生在RFD句柄上,因为在第2步执行了一个写操作,然后,

事件将会在第3步被销毁。因为第4步的读取操作没有读空文件输入缓冲区内的数据,因此我们在第5步调用 epoll_wait(2)完成

后,是否挂起是不确定的。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操

作把处理多个文件描述符的任务饿死。最好以下面的方式调用ET模式的epoll接口,在后面会介绍避免可能的缺陷。

   i    基于非阻塞文件句柄

   ii   只有当read(2)或者write(2)返回EAGAIN时才需要挂起,等待。但这并不是说每次read()时都需要循环读,直到读到产生一

个EAGAIN才认为此次事件处理完成,当read()返回的读到的数据长度小于请求的数据长度时,就可以确定此时缓冲中已没有

数据了,也就可以认为此事读事件已处理完成。


Level Triggered 工作模式

相反的,以LT方式调用epoll接口的时候,它就相当于一个速度比较快的poll(2),并且无论后面的数据是否被使用,因此他们具

有同样的职能。因为即使使用ET模式的epoll,在收到多个chunk的数据的时候仍然会产生多个事件。调用者可以设定

EPOLLONESHOT标志,在 epoll_wait(2)收到事件后epoll会与事件关联的文件句柄从epoll描述符中禁止掉。因此当 

EPOLLONESHOT设定后,使用带有 EPOLL_CTL_MOD标志的epoll_ctl(2)处理文件句柄就成为调用者必须作的事情。

注:ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。epoll工作在ET模式的时候,必须使用

非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。


LT(level triggered)是epoll缺省的⼯工作⽅方式,并且同时⽀支持block和no-block socket.在这种做法中,内核告诉你⼀一个⽂

文件描述符是否就绪了,然后你可以对这个就绪的fd进⾏行IO操作。如果你不作任何操作,内核还是会继续通知你 的,所以,

这种模式编程出错误可能性要⼩小一点。传统的select/poll都是这种模型的代表。


ET (edge-triggered)是⾼高速⼯工作⽅方式,只⽀支持no-block socket,它效率要⽐比LT更⾼高。ET与LT的区别在于,当一个

新的事件到来时,ET模式下当然可以从epoll_wait调⽤用中获取到这个事件,可是如果这次没有把这个事件对应的套接字缓冲

区处理完,在这个套接字中没有新的事件再次到来时,在ET模式下是⽆无法再次从epoll_wait调⽤用中获取这个事件的。⽽而

LT模式正好相反,只要⼀一个事件对应的套接字缓冲区还有数据,就总能从epoll_wait中获取这个事件。因此,LT模式下开发

基于epoll的应⽤用要简单些,不太容易出错。⽽而在ET模式下事件发⽣生时,如果没有彻底地将缓冲区数据处理完,则会导

致缓冲区中的⽤用户请求得不到响应。Nginx默认采⽤用ET模式来使⽤用epoll。



四、epoll实现服务器

#include<stdio.h>
#include<sys/epoll.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include<string.h>
static void usage(const char *proc)
{printf("usage:%s [local_ip] [local_port]",proc);
}typedef struct fd_buf
{int fd;char buf[10240];
}fd_buf_t,*fd_buf_p;static void *alloc_fd_buf(int fd)
{fd_buf_p tmp=(fd_buf_p)malloc(sizeof(fd_buf_t));if(!tmp){perror("malloc");return NULL;}tmp->fd=fd;return tmp;
}int startup(const char *_ip,const int _port)
{int sock=socket(AF_INET,SOCK_STREAM,0);if(sock<0){perror("socket");return 2;}struct sockaddr_in local;local.sin_family=AF_INET;local.sin_port=htons(_port);local .sin_addr.s_addr=inet_addr(_ip);if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0){perror("bind");return 3;}if(listen(sock,10)<0){perror("listen");return 4;}return sock;
}
int main(int argc,char *argv[])
{if(argc!=3){usage(argv[0]);return 1;}int listen_sock=startup(argv[1],atoi(argv[2]));int epfd=epoll_create(256);if(epfd<0){printf("epoll_create");close(listen_sock);return 5;}struct epoll_event ev;ev.events=EPOLLIN;ev.data.ptr=alloc_fd_buf(listen_sock);epoll_ctl(epfd,EPOLL_CTL_ADD,listen_sock,&ev);int num=0;struct epoll_event evs[64];int timeout=-1;while(1){switch((num=epoll_wait(epfd,evs,64,timeout))){//等待失败 case -1:perror("epoll_wait");break;//超时case 0:perror("timeout...");break;//等待成功default:{int i=0;for(;i<num;i++){fd_buf_p fp=(fd_buf_p)evs[i].data.ptr;if(fp->fd==listen_sock && \(evs[i].events & EPOLLIN)){struct sockaddr_in client;socklen_t len=sizeof(client);int new_sock=accept(listen_sock,\(struct sockaddr*)&client,&len);if(new_sock<0){perror("accept");continue;}printf("get a new client\n");ev.events=EPOLLIN;ev.data.ptr=alloc_fd_buf(new_sock);epoll_ctl(epfd,EPOLL_CTL_ADD,\new_sock,&ev);}else if(fp->fd!=listen_sock){//读事件if(evs[i].events & EPOLLIN){ssize_t s=read(fp->fd,fp->buf,sizeof(fp->buf));if(s>0){fp->buf[s]=0;printf("client say:%s\n",fp->buf);ev.events=EPOLLOUT;ev.data.ptr=fp;epoll_ctl(epfd,EPOLL_CTL_MOD,fp->fd,&ev);}else if(s<=0){close(fp->fd);epoll_ctl(epfd,EPOLL_CTL_DEL,fp->fd,NULL);free(fp);}else{}}//写事件else if(evs[i].events & EPOLLOUT){const char *msg="HTTP/1.0 200 OK\r\n\r\n<html><h1>hello epoll</h1></html>";write(fp->fd,msg,strlen(msg));close(fp->fd);epoll_ctl(epfd,EPOLL_CTL_DEL,fp->fd,NULL);free(fp);}else{}}else{}}}break;}}return 0;
}

五、epoll与select、poll比较


1 支持一个进程所能打开的最大连接数

 select                  单个进程所能打开的最大连接数有FD_SETSIZE宏定义,其大小是32个整数的大小(在32位的机器上,大小就是32*32,同理64位机器上 FD_SETSIZE为32*64),当然我们可以对它进行修改,然后重新编译内核,但是性能可能会受到影响,这需要进一步的测试。
 poll poll本质上和select没有区别,但是它没有最大连接数的限制,原因是它是基于链表来存储的
 

epoll

 虽然连接数有上限,但是很大,1G内存的机器上可以打开10万左右的连接,2G内存的机器可以打开20万左右的连接。

 

2 FD剧增后带来的IO效率问题

 select                   

 因为每次调用时都会对连接进行线性遍历,所以随着FD的增加会造成遍历速度慢的“线性下降性能问题”。
 poll 同上
 epoll 因为epoll内核中实现是根据每个fd上的callback函数来实现的,只有活跃的socket才会主动调用callback,所以在活跃socket较少的情况下,使用epoll没有前面两者的线性下降的性能问题,但是所有socket都很活跃的情况下,可能会有性能问题。

   

3 消息传递方式

 select 内核需要将消息传递到用户空间,都需要内核拷贝动作
 poll 同上
 epoll epoll通过内核和用户空间共享一块内存来实现的

   

综上,在选择select,poll,epoll时要根据具体的使用场合以及这三种方式的自身特点。表面上看epoll的性能最好,但是在连接数少并且连接都十

分活跃的情况下,select和poll的性能可能比epoll好,毕竟epoll的通知机制需要很多函数回调。



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

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

相关文章

ewebeditor未授权:功能被禁用请先配置授权_SteamPY新功能——外区账号礼物自动领取...

自从PY平台增加了外区代购后发现许多玩家在购买礼物时常会发生收到礼物后准备点击入库时弹出地区不可用的提示这个问题在Steam外区账号一直频繁发生究其因在于Steam账号登录时的IP问题遇到该问题切勿拒收礼物&#xff01;通过Steam客户端清理登录授权注销退出后再次使用账号对应…

Linux: shell 中命令代换 $() 和 ``(有图有代码有真相!!!)

一、命令代换&#xff08;命令替换&#xff09; 由 或 $() 括起来的也是一条命令&#xff0c;shell先执行该命令&#xff0c;再将结果立刻代换到当前命令行中。 简单例子&#xff1a; DATEdate echo $DATE DATE$(date) echo $DATE 执行结果&#xff1a; 二、优缺点&#x…

精雕道路怎么遍弧形_【养护技术】道路“创可贴”——沥青冷补料 六大优势助力道路养护...

点击上面蓝字关注我们微信号&#xff1a;xzgsgl随着城市精细化管理目标不断提高&#xff0c;市政道路养护修补的要求也越来越高。不但对修补的外观、质量有了更高的标准&#xff0c;对修复时限也提出了一定要求&#xff0c;这就要求我们的养护单位快速、优质地完成道路修补任务…

单耳蓝牙耳机怎么连接_蓝牙耳机怎么挑选?推荐性价比高的蓝牙耳机

随着手机逐渐取消了耳机孔&#xff0c;越来越多的人们开始使用上了蓝牙耳机。在当今这个飞速发展的时代&#xff0c;蓝牙耳机无疑成为了新时代的宠儿。无论是上班族还是当代大学生等年轻化群体&#xff0c;耳机的第一选择都是蓝牙耳机。但是面对市面上如此多的蓝牙耳机&#xf…

Linux: shell命令 eval (有图有代码有真相!!!)

一、eval 命令定义 shell中的eval命令将会首先扫描命令行进行所有的替换&#xff0c;然后再执行命令。该命令使用于那些一次扫描无法实现其功能的变量。 该命令对变量进行两次扫描。这些需要进行两次扫描的变量有时候被称为复杂变量。不过这些变量本身并不复杂。eval 命令也可…

Linux:shell脚本命令: /dev/null 21 的理解

1、可以将/dev/null看作"黑洞". 它非常等价于一个只写文件. 所有写入它的内容都会永远丢失. 而尝试从它那儿读取内容则什么也读不到. 然而, /dev/null对命令行和脚本都非常的有用. 禁止标准输出. 1 cat $filename >/dev/null # 文件内容丢失&#xff0c;而不…

qlabel可以选中吗_惊现凡尔赛式排版!原来微信公众号排版样式还可以“变装”?...

各位小伙伴们&#xff0c;要集中注意力了&#xff01;接下来就是考验你们观察力的时候啦&#xff01;快跟着小妹儿看一下&#xff0c;一个样式到底能有多少种玩法&#xff1f;文中使用工具为公众号编辑器-小蚂蚁编辑器。1、添加/删除背景编辑器里的内容样式是可以增加或者删除背…

LInux:shell 彩色进度条实现(有图有代码有真相!!!)

一、进度条原理&#xff08;以前的博客详细讲述过&#xff09;&#xff1a;http://blog.csdn.net/sharp_up/article/details/55506555 二、颜色设置 // 字体颜范围(前景颜色):30~39 30:黑 31:红 32:绿 33:黄 34:蓝色 35:紫色 36:深绿 37:白色 // 字背景颜色范围(背景颜…

vb语言中怎样编码窗体中所有字体加粗_VBText控件中使字体加粗和倾斜的代码是什么...

展开全部VBText控件中使字体加粗和倾e69da5e6ba9062616964757a686964616f31333365656537斜的代码是&#xff1a;加粗 Label1.FontBold True斜体 Label1.FontItalic true拓展资料&#xff1a;TextBox控件&#xff1a;a. 控制Textbox输入格式&#xff0c;我想大多人都遇到这个…

Linux: 系统配置 crond 和 crontab(有图有代码有真相!!!)

1、相关概述 linux下工作调度的种类有&#xff1a;at , cron 一种是例行性的&#xff0c;就是每隔一定的周期来办某事。 一种是突发性的&#xff0c;就是做完这一次没有以后。 crontab这个命令所设置的工作将会一直循环进行下去&#xff0c;循环的时间可以是分钟、小时、…

前端radio单选框默认选中_「radio选中」单选框radio总结(获取值、设置默认选中值、样式) - seo实验室...

radio选中单选框 radio是否1.获取值$("input[namekillOrder]:checked").val();$(input:radio:checked).val()&#xff1b;$("input[typeradio]:checked").val();$(":radio[checked]").each(function(radio){alert($(this).val());2.设置第一个Ra…

LInux:shell 命令:字符串截取

1、cut命令截取 使用说明 cut 命令从文件的每一行剪切字节、字符和字段并将这些字节、字符和字段写至标准输出。 如果不指定 File 参数&#xff0c;cut 命令将读取标准输入。必须指定 -b、-c 或 -f 标志之一。 主要参数 -b &#xff1a;以字节为单位进行分割。这些字节位置将忽…

cocostuff10k数据集介绍_(六)COCO数据集的简单介绍

COCO通过大量使用Amazon Mechanical Turk来收集数据。COCO数据集现在有3种标注类型&#xff1a;object instances(目标实例), object keypoints(目标上的关键点), 和image captions(看图说话)&#xff0c;使用JSON文件存储。比如下面就是Gemfield下载的COCO 2017年训练集中的标…

数据结构:单链表操作之如何判断链表是否带环及相关操作

//判断链表是否有环 int HasCircle(Node* pHead) { Node* lowpHead; Node* fastpHead; while(fast ! NULL && fast->next ! NULL) { lowlow->next; fastfast->next->next; if(lowfast) return 1; } return 0; } 时…

smart700iev3 程序下载设置_分享一款Aira2下载工具

Qdown&#xff0c;一款新的Aria2下载器&#xff0c;Aria2是一个命令行的下载器&#xff0c;非常强大&#xff0c;本软件套壳了Aria2&#xff0c;并且制作了界面版本&#xff0c;使用体验不错。Qdown是一款基于Aria2的Windows文件下载器&#xff0c;几乎支持现阶段所有的下载协议…

引用js_js值和引用

值和引用在许多编程语言中&#xff0c;赋值和参数传递可以通过值复制或者引用复制来完成&#xff0c;这取决于我们使用什么语法。例如&#xff0c;在 C 中如果要向函数传递一个数字并在函数中更改它的值&#xff0c;就可以这样来声明参 数 int& myNum&#xff0c;即如果传递…

]数据结构:单链表之判断两个链表是否相交及求交点(带环、不带环)

1、判断两个链表是否相交&#xff0c;若相交&#xff0c;求交点。&#xff08;假设链表不带环&#xff09; 两个指针同时指向两个链表&#xff0c;分别依次往后遍历链表到最后一个节点&#xff0c;如指针的值相同&#xff08;即节点地址相同&#xff09;&#xff0c;反之没有交…

某月某日前包括当天吗_10月26日,你真的理解了导数的定义吗?(答思考题送猫王小音箱)...

点击并关注上方“鸡汤斋”&#xff0c;与斋主一起成长特别说明&#xff1a;公众号的“一天一题”都是从历年期中、期末&#xff0c;以及各个高等学校或者国家统一的考研试题中抽取的题目进行的详细讲解。如果您每天在固定的时间(无聊时、吃饭时、睡觉前、早上起床前、“吃鸡”前…

数据结构:栈和列之如何用两个队列实现一个栈?两个栈实现一个队列?

1、栈和队列分析 栈是一种特殊的线性表。其特殊性在于限定插入和删除数据元素的操作只能在线性表的一端进行 队列(Queue)也是一种运算受限的线性表&#xff0c;它的运算限制与栈不同&#xff0c;是两头都有限制&#xff0c;插入只能在表的一端进行(只进不出)&#xff0c;而删 …

人脸识别进水_万维|人脸识别闸机怎么选?

如今人脸识别在安防行业的逐渐普及&#xff0c;现在已经有越来越多的场景都已应用人脸识别闸机&#xff0c;像办公大厦啊&#xff0c;景区门口&#xff0c;社区门口等&#xff0c;什么都不用带&#xff0c;只要刷个脸就行&#xff0c;方便快捷又省事。那么&#xff0c;你又对人…