目录
参考:https://www.2cto.com/kf/201611/561895.html
https://blog.csdn.net/davidsguo008/article/details/73556811
https://blog.csdn.net/zgege/article/details/81632990
https://www.liangzl.com/get-article-detail-26853.html
https://blog.csdn.net/qq546770908/article/details/53082870
https://www.cnblogs.com/aspirant/p/9166944.html
int poll (struct pollfd *fds, unsigned int nfds, int timeout);
select, poll, epoll都是I/O多路复用机制。
I/O多路复用可以让单个线程高效的处理多个连接请求。
I/O多路复用通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪), 能够通知程序进行相应的写操作。
select
select()函数监视的文件描述符分3类:writefds,readfds, exceptfds。
调用后,select()函数会阻塞,直到有描述符就绪(有数据 可读、可写、或者有except),或者超时(timeout指定等待时间,如果立即返回设为null即可)函数才返回。
当select()函数返回后,可以通过遍历fdset,来找到就绪的描述符。
select目前几乎在所有平台支持,良好的跨平台支持。
缺点
1.单个进程能够监视的文件描述符数量存在最大限制,通常是1024,当然可以更改数量。
// 查看文件描述符信息
ulimit -a
// 临时修改文件描述符数量(只对当前会话有效,并不是永久修改)
ulimit -n 数量
// 永久修改文件描述符数量
// 在/etc/security/limits.conf下的end of file前添加以下信息
* soft nofile 65536
* hard nofile 65536
由于select采用轮询方式扫描文件描述符,文件描述符数量越多,性能越差。(linux内核头文件中,有定义:#define __FD_SETSIZE 1024)
2.内核/用户空间内存拷贝问题,select需要复制大量的句柄数据结构,产生巨大开销。
3.select返回的是含有整个句柄的数组,应用程序需要遍历整个数组才能发现哪些句柄发生了事件
4.select出发方式是水平触发,应用程序如果没有完成对一个已经就绪的文件描述符进行I/O操作,那么之后每次select调用还会将这些文件描述符通知进程。
poll (todo)
int poll (struct pollfd *fds, unsigned int nfds, int timeout);
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events to watch */
short revents; /* returned events witnessed */
};
poll使用pollfd指针来实现文件描述符
epoll
是2.6内核中提出的,是select和poll的增强版。
epoll灵活,没有描述符限制,使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在内核空间和用户空间只需要copy一次。
int epoll_create(int size);//创建一个epoll的句柄,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);
int epoll_create(int size):简历一个epoll对象,在epoll文件系统中为这个句柄对象分配资源.size告诉内核监听数目的大小。
epoll创建好后会占用一个fd值,在使用完epoll后,必须close(),否则可能导致fd耗尽。
epoll_ctl(int epfd, int op, int fd, struct epoll_event *event):对指定的描述符fd进行op操作
epfd:epoll_create()返回值
op:添加(EPOLL_CTL_ADD), 删除(EPOLL_CTL_DEL), 修改(EPOLL_CTL_MOD)
epoll_event
告诉内核需要监听的事件。
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队列里
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
等待epfd上的io事件,最多返回maxevents个事件。
events用来从内核中得到事件的集合,maxevents告诉内核events由多大,maxevents的值不能大于epoll_create()的size。timeout是超时时间。
epollpoll
struct eventpoll{
....
/*红黑树的根节点,这颗树中存储着所有添加到epoll中的需要监控的事件*/
struct rb_root rbr;
/*双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件*/
struct list_head rdlist;
....
};
每个epoll对象 对象都有一个独立的eventpoll结构体,用来存放epoll_ctl()向epoll对象中添加进来的事件。这些事件都会挂载到红黑树中。
epitem
对于每一个事件,都会建立一个epitem结构体
struct epitem{
struct rb_node rbn;//红黑树节点
struct list_head rdllink;//双向链表节点
struct epoll_filefd ffd; //事件句柄信息
struct eventpoll *ep; //指向其所属的eventpoll对象
struct epoll_event event; //期待发生的事件类型
}
当调用epoll_wait()检查是否有事件发生时,只需要检查eventpoll对象中的rdlist双链表是否有epitem元素即可。如果rdlist不为空,则把发生的事件复制到用户态,同时将事件数量返回给用户。
所有添加到epoll中的事件都会与设备(网卡)驱动程序建立回调关系,也就是说相应的事件发生时会调用整个回调方法:ep_poll_callback(),会将发生的事件添加到rdlist双链表中。
通过红黑树和双链表数据结构,并结合回调机制,造就了epoll的高效。
epoll工作模式
水平触发(LT: level trigger)
是默认模式。同时支持阻塞和非阻塞socket。当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不用立即处理该事件。
当下次调用epoll_wait(),会再次响应应用程序并通知此事件
边缘触发(ET:edge trigger)
只支持非阻塞socket。当epoll_wait()检测到描述符事件发生并将该事件通知应用程序时,应用程序必须立即处理该事件,如果不处理,下次调用epoll_wait(),不会再次响应应用程序通知该事件。
总结
ET模式很大程度上减少了epoll事件被重复触发的次数,效率比LT高。
在ET模式时,必须使用非阻塞socket,以避免由于一个文件句柄的阻塞读写操作把处理多个文件描述符的任务饿死。
总结
1.select和epoll的动作基本一致,只是poll采用链表来进行文件描述符的存储,而select采用fd标志位来存放,所以select会收到最大连接数的限制。
2.select, poll, epoll都会返回文件描述符的数量,但select和poll不会明确指出哪些文件描述符就绪,而epoll会。
这就导致了系统调用返回后,调用select和poll的程序需要遍历监听的整个文件米松护肤找到是谁处于就绪,而epoll则直接处理。
3.select, poll都需要将有关文件描述符的数据结构拷贝进内核,然后再拷贝出来。
而epoll创建的有关文件描述符的数据结构本身就存在于内核中,系统调用返回时,采用mmap共享存储区,需要拷贝的次数减小。
4.select, poll都采用轮询的方式检查文件描述符是否处于就绪状态,而epoll采用回调机制。
这就导致了随着fd的增加,select和poll的效率会降低,而epoll不会受到影响,除非活跃的socket很多。
减少了用户态和内核态之间的文件描述符的拷贝
减少了对就绪文件描述符的遍历。