zoukankan      html  css  js  c++  java
  • Select、Poll与Epoll比較

    (1)select
    select最早于1983年出如今4.2BSD中,它通过一个select()系统调用来监视多个文件描写叙述符的数组。当select()返回后,该数组中就绪的文件描写叙述符便会被内核改动标志位。使得进程能够获得这些文件描写叙述符从而进行兴许的读写操作。
    select眼下差点儿在全部的平台上支持,其良好跨平台支持也是它的一个长处,其实从如今看来。这也是它所剩不多的长处之中的一个。
    select的一个缺点在于单个进程能够监视的文件描写叙述符的数量存在最大限制,在Linux上一般为1024,只是能够通过改动宏定义甚至又一次编译内核的方式提升这一限制。另外。select()所维护的存储大量文件描写叙述符的数据结构,随着文件描写叙述符数量的增大,其复制的开销也线性增长。同一时候。由于网络响应时间的延迟使得大量TCP连接处于非活跃状态,但调用select()会对全部socket进行一次线性扫描,所以这也浪费了一定的开销。
    (2)poll
    poll在1986年诞生于System V Release 3,它和select在本质上没有多大区别,可是poll没有最大文件描写叙述符数量的限制。
    poll和select相同存在一个缺点就是,包括大量文件描写叙述符的数组被总体复制于用户态和内核的地址空间之间,而不论这些文件描写叙述符是否就绪,它的开销随着文件描写叙述符数量的添加而线性增大。另外,select()和poll()将就绪的文件描写叙述符告诉进程后,假设进程没有对其进行IO操作,那么下次调用select()和poll()的时候将再次报告这些文件描写叙述符。所以它们一般不会丢失就绪的消息,这样的方式称为水平触发(Level Triggered)。(3)epoll
    直到Linux2.6才出现了由内核直接支持的实现方法,那就是epoll。它差点儿具备了之前所说的一切长处,被公觉得Linux2.6下性能最好的多路I/O就绪通知方法。
    epoll能够同一时候支持水平触发和边缘触发(Edge Triggered。仅仅告诉进程哪些文件描写叙述符刚刚变为就绪状态,它仅仅说一遍。假设我们没有採取行动,那么它将不会再次告知,这样的方式称为边缘触发)。理论上边缘触发的性能要更高一些,可是代码实现相当复杂。
    epoll相同仅仅告知那些就绪的文件描写叙述符。并且当我们调用epoll_wait()获得就绪文件描写叙述符时,返回的不是实际的描写叙述符。而是一个代表就绪描写叙述符数量的值,你仅仅须要去epoll指定的一个数组中依次取得相应数量的文件描写叙述符就可以。这里也使用了内存映射(mmap)技术,这样便彻底省掉了这些文件描写叙述符在系统调用时复制的开销。
    另一个本质的改进在于epoll採用基于事件的就绪通知方式。

    在select/poll中,进程仅仅有在调用一定的方法后。内核才对全部监视的文件描写叙述符进行扫描,而epoll事先通过epoll_ctl()来注冊一个文件描写叙述符,一旦基于某个文件描写叙述符就绪时,内核会採用相似callback的回调机制,迅速激活这个文件描写叙述符,当进程调用epoll_wait()时便得到通知。


    Select

    select本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理。这样所带来的缺点是:
    1 单个进程可监视的fd数量被限制
    2 须要维护一个用来存放大量fd的数据结构。这样会使得用户空间和内核空间在传递该结构时复制开销大
    3 对socket进行扫描时是线性扫描

    Poll

    poll本质上和select没有区别,它将用户传入的数组复制到内核空间。然后查询每一个fd相应的设备状态,假设设备就绪则在设备等待队列中加入一项并继续遍历,假设遍历全然部fd后没有发现就绪设备,则挂起当前进程。直到设备就绪或者主动超时。被唤醒后它又要再次遍历fd。这个过程经历了多次无谓的遍历。
    它没有最大连接数的限制,原因是它是基于链表来存储的,可是相同有一个缺点:大量的fd的数组被总体复制于用户态和内核地址空间之间,而无论这样的复制是不是有意义。
    poll另一个特点是“水平触发”,假设报告了fd后,没有被处理,那么下次poll时会再次报告该fd。

    Epoll

    epoll支持水平触发和边缘触发,最大的特点在于边缘触发。它仅仅告诉进程哪些fd刚刚变为就需态。并且仅仅会通知一次。


    在前面说到的复制问题上,epoll使用mmap降低复制开销。


    另一个特点是,epoll使用“事件”的就绪通知方式,通过epoll_ctl注冊fd,一旦该fd就绪,内核就会採用相似callback的回调机制来激活该fd。epoll_wait便能够收到通知

    注:水平触发(level-triggered):仅仅要满足条件,就触发一个事件(仅仅要有数据没有被获取。内核就不断通知你);边缘触发(edge-triggered)——每当状态变化时。触发一个事件。

    Select

    Poll

    Epoll

    支持最大连接数

    1024x86) or 2048x64

    无上限

    无上限

    IO效率

    每次调用进行线性遍历,时间复杂度为ON

    每次调用进行线性遍历,时间复杂度为ON

    使用事件通知方式。每当fd就绪。系统注冊的回调函数就会被调用,将就绪fd放到rdllist里面。这样epoll_wait返回的时候我们就拿到了就绪的fd

    时间发复杂度O1

    fd拷贝

    每次select都拷贝

    每次poll都拷贝

    调用epoll_ctl时拷贝进内核并由内核保存。之后每次epoll_wait不拷贝

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

     select

     由于每次调用时都会对连接进行线性遍历。所以随着FD的添加会造成遍历速度慢的线性下降性能问题

     poll

     同上

     epoll

     由于epoll内核中实现是依据每一个fd上的callback函数来实现的。仅仅有活跃的socket才会主动调用callback,所以在活跃socket较少的情况下。使用epoll没有前面两者的线性下降的性能问题,可是全部socket都非常活跃的情况下,可能会有性能问题。

       

    消息传递方式

     select

     内核须要将消息传递到用户空间。都须要内核拷贝动作

     poll

     同上

     epoll

     epoll通过内核和用户空间共享一块内存来实现的


    使用:

    当同一时候须要保持非常多的长连接,并且连接的开关非常频繁时,就能够发挥epoll最大的优势了。这里与server模型其实已经有些交集了。
    同一时候须要保持非常多的长连接,并且连接的开关非常频繁,最高效的模型是非堵塞、异步IO模型。并且不要用select/poll。这两个API的有着O(N)的时间复杂度。
    在Linux用epoll,BSD用kqueue。Windows用IOCP。或者用Libevent封装的统一接口(对于不同平台Libevent实现时採用各个平台特有的API)。这些平台特有的API时间复杂度为O(1)。 然而在非堵塞,异步I/O模型下的编程是非常痛苦的。由于I/O操作不再堵塞,报文的解析须要小心翼翼。并且须要亲自管理维护每一个链接的状态。并且为了充分利用CPU,还应结合线程池,避免在轮询线程中处理业务逻辑。
    但这样的模型的效率是极高的。以知名的httpservernginx为例,能够轻松应付上千万的空连接+少量活动链接。每一个连接连接仅须要几K的内核缓冲区,想要应付很多其它的空连接,仅仅需简单的添加内存(数据来源为淘宝一位project师的一次技术讲座,并未实測)。

    这使得DDoS攻击者的成本大大添加,这样的模型攻击者仅仅能将server的带宽全部占用。才干达到目的,而双方的投入是不成比例的。

    注:长连接——连接后始终不断开,然后进行报文发送和接受;短链接——每一次通讯都建立连接,通讯完毕即断开连接,下次通讯再建立连接。


    linux提供了select、poll、epoll接口来实现IO复用,三者的原型例如以下所看到的:

    int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
    int poll(struct pollfd *fds, nfds_t nfds, int timeout);
    int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

    select、poll、epoll_wait參数及实现对照
    1. select的第一个參数nfds为fdset集合中最大描写叙述符值加1,fdset是一个位数组,其限制大小为__FD_SETSIZE(1024),位数组的每一位代表其相应的描写叙述符是否须要被检查。select的第二三四个參数表示须要关注读、写、错误事件的文件描写叙述符位数组,这些參数既是输入參数也是输出參数,可能会被内核改动用于标示哪些描写叙述符上发生了关注的事件。

    所以每次调用select前都须要又一次初始化fdset。

    timeout參数为超时时间,该结构会被内核改动,其值为超时剩余的时间。
    select相应于内核中的sys_select调用。sys_select首先将第二三四个參数指向的fd_set复制到内核,然后对每一个被SET的描写叙述符调用进行poll,并记录在暂时结果中(fdset),假设有事件发生,select会将暂时结果写到用户空间并返回;当轮询一遍后没有不论什么事件发生时,假设指定了超时时间,则select会睡眠到超时。睡眠结束后再进行一次轮询,并将暂时结果写到用户空间。然后返回。
    select返回后。须要逐一检查关注的描写叙述符是否被SET(事件是否发生)。

    2. poll与select不同,通过一个pollfd数组向内核传递须要关注的事件,故没有描写叙述符个数的限制,pollfd中的events字段和revents分别用于标示关注的事件和发生的事件,故pollfd数组仅仅须要被初始化一次。
    poll的实现机制与select相似,其相应内核中的sys_poll,仅仅只是poll向内核传递pollfd数组,然后对pollfd中的每一个描写叙述符进行poll,相比处理fdset来说。poll效率更高。
    poll返回后。须要对pollfd中的每一个元素检查其revents值,来得指事件是否发生。

    3. epoll通过epoll_create创建一个用于epoll轮询的描写叙述符,通过epoll_ctl加入/改动/删除事件,通过epoll_wait检查事件,epoll_wait的第二个參数用于存放结果。
    epoll与select、poll不同,首先,其不用每次调用都向内核拷贝事件描写叙述信息,在第一次调用后。事件信息就会与相应的epoll描写叙述符关联起来。

    另外epoll不是通过轮询。而是通过在等待的描写叙述符上注冊回调函数。当事件发生时,回调函数负责把发生的事件存储在就绪事件链表中,最后写到用户空间。
    epoll返回后,该參数指向的缓冲区中即为发生的事件,对缓冲区中每一个元素进行处理就可以,而不须要像poll、select那样进行轮询检查。

  • 相关阅读:
    JS自定义功能函数实现动态添加网址参数修改网址参数值
    伍、ajax
    类的静态方法(函数)中为什么不能调用非静态成员(属性)?
    android 数据存储 SharePreferences 简单使用
    实现多线程的方式
    线程、进程概念与Android系统组件的关系
    通知—Notifications
    活动栏—Action Bar
    Android菜单—Menu
    对话框控件—Dialog
  • 原文地址:https://www.cnblogs.com/lcchuguo/p/5403984.html
Copyright © 2011-2022 走看看