1.select
在一段指定时间内,监听用户感兴趣文件描述符上的可读,可写和异常等事件。
函数原型:
int select(int nfds, struct fd_set *readfds, struct fd_set *writefds,
struct fd_set *execptfds, struct timeval * timeout);
nfds指定被监听的文件描述符总数,一般设置为select监听的所有文件描述符最大值加1;
select函数通过三个fd_set结构体变量readfds,writefds和execptfds,分别给内核传递用户关注的所有可读、可写、异常事件,
这使得select不能处理更多的事件类型,并且内核也通过这三个结构体变量返回就绪的文件描述符,所以每次使用之前,都必须重
新设置这三个结构体变量。fd_set结构体仅包含一个整型数组,数组的每个元素的每一位标记一个文件描述符。
timeout参数用来设置函数的超时时间。timeval结构体中long类型tv_sec成员(秒数)和tv_usec成员(微秒数),如果两者都
传递0,则select立即返回;如果传递NULL,则select将一直阻塞,直到某个文件描述符就绪。
select成功返回就绪文件描述符的总数。超时时间内没有任何文件描述符就绪,就返回0;失败返回-1并设置errno;接收到信号,
立即返回-1,并设置errno为EINTR。
2.poll系统调用
指定时间内轮询一定数量的文件描述符,以测试其中是否有就绪者。
函数原型:
int poll(struct pollfd * fds, nfds_t fds, int timeout);
fds参数是个pollfd结构类型的数组,指定所有感兴趣的文件描述符上发生的可读,可写和异常等事件。(poll事件类型如下图)
nfds是指定被监听事件集合fds的大小。
timeout参数指定poll的超时值。当timeout为-1时,poll调用永远阻塞,直到某个事件发生;当为0时,poll调用立即返回。
3.epoll系统调用
epoll使用一组函数来完成任务;其次,epoll把用户关心的文件描述符上的事件放在内核里的一个事件表中。但需要一个额外的文件描述符来标识内核中的事件表。
#include
int epoll_create(int size);
int epoll_ctl(int epollfd,int op,int fd,struct epoll_event* event);
int epoll_wait(int epollfd, struct epoll_event *events, int maxevents, int timeout);
3.1 内核事件表
int epoll_create(int size);
size参数是告诉内核事件表的大小。该函数返回的文件描述符作为epoll系统调用的第一个参数,用来指定要访问的内核事件表。
int epoll_ctl(int epollfd,int op,int fd,struct epoll_event* event);
此函数用来操作内核事件表。fd为要操作的文件描述符,op参数则指定操作类型。成功返回0;失败则返回-1并设置errno.
op操作类型如下:
EPOLL_CTL_DEL,删除fd上的注册事件
epoll_data_t是一个联合体,其中的fd是指定事件所从属的目标文件描述符。
3.2 epoll_wait函数:在一段超时时间内等待一组文件描述符上的事件。
如果监测到事件,就将所有就绪的事件从内核事件表(epfd指定)中复制到events指向的数组中。
int epoll_wait(int epollfd, struct epoll_event *events, int maxevents, int timeout);
该函数成功返回就绪的文件描述符个数;失败则返回-1并设置errno.
maxevents参数指定最多监听事件个数;
events用于输出epoll_wait检测到的就绪事件;
timeout参数指定poll的超时值。当timeout为-1时,poll调用永远阻塞,直到某个事件发生;当为0时,poll调用立即返回。
3.3 LT和ET模式
LT模式: 默认的工作模式,当epoll_wait检测到有事件发生并将此事件通知应用程序后,应用程序可以不立即处理该事件。
当下一次调用时,epoll_wait还会再次向应用程序通告此事件,直到事件被处理。
ET模式:当epoll_wait检测到有事件发生并将事件通知应用程序后,就立即处理该事件,因为后续的epoll_wait调用将不再
向应用程序通知这一事件。
ET模式在很大程度上降低了同一epoll事件被重复触发的次数,效率较高。
3.4 EPOLLONESHOT事件
对于注册了EPOLLONESHOT事件的文件描述符,操作系统最多触发其上注册的可读,可写或异常事件,且只触发一次,除非使用
epoll_ctl函数重置该文件描述符上注册的EPOLLONESHOT事件。注册了EPOLLONESHOT事件的socket一旦被某个线程处理完,
该线程就立即重置socket上的EPOLLONESHOT事件,以确保这个socket下次可读时,EPOLLIN事件能被触发。
1、使用方面:
select函数通过三个fd_set结构体变量分别给内核传递用户关注的所有可读、可写、异常事件,这使得select不能处理更多的事
件类型,并且内核也通过这三个结构体变量返回就绪的文件描述符,所以每次使用之前,都必须重新设置这三个结构体变量。
poll函数将用户关注的文件描述符以及其关注的事件、内核返回的文件描述符上发生的事件分离开表示,并且通过 一个用户数组
将所有的文件描述符传递给内核。因此,poll函数能关注的事件类型更多,每次调用也不需要重新设置。
epoll是通过一组函数来完成的,epoll通过epoll_create创建一个内核事件表,通过 epoll_ctl函数添加、删除、修改事件。
epoll_wait只需要从内核事件表中读取用户的注册的事件。
2、使用限制:
select所使用的fd_set结构实际上是一个整形数组,32bit系统上关注的文件描述符最多1024个,最大文件描述符数1023.
poll和epoll分别用nfds和maxevents参数指定最多监听多少个文件描述符,这两个数值都能达到系统允许打开的最大文件描述符数65535.
3、使用效率:
1、select,poll每次调用都需要将用户空间的文件描述符拷贝到内核空间,epoll则只需要拷贝一次。效率更高。
2、select、poll每次都将所有的文件描述符(就绪的和未就绪的)返回,所以应用程序检索就绪文件描述符的时间复杂度为O(n),
epoll通过events参数返回所有就绪的文件描述符,应用程序检索就绪文件描述符的时间复杂度为O(1)。
3、select、poll只能工作在效率较低的LT模式,而epoll则能工作在ET高效模式,并且epoll还支持EPOLLONESHOT事件,从而进一
步减少事件被触发的次数。
5、内核效率:
select和poll采用轮询的方式:即每次都需要扫描整个注册的文件描述符集合,并将其中就绪的文件描述符返回给用户程序,因此,
内核中检测就绪文件描述符的算法时间复杂度为O(n),
epoll则采取回调的方式,内核检测到就绪文件描述符,就触发回调函数,将文件描述符及发生的事件插入内核就绪事件队列,因此,
epoll在内核中检测就绪文件描述符的算法时间复杂度为O(1)。
但是,当链接的活动比较频繁是,select和poll的效率比epoll要高,因为epoll的回调函数调用过去频繁,所以,epoll适用于
链接较多,但是活动不频繁的情况。