zoukankan      html  css  js  c++  java
  • select poll epoll相关知识速记

    缘起

    面试的时候经常被问的一个很蛋疼的问题,经常被问,但是知识很零散,难记忆,看完就忘

    select

    作用

    可以监视文件描述符是否可以读写,要求监视的文件描述符是非阻塞的

    诞生背景

    产生与上个世纪80年代的UNIX系统,到1993年写入POSIX1.b规范(一个操作系统的编程接口的规范,你要是写个操作系统想被兼容得遵守这个规范)。由于那个年代还没有多线程(2年后线程相关的内容才写入POSIX1.c规范),还没有什么C10K问题,所以在设计select的时候体现了那个年代的特点。

    接口

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

    nfds 是参数2,3,4中最大的文件描述符 + 1

    readfds 是要检测fd的可读事件,当fd可读的时候select就返回

    writefds 是要检测fd的写事件,当fd可写的时候select就返回

    excpetfds 是要检测fd的出错事件,当fd出错的时候select就返回,当是NULL的时候,就是检测readfds和writefds的出错事件,所以一般都写NULL

    timeout 是一个纳秒级的超时时间

    想使用这个函数需要设置个fd_set结构,所以就用到void FD_SET(int fd, fd_set *set);这个宏,用来设置fd_set

    使用过程

    fd_set fd_in, fd_out;
    struct timeval tv;
     
    // 初始化fd_set
    FD_ZERO( &fd_in );
    FD_ZERO( &fd_out );
     
    // 把网络IO复制到fd_set
    FD_SET( sock1, &fd_in );
    FD_SET( sock1, &fd_out );
     
    // 用户初始化select
    int largest_sock = sock1 > sock2 ? sock1 : sock2;
     
    // 设置select的超时时间
    tv.tv_sec = 10;
    tv.tv_usec = 0;
     
    // 调用select 并阻塞在这里等待 IO事件
    int ret = select( largest_sock, &fd_in, &fd_out, NULL, &tv );
     
    // 检查返回值的状态
    if ( ret == -1 )
        // 异常情况
    else if ( ret == 0 )
        // 超时或者没有可以监控的fd
    else
    {
        // 检测每个IO事件是否可以读写
        if ( FD_ISSET( sock1, &fd_in ) )
            // IO可读
     
        if ( FD_ISSET( sock2, &fd_out ) )
            // IO可写
    }
    View Code

    可以看到使用select的时候,每个fd对应一个fd_set结构,然后调用FD_SET,调用select以后进入polling,等返回以后通过FD_ISSET对每个fd_set检测是否可读可写。

    存在问题

    是不是会儿还没有现在nginx几万并发的场景,select只能对1024个fd进行监控

    select 函数会修改fd_set,所以每次调用完select以后需要重新通过FD_SET设置fd_set

    select 返回以后并不知道具体哪个fd可以读写,需要使用FD_ISSET把所有的fd检测一遍才知道具体是哪个可读可写

    那年代估计不像现在这么广泛的用多线程,所以select中的fd_set在调用select的时候相当于被独占的

    优点

    使用简单,POSIX标准所以跨平台比较好

    POLL

    功能和select相同,但是主要解决select的一些限制

    接口

    int poll(struct pollfd fds[], nfds_t nfds, int timeout);

    fds 是一个pollfd的数组,和select的fd_set差不多,下面具体解释

    nfds 是fds数组的长度,可以看到没有select还需要求一个fd最大值再加1那么麻烦

    timeout 是超时的毫秒数

    pollfd的结构

    struct pollfd {
        int    fd;       /* 文件描述符 */
        short  events;   /* 需要监听的事件 */
        short  revents;  /* 返回的事件 */
    };

    对比一下select,接口上更加优雅,首先,pollfd 通过单独的events来区分了监控的是什么样的事件,而不是像select那样通过参数来区分。

    使用过程

    // 创建pollfd
    struct pollfd fds[2];
     
    // 设置pollfd的fd和要监控的事件,sock1监控读,sock2监控写
    fds[0].fd = sock1;
    fds[0].events = POLLIN;
    fds[1].fd = sock2;
    fds[1].events = POLLOUT;
     
    // 10秒超时,开始等待sock1和sock2上的事件
    int ret = poll( &fds, 2, 10000 );
    // 有事件返回
    if ( ret == -1 )
        // 出错了
    else if ( ret == 0 )
        // 超时
    else
    {
        // 对每个pollfd检测是否有就绪的事件
        if ( pfd[0].revents & POLLIN )
            pfd[0].revents = 0;
            // 可读
    
        if ( pfd[1].revents & POLLOUT )
            pfd[1].revents = 0;
            // 可写
    }
    View Code

    与select相同,都是创建结构,设置,开始polling,逐个检测事件

    相比于SELECT的改进

    对于可以监控fd的数量没有限制,而不是像select那样最大才1024个

    每次poll之后不需要重新设置pollfd,而不像fd_set需要重新设置

    兼容性

    vista之前的windows上没有poll

    #if defined (WIN32)
    static inline int poll( struct pollfd *pfd, int nfds, int timeout) { return WSAPoll ( pfd, nfds, timeout ); }
    #endif

    EPOLL

    linux平台上最新的polling技术,出现与linux2.6版本,linux2.6发布是在2003年(居然epoll出现已经12年了)。

    接口

    int epoll_create(int size);

    用于创建一个size大小的epoll,返回一个epfd的描述符

    int  epoll_ctl(int  epfd,  int  op,  int fd, struct epoll_event *event);

    修改某个文件描述符的状态

    epfd 是创建的epoll

    op 是修改的操作类型,可以是EPOLL_CTL_ADD 或者 EPOLL_CTL_DEL,代表添加和删除

    fd 是要操作的文件描述符

    event 是文件描述符fd上挂的一个context,是一个epoll_event结构体,下面是epoll_event的结构体内容:

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

    其中epoll_event.events 和 pollfd中的events 差不多,不过事件更加丰富,data是对于文件描述符上可以挂的卫星数据,也更加灵活。

    int epoll_wait(int epfd, struct epoll_event *events,
                          int maxevents, int timeout);

    epdf 是epoll_create时候返回的

    events 是polling 返回的结果会赋值在events

    maxevents 是一次通知用户最大的events数量,一般就是events的数组长度

    timeout 是超时时间

    相比select/poll,epoll_wait 只返回可读写的事件,而不是全部返回

    关于几个size的理解

    epoll_create 时候的size是指 epoll在核心态监控fd的最大数量

    epoll_wait 时候的events 是指一次通知的数据,这个数量认为是一次批量,肯定是小于等于epoll_create时候的大小,比这个再大也没用了

    epoll_wait 时候的maxevents 是为了防止一次通知events溢出的一个边界,如果设置的比events的数组长度小,那就相当于批量变小,比这个大会溢出,所以应该是相等就可以了

    使用过程

    // 创建
    int pollingfd = epoll_create( 0xCAFE ); 
    
    // 创建一个epoll_event 用来一会儿epoll_ctl的时候EPOLL_CTL_ADD用
    struct epoll_event ev = { 0 };
    
    // 假设sock1是个网络连接
    int sock1 = pConnection1->getSocket();
    
    // 给这个sock1挂一点卫星数据,这里可以是任意的东西,我们就放个他的connection
    ev.data.ptr = pConnection1;
    
    // 来监控sock1的可读事件
    ev.events = EPOLLIN | EPOLLONESHOT;
    
    // 把设置好的epoll_event 添加到创建的epollfd
    if ( epoll_ctl( epollfd, EPOLL_CTL_ADD, sock1, &ev ) != 0 )
        // 出错
    
    // 创建一些epoll_event用来在用户态来接收
    struct epoll_event pevents[ 20 ];
    
    // 等待可读事件
    int ready = epoll_wait( pollingfd, pevents, 20, 10000 );
    if ( ret == -1 )
        // 出错
    else if ( ret == 0 )
        // 超时
    else
    {
        // ret是返回了多少个可以读写的事件
        for ( int i = 0; i < ret; i++ )
        {
            // 判断通知到用户这个到底是个什么事件
            if ( pevents[i].events & EPOLLIN )
            {
                // 取到当初我们挂在上面的卫星数据
                Connection * c = (Connection*) pevents[i].data.ptr;
                // 对这个socket进行一些操作
                c->handleReadEvent();
             }
        }
    }
    View Code

    epoll的使用过程还是比select和poll复杂不少的,首先你得创建一个epoll,然后创建和设置epoll_event,再通过epoll_ctl添加到epoll,最后epoll_wait,遍历通知过来的events

    比select和poll的改进

    最大的改进就是不需要在遍历所有事件了,不需要FD_ISSET,也不需要遍历所有pollfd.revents,取而代之的是,内核帮我们把active的fd赋值到epoll_wait的events上

    pollfd封装了一个event,而不是像select的fd_set只有一个fd属性,epoll_event 比pollfd又多了一个data的卫星数据,可以放任意的东西上去

    select和poll一旦进入polling阶段,就没法对fd做修改了,但是epoll_ctl可以在任意的线程里在任意事件动态的添加,删除epoll_event

    缺点

    改变epoll中fd的监听事件类型需要epoll_ctl的系统调用,而在poll中只需要在用户态做BITMASK

    只能在linux上用,虽然有libevent这种东西

    epoll的api比select和poll复杂

    该如何选择

    如果连接数很低小于1024,epoll对比select和poll是没有性能提升的,选择select还是poll就看个人喜好了,一般select就行,比如fpm,epoll早就出了fpm也没改,PHP很少有人能worker开1000以上

    如果连接是短连接,经常accept出一些fd添加到epoll中的系统调用开销需要考虑,具体性能还需要再综合考虑,比如nginx,虽然都是短连接,但是有高并发,几万并发select每次遍历一遍所有fd更耗

    如果是长连接,并且都是idle的,例如一些聊天的服务器,一个连接,半天才说一句话,都是挂机的,但是连接几十万,那有个人说句话,服务区需要读,你遍历几十万个fd就不值了

    如果你的应用是多线程来处理网络的,那么为了利用多线程还是使用epoll比较好,可以用多线程配合边缘触发(如果可读只通知一次,不管读完没读完,水平触发没读完就一直通知,所以效率会比边缘触发低一些),这也是边缘触发推荐的使用方式。

    为什么epoll高效

    简单来说是这样的select和poll当检测到fd就绪以后,就通知到用户态了,函数也就返回了。而epoll在add的时候就开始监听,发现他就绪以后就放到一个就绪表里,epoll_wait只是定时查看一下这个就绪表里的数据。

    参考文章

    https://cs.uwaterloo.ca/~brecht/papers/getpaper.php?file=ols-2004.pdf

    http://www.ulduzsoft.com/2014/01/select-poll-epoll-practical-difference-for-system-architects/

    http://www.unix.org/what_is_unix/history_timeline.html

    http://en.wikipedia.org/wiki/Asynchronous_I/O

    http://en.wikipedia.org/wiki/POSIX

    http://blog.csdn.net/vividonly/article/details/7539342

  • 相关阅读:
    文件语音识别Google语音识别学习札记 Windows PC机上测试语音识别Strut2教程java教程
    进程方法Android面试题(二)Strut2教程java教程
    工程选择LibGdx开发环境搭建Strut2教程java教程
    检查点重做检查点队列简单总结Strut2教程java教程
    ORACLE连接数据库(备忘)
    字符扫描剑指Offer读书笔记之第二章2字符替换Strut2教程java教程
    文件函数Django1.5实战第一步之搭建环境,创建工程,运行开发服务器Strut2教程java教程
    算法长度K最短路问题(单源点最短路径+A*算法)Strut2教程java教程
    单位真实世界LibGdxBox2d单位换算Strut2教程java教程
    配置执行【Oracle】无法对所有 EM 相关帐户解锁Strut2教程java教程
  • 原文地址:https://www.cnblogs.com/23lalala/p/4288718.html
Copyright © 2011-2022 走看看