• select、poll、epoll的一些知识


    I/O复用使的程序能同时监听多个文件描述符,这对提高程序的性能至关重要。通常网络程序在下列情况下需要使用I/O复用技术:

    (1)客户端程序要同时处理多个socket

    (2)客户端程序要同时处理用户输入和网络连接

    (3)TCP服务器要同时处理监听socket和连接socket(这时I/O复用使用最多的场合)

    (4)服务器要同时处理TCP请求和UDP请求

    (5)服务器要同时监听多个端口,或者处理多种服务。

     需要指出的是,I/O复用虽然能同时监听多个文件描述符,但是它本身是阻塞的。并且当多个文件描述符就绪时,如果不采取额外的措施,程序就只能按顺序依次处理其中的每一个文件描述符,这使得服务器程序看起来像是串行工作。如果要实时并发,只能使用多进程和多线程等手段。

    2、select函数 

    1 #include <sys/select.h>  

    2 int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)   

    返回值:select成功时返回就绪文件描述符的总数,超时没有人任何文件描述符准备就绪,返回0.;出错返回-1

    函数参数介绍:

    (1)nfds参数指定被监听的文件描述符的总数。它通常被设置为监听的所有文件描述符加一,因为文件描述符是从0开始计数的。

    (2)Readfds、writefds、exceptfds参数分别指向可读、可写和异常等事件对应的文件描述符,应用程序调用select函数时,通过这三个参数传入自己感兴趣的文件描述符。Select返回时,内核将修改他们来通知应用程序那些文件描述符已经就绪。这三个参数都是fd_set结构体指针类型。Fd_set结构体仅包含一个整形数组,该数组的每一个元素的每一位标记一个文件描述符,fd_set所能容纳的文件描述符有FD_SETSIZE指定,这就限制了select能同时处理的文件描述符的总量。可以通过下面的宏来访问fd_set结构体中的位

    1 #include <sys/select.h>  
    2 FD_ZERO(fd_set *fdset)              //清除fdset的所有位  
    3 FD_SET(int fd,  fd_set *fd_set)        //设置fdset的位fd  
    4 FD_CLR(int fd,  fd_set *fd_set)         //清除fdset的位fd  

    5 FD_ISSET(int fd,  fd_set *fd_set)     //测试fdset的位fd是否被设置   

     (3)timeout用来设置select函数的超时时

    1 Struct timeval  
    2 {  
    3    Long tv_sec;         //秒数  
    4    Long tv_usev;        //微妙数   

    5 };   

    这个参数有三种可能:当把参数设置为NULL,则select将一直阻塞,直到某个文件描述符准备就绪;设置为一个固定的时间,在有一个描述符准备好返回,但是不超过指定的时间;设置为0时,select则立即返回。

    2、poll函数

    poll系统调用的select类似,也是在指定时间内轮询一定数量的文件描述符,以测试其是否就绪

    1 #include <poll.h>  
    2 Int poll(struct pollfd *fds, nfds_t nfds, int timrout);  

    (1)fds参数是一个pollds类型的结构体数组,它指定在我们感兴趣的文件描述符上发生可读、可写和异常事件。Pollfd结构体的定义

    1 Struct pollfd  
    2 {  
    3      Int fd;                                      //文件描述符  
    4       Short events;                        //注册的事件  
    5       Short revents;                      //实际发生的事件,有内核完成  

    6 } ;   

    每个结构体的events域是监控该文件描述符的事件掩码,由用户来设置这个域,revents域是文件描述符的操作结果事件掩码,内核在调用返回时设置这个域。events域中请求的任何事件都可能在revents域中返回。合法的事件如下:

     1 POLLIN         有数据可读。  
     2 POLLRDNORM        有普通数据可读。  
     3 POLLRDBAND       有优先数据可读。  
     4 POLLPRI         有紧迫数据可读。  
     5 POLLOUT            写数据不会导致阻塞。  
     6 POLLWRNORM        写普通数据不会导致阻塞。  
     7 POLLWRBAND        写优先数据不会导致阻塞。  
     8 POLLMSGSIGPOLL     消息可用。  
     9 POLLER               指定的文件描述符发生错误。  
    10 POLLHUP             指定的文件描述符挂起事件。  

    (2)nfds参数指定被监听事件集合fds的大小

    (3)Timeout参数指定poll的超时值。当timeout为-1时,poll调用将永远阻塞,直到某个事件发生;当timeout为0时,poll将立即返回。

    3、epoll函数

    Epoll是Linux特有的I/O复用函数。它在实际和使用上和select、poll有很大的差异。首先,epoll使用一组函数来完成任务,而不是单个函数。其次,epoll把用户关心的文件描述符上的事件放在内核里的一个事件表中,从而无需想select和poll那样每次调用都要重复传入文件描述符集。但是epoll需要使用一个额外的文件描述符,来唯一标识内核中的这个事件表。

    (1)这个文件描述符使用epoll_create函数来创建:

    1 #include <sys/epoll.h>  
    2 Int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);  

    该函数返回成功就绪文件描述符的个数,失败是返回-1并设置errno。

     类似于select调用。参数events用来从内核得到事件的集合,maxevents的值不能大于创建epoll_create时的size,参数timeout是超时时间

    5、LT和ET模式

    LT模式(水平触发):当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序可以不立即处理该事件。这样,当应用程序下一次调用epoll_wait时,epoll_wait还会再次向应用程序通告次事件。

    ET模式(边沿触发):当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序必须立即处理该事件,因为后续的epoll_wait调用将不再向应用程序通知这一事件。Epoll工作在ET模式的时候,必须使用非阻塞套接字,以避免由于一个文件句柄阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。

    可见,ET模式在很大程度上降低了同一个epoll事件被重复触发的次数,因此效率要比LT模式高。

    6、三组I/O复用函数的比较

    (1)事件集:

    Select:Select的参数类型fd_set没有将文件描述符和事件进行绑定,它仅仅是一个文件描述符的集合,因此select的参数需要提供3个这种类型的参数来分别传入和输出可读、可写及异常事件。这一方面使得select不能处理更多类型的事件,另一方面由于内核对fd_set集合的在线修改,应用程序下次调用select前不得不重置3个fd_set集合。

    Poll:poll的参数pollfd,它把文件描述符和事件都定义在其中,任何事件都被统一处理,从而使编程接口简单的多,并且内核每次修改的是pollfd结构体的revents成员,而events 成员保持不变,因此下次调用poll时应用程序无需重置pollfd类型的事件集合。

    Epoll:epoll采用完全不同与select和poll的方式来管理用户注册的事件。它在内核中维护一张事件表,并提供一个独立的系统调用epoll_ctl来控制往其中添加、删除、修改事件。这样,每次epoll_wait调用都直接从内核事件表中取得用户注册的事件,而无需反复从用户空间读入这些事件。

    (2)最大支持文件描述符数:

    Select:select打开的文件描述符fd是由一定限制的,有 fd_size设置,默认为2048,对于那些需要支持上万连接数目的服务器来说显然太少了。

    Poll和epoll:poll和epoll分别用nfds和maxevents参数指定最多监听多少个文件描述符和事件,这两个值都能达到系统允许打开的最大文件描述符数目,即65535。

    (3)工作模式:

    Select和poll都只能工作在相对低效的LT模式,而epoll则可以工作在ET高效模式。

    (4)实现原理:

    Select和poll采用的都是轮询的方式,即每次调用都要扫描整个注册文件描述符集合,并将其中就绪的文件描述符返回给用户,因此他们检测就绪事件的算法时间复杂度是O(n)。

    Epoll的epoll_wait则不同,它采用的是回调函数的方式。内核检测到就绪的文件描述符时,将触发回调函数,回调函数就将该文件描述符上对应的时间插入内核就绪事件队列。内核最后在适当的时候将就绪事件队列中的内容拷贝到用户空间。因此epoll_wait无需轮询整个文件描述符集合来检测那些事件已经准备就绪,其算法事件复杂度O(1)。但是,当活动链接比较多时,epoll_wait的效率未必比select和poll高,因为回调函数被触发得过于频繁。所以epoll_wait适用于连接数量多,但是活动连接少的情况。

    (5)使用mmap加速内核与用户空间的消息传递。

    这点实际上涉及到epoll的具体实现了。无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就很重要,在这点上,epoll是通过内核于用户空间mmap同一块内存实现的。

  • 相关阅读:
    在网易和百度实习之后,我才明白了这些事
    从Java小白到收获BAT等offer,分享我这两年的经验和感悟
    曾经做的一个JS小游戏——《Battle City》
    适配器(Adapter)模式
    装饰器(Decorator)模式
    Java IO
    JDBC中驱动加载的过程分析
    从PipedInputStream/PipedOutputStream谈起
    从InputStream到ByteArrayInputStream
    JDK中的动态代理
  • 原文地址:https://www.cnblogs.com/ancient/p/3629171.html
Copyright © 2020-2023  润新知