I/O多路复用——epoll函数

select、poll、epoll区别总结

一、select、poll、epoll区别总结

 

1 本质上都是同步I/O

  三者都是I/O复用,本质上都属于同步I/O。因为三者只是负责通知应用程序什么时候数据准备好了,实际的I/O操作还是在由应用程序处理;如果是异步I/O的话,实际I/O由内核处理,然后再通知应用程序。这一点要搞清楚。

  select、poll函数介绍:http://www.cnblogs.com/qiaoconglovelife/p/5488871.html

  epoll函数介绍:http://www.cnblogs.com/qiaoconglovelife/p/5503473.html

2 相比select、poll,epoll的缺点:

  (1)Linux系统独有:epoll函数并不是Unix系统通用,所以不适合开发兼容性强的程序;

  (2)select、poll都只有一个函数,而epoll有三个(epoll_create,epoll_ctl和epoll_wait),操作起来更复杂,并且由于要实现回调机制,epoll的内部实现也更加复杂。如果并发量小且连接不频繁的话,最好使用select和poll,性能可能更好。

3 相比select、poll,epoll的优点  

  (1)每次调用select、poll,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大,而epoll函数只有使用epoll_ctl函数时才会进行fd的拷备,并且只拷备增加的fd;

  (2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大,而epoll函数只传递所有新注册事件的fd;

  (3)select支持的文件描述符数量太小了,默认是1024,而epoll函数所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048。

二、I/O多路复用——epoll函数

 

1 select的低效率

  select/poll函数效率比较低,主要有以下两个原因:

  (1)调用select函数后需要对所有文件描述符进行循环查找

  (2)每次调用select函数时都需要向该函数传递监视对象信息

  在这两个原因中,第二个原因是主要原因:每次调用select函数时,应用程序都要将所有文件描述符传递给操作系统,这给程序带来很大的负担。在高并发的环境下,无论怎样优化应用程序的代码,都无法完成应用的服务。  

  所以,select与poll并不适合以Web服务器端开发为主流的现代开发环境,只在要求满足以下两个条件是适用:

  (1)服务器端接入者少

  (2)程序要求兼容性

2 Linux的epoll机制

  由上一节,我们需要一种类似于select的机制来完成高并发的服务器。需要有以下两个特点(epoll和select的区别)

  (1)应用程序仅向操作系统传递1次监视对象

  (2)监视范围或内容发生变化是,操作系统只通知发生变化的事项给应用程序

  幸运的是,的确存在这样的机制。Linux的支持方式是epoll,Windows的支持方式是IOCP。

3 epoll函数原型  

  epoll操作由三个函数组成:  

#include <sys/epoll.h>
int epoll_create(int size);
            //成功时返回epoll文件描述符,失败时返回-1
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
            //成功时返回0,失败时返回-1
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
            //成功时返回发生事件的文件描述数,失败时返回-1

  (1)epoll_create:创建保存epoll文件描述符的空间。

  调用epoll_create函数时创建的文件描述符保存空间称为“epoll例程”。但要注意:size参数只是应用程序向操作系统提的建议,操作系统并不一定会生成一个大小为size的epoll例程。

  (2)epoll_ctl:向空间注册并注销文件描述符。

  参数epfd指定注册监视对象的epoll例程的文件描述符,op指定监视对象的添加、删除或更改等操作,有以下两种常量:

    1)EPOLL_CTL_ADD:将文件描述符注册到epoll例程

    2)EPOLL_CTL_DEL:从epoll例程中删除文件描述符

    3)EPOLL_CTL_MOD:更改注册的文件描述符的关注事件发生情况

  fd指定需要注册的监视对象文件描述符,event指定监视对象的事件类型。epoll_event结构体如下:  

struct epoll_event
{
__uint32_t events;
epoll_data_t data;
}
typedef union epoll_data
{
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
}epoll_data_t;

  epoll_event的成员events中可以保存的常量及所指的事件类型有以下:

    1)EPOLLIN:需要读取数据的情况

    2) EPOLLOUT:输出缓冲为空,可以立即发送数据的情况  

    3) EPOLLPRI:收到OOBO数据的情况

    4) EPOLLRDHUP:断开连接或半关闭的情况,这在边缘触发方式下非常有用

    5) EPOLLERR:发生错误的情况

    6) EPOLLET:以边缘触发的方式得到事件通知

    7) EPOLLONESHOT:发生一次事件后,相应文件描述符不再收到事件通知。因此需要向epoll_ctl函数的第二个参数EPOLL_CTL_MOD,再次设置事件。

  (3)epoll_wait:与select函数类似,等待文件描述符发生变化。操作系统返回epoll_event类型的结构体通知监视对象的变化。timeout函数是为毫秒为单位的等待时间,传递-1时,一直等待直到事件发生。声明足够大的epoll_event结构体数组后,传递给epoll_wait函数时,发生变化的文件符信息将被填入该数组。因此,不需要像select函数那样针对所有文件符进行循环。

4 基于epoll的echo服务器代码:

#define BUF_SIZE 1024
#define EPOLL_SIZE 50
void error_handling(char *buf); int main(int argc, char *argv[])
{
int listenfd, connfd;
struct sockaddr_in serv_addr;
socklen_t socklen;
char buf[BUF_SIZE]; int epfd, event_cnt;
struct epoll_event *ep_events;
struct epoll_event event; if (argc != 2)
{
printf("Usage: echo <port>\n");
exit(1);
} listenfd = socket(PF_INET, SOCK_STREAM, 0);
memset(&serv_addr, 0, sizeof(serv_addr);
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(atoi(argv[1])); if (bind(listenfd, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1)
error_handling("bind() error\n");
if (listen(serv_addr, 5) == -1)
error_handling("listen() error\n"); epfd = epoll_create(EPOLL_SIZE);
ep_events = malloc(sizeof(epoll_event)*EPOLL_SIZE); event.event = EPOLLIN;
event.data.fd = listenfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &event); for (;;)
{
event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
if (event_cnt == -1)
error_handling("epoll_wait() error\n");
for (int i = 0; i < event_cnt; ++i)
{
if (ep_events[i].data.fd == listenfd)
{
connfd = accept(listenfd, NULL, NULL);
event.events = EPOLLIN;
event.data.fd = connfd;
epoll_ctl(pefd, EPOLL_CTL_ADD, connfd, &event);
printf("connect another client\n");
}
else
{
int nread = read(ep_events[i].dada.fd, buf, BUF_SIZE);
if (nread == 0)
{
close(ep_events.data.fd);
epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events.data.fd, NULL);
printf("disconnect with a client\n");
}
else
{
write(ep_events[i].data.fd, buf, nread);
}
}
}
}
close(listenfd);
close(epfd);
return 0;
} void error_handling(char* buf)
{
printf("%s\n", buf);
exit(1);
}

5 条件触发与边缘触发

  条件触发:只要引起epoll_wait返回的事件还存在,再次调用epoll_wait时,该事件还会被注册

  边缘触发:每个事件在刚发生的时候被注册一次,之后就不会被注册,除非又有新的事件发生。

  比如,一个已连接的socket套接字收到了数据,而读取缓冲区小于接收到的数据,这时,两种触发方式有以下区别:(1)条件触发:一次读取之后,套接字缓冲区里还有数据,再调用epoll_wait,该套接字的EPOLL_IN事件还是会被注册;(2)边缘触发:一次读取之后,套接字缓冲区里还有数据,再调用epoll_wait,该套接字的EPOLL_IN事件不会被注册,除非在这期间,该套接字收到了新的数据。

  epoll默认采用条件触发,上一节的代码采用的就是条件触发。

  还是不太清楚?用代码来砸!边缘触发实现echo服务器:  

//设置较小的读取缓冲区,以测试边缘触发特性
#define BUF_SIZE 4
#define EPOLL_SIZE 50
void error_handling(char *buf); int main(int argc, char *argv[])
{
int listenfd, connfd;
struct sockaddr_in serv_addr;
socklen_t socklen;
char buf[BUF_SIZE]; int epfd, event_cnt;
struct epoll_event *ep_events;
struct epoll_event event; if (argc != 2)
{
printf("Usage: echo <port>\n");
exit(1);
} listenfd = socket(PF_INET, SOCK_STREAM, 0);
memset(&serv_addr, 0, sizeof(serv_addr);
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(atoi(argv[1])); if (bind(listenfd, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1)
error_handling("bind() error\n");
if (listen(serv_addr, 5) == -1)
error_handling("listen() error\n"); epfd = epoll_create(EPOLL_SIZE);
ep_events = malloc(sizeof(epoll_event)*EPOLL_SIZE); event.event = EPOLLIN;
event.data.fd = listenfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &event); for (;;)
{
event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
if (event_cnt == -1)
error_handling("epoll_wait() error\n");
printf("event_cnt() return\n"); //指示一次返回
for (int i = 0; i < event_cnt; ++i)
{
if (ep_events[i].data.fd == listenfd)
{
connfd = accept(listenfd, NULL, NULL);
//设置为非阻塞I/O
int flag = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flag | O_NONBLOCK); event.events = EPOLLIN|EPOLLET; //边缘触发
event.data.fd = connfd;
epoll_ctl(pefd, EPOLL_CTL_ADD, connfd, &event);
printf("connect another client\n");
}
else
{
//读完每个已连接socket的缓冲区里的数据
while (1)
{
int nread = read(ep_events[i].dada.fd, buf, BUF_SIZE);
if (nread == 0)
{
close(ep_events.data.fd);
epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events.data.fd, NULL);
printf("disconnect with a client\n");
}
else if (nread < 0)
{
//errno为EAGAIN,则缓冲区内已没有数据
if (errno == EAGAIN)
break;
}
else
{
write(ep_events[i].data.fd, buf, nread);
}
} }
}
}
close(listenfd);
close(epfd);
return 0;
} void error_handling(char* buf)
{
printf("%s\n", buf);
exit(1);
}

  几个说明:

  (1)在使用epoll_ctl注册事件的时候,选择边缘触发,|EPOLLET

  (2)处理已发生的边缘触发的事件时,要处理完所有的数据再返回。例中,使用了循环的方式读取了套接字中的所有数据

  (3)读/写套接字的时候采用非阻塞式I/O。为何?边缘触发方式下,以阻塞方式工作的read&write函数有可能引起服务器端的长时间停顿。

  那么边缘触发好不好?有什么优点呢?书上说,边缘触发可以分离接收数据和处理数据的时间点。也就是说,在事件发生的时候,我们只记录事件已经发生,而不去处理数据,等到以后的某段时间才去处理数据,即分离接收数据和处理数据的时间点。好奇的我一定会问:条件触发没办法分离接收数据和处理数据的时间点吗?答案是可以的。但存在问题:在数据被处理之前,每次调用epoll_wait都会产生相应的事件,在一个具有大量这样的事件的繁忙服务器上,这是不现实的。

  可是。还没有说边缘触发和条件触发哪个更好呀?马克思说,要辩证地看问题。so,边缘触发更有可能带来高性能,但不能简单地认为“只要使用边缘触发就一定能提高速度”,要具体问题具体分析。好吧,马克思的这一个“具体问题具体分析”适用于回答绝大部分比较类问题,已和“多喝水”,“重启一下试试看”,“不行就分”并列成为最简单粗暴的4个通用回答。

转载-select、poll、epoll区别总结的更多相关文章

  1. select,poll.epoll区别于联系

    select,poll,epoll都是IO多路复用中的模型.再介绍他们特点时,先来看看多路复用的 模型. 同其他IO的不同的是,IO多路复用一次可以等多个文件描述符.大大提高了等待数据准备好的时间的效 ...

  2. select,poll,epoll区别

    select:忙轮询,一直在轮询,效率跟链接数成反比,资源限制 poll:轮询,不用一直轮询,有事件触发时轮询,资源限制 epoll:有事件触发时直接通知复杂度O(1)

  3. 哪5种IO模型?什么是select/poll/epoll?同步异步阻塞非阻塞有啥区别?全在这讲明白了!

    系统中有哪5种IO模型?什么是 select/poll/epoll?同步异步阻塞非阻塞有啥区别? 本文地址http://yangjianyong.cn/?p=84转载无需经过作者本人授权 先解开第一个 ...

  4. select.poll,epoll的区别与应用

    先讲讲同步I/O的五大模型 阻塞式I/O, 非阻塞式I/O, I/O复用,信号驱动I/O(SIGIO),异步I/O模型 而select/poll/epoll属于I/O复用模型 select函数 该函数 ...

  5. Python之路-python(Queue队列、进程、Gevent协程、Select\Poll\Epoll异步IO与事件驱动)

    一.进程: 1.语法 2.进程间通讯 3.进程池 二.Gevent协程 三.Select\Poll\Epoll异步IO与事件驱动 一.进程: 1.语法 简单的启动线程语法 def run(name): ...

  6. select,poll,epoll的归纳总结区分

    Select.Poll与Epoll比较 以下资料都是来自网上搜集整理.引用源详见文章末尾. 1 Select.Poll与Epoll简介 Select select本质上是通过设置或者检查存放fd标志位 ...

  7. select, poll, epoll的实现分析

    select, poll, epoll都是Linux上的IO多路复用机制.知其然知其所以然,为了更好地理解其底层实现,这几天我阅读了这三个系统调用的源码. 以下源代码摘自Linux4.4.0内核. 预 ...

  8. 转--select/poll/epoll到底是什么一回事

    面试题:说说select/poll/epoll的区别. 这是面试后台开发时的高频面试题,属于网络编程和IO那一块的知识.Android里面的Handler消息处理机制的底层实现就用到了epoll. 为 ...

  9. IO多路复用select/poll/epoll详解以及在Python中的应用

    IO multiplexing(IO多路复用) IO多路复用,有些地方称之为event driven IO(事件驱动IO). 它的好处在于单个进程可以处理多个网络IO请求.select/epoll这两 ...

  10. python 套接字之select poll epoll

    python下的select模块使用 以及epoll与select.poll的区别 先说epoll与select.poll的区别(总结) select, poll, epoll 都是I/O多路复用的具 ...

随机推荐

  1. swoole 消息队列

    <?php /** * 场景: * 监控订单表状态 队列通信 * 一个进程向队列发布消息 另外两个进程争抢 */ //设置主进程名 echo '主进程id:' . posix_getpid() ...

  2. Python学习笔记003

    windows环境配置 系统变量: Path: D:\Program Files\Python35\Scripts\; D:\ProgramFiles\Python35\; D:\Program Fi ...

  3. 导入spark程序的maven依赖包时,无法导入,报错Unable to import maven project: See logs for details

    问题:导入spark程序的maven依赖包时,无法导入,且报错:0:23 Unable to import maven project: See logs for details 2019-08-23 ...

  4. 搭建私有CA并实现证书颁发

    一.搭建私有CA服务器 1.安装包 # yum -y install openssl 2.生成密钥对儿 # cd /etc/pki/CA # (umask 077;openssl genrsa -ou ...

  5. C++的注册和回调

    注册回调的作用 在设计模式中注册回调的方式叫做回调模式.在SDK开发中,为增强开发者的SDK通用性,排序或者一些算法逻辑需要使用者进行编写.这时候就需要向SDK传递回调函数.注册回调能使下层主动与上层 ...

  6. Gamma 矫正

    参考如下链接: https://www.zhihu.com/question/27467127

  7. ABC155E - Payment

    简述题意,给你一个大数,你可以选择10的次幂进行加减运算,问如何用最少的次数从0到达这个大数 考虑从这个大数到0,从最低位开始,每次都将这个位置取完,2种策略,贪心的话不好处理进位的情况,可以想到是D ...

  8. Python简单程序爬取天气信息,定时发邮件给朋友【高薪必学】

    前段时间看到了这个博客.https://blog.csdn.net/weixin_45081575/article/details/102886718.他用了request模块,这不巧了么,正好我刚用 ...

  9. 一文解读XaaS (转)

    艾克赛斯???别慌,读完你就知道啦~ 服务和云服务 了解Xaas云服务,不妨从了解服务开始. “服务”在本质上是一种租赁,它对资源的占用方式是“为我所用”而非“为我所有”,对资源的消费模式是按需付费而 ...

  10. 多年珍藏的55w御剑字典

    御剑珍藏55w目录字典,很给力,放在以前直接数据库都能给跑出来. 用法:直接把放入配置文件的目录 链接:https://pan.baidu.com/s/1MGxdd9hH006Y7AO7CpkO8g ...