• NIO原理浅析(三)


    epoll

    首先认识一下epoll的几个基础函数

    int s = socket(AF_INET, SOCK_STREAM, 0);
    bind(s, ...);
    listen(s, ...);
    
    int epfd = epoll_create(...)
    epoll_ctl(epfd, ...); //将所有需要监听的socket添加到epfd中
    
    while(1) {
        int n = epoll_wait(...);
        for(接受到数据的socket) {
          //处理数据
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    这段代码涉及到几个与epoll相关的函数方法:

    • epoll_create: 创建一个文件句柄

    • epoll_ctl: 向epoll对象添加/修改/删除要管理的连接

    • epoll_wait: 等待其管理的连接上的IO事件

    1、epoll_create
    int epoll_create(int size);
    
    • 1

    功能描述:用于生成一个epoll专用的文件描述符

    参数size:因为epoll底层使用红黑树来保存文件描述符,红黑树的查找时间复杂度为O(logN),这个size并没有必要限制大小。size可以设置为大于0的任何数。

    返回值:如果成功,返回epoll专用的文件描述符,如果失败,则返回-1。

    2、epoll_ctl
    int epoll_ctl(int epfd, int op, int fd, struct epoll_event * event);
    
    • 1

    epoll中的事件注册函数,用于注册要监听的事件类型

    参数

    • epfd: epoll专用的文件描述符,epoll_create()的返回值

    • op: 表示动作,用三个宏来表示:

      • EPOLL_CTL_ADD: 注册新的fd到epfd中

      • EPOLL_CTL_MOD: 修改已经注册的fd的监听事件

      • EPOLL_CTL_DEL: 从epfd中删除一个fd

    • fd: 需要被监听的文件描述符

    • event: 告诉内核需要监听什么事件

    • 返回值:0表示成功,-1表示失败

    epoll_event结构体如文档描述:

           #include 
    
           struct epoll_event {
               uint32_t      events;  /* Epoll events */
               epoll_data_t  data;    /* User data variable */
           };
    
           union epoll_data {
               void     *ptr;
               int       fd;
               uint32_t  u32;
               uint64_t  u64;
           };
    
           typedef union epoll_data  epoll_data_t;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    events可以是以下几个宏的集合:

    • EPOLLIN: 表示对应的文件描述符可以读

    • EPOLLOUT:表示对应的文件描述符可以写

    • EPOLLPRI:表示对应的文件描述符有紧急的数据可以读

    • EPOLLERR: 表示对应的文件描述符发生错误

    • EPOLLHUP:表示对应的文件描述符被挂断

    • EPOLLET: 将EPOLL设为边缘触发模式,不设置为水平触发模式

    • EPOLLONESHOT: 只监听一次事件,当监听完这次事件之后,如果还需要监听这次事件,则需要把socket重新加入到EPOLL中。

    3、epoll_wait
    int epoll_wait(int epfd, struct epoll_event * event, 
                   int maxevents, int timeout);
    
    • 1
    • 2

    功能:本函数用于监控事件的发生,当epoll监控的事件中存在已经发送的事件,那么就可以将此事件收集起来。

    参数:

    • epfd:epoll自身产生的文件描述符,调用epoll_create的返回值

    • event:事先必须将空间分配好的结构体数组,epoll将即将发生的事件赋值到event数组中。

    • maxevents:告诉内核共有多少个event数组的大小

    • timeout:超时时间,单位为毫秒,设为-1时,该函数状态为阻塞。

    • 返回值:如果为-1,表示失败;如果为0,表示已经超时;如果成功,返回需要处理的事件数目。

    epoll整体流程图如下所示:

    在这里插入图片描述

    我们在之前文章中讨论过,select和poll存在三个缺陷,epoll方式很好的解决了这些问题:

    epoll在内核中使用红黑树这个数据结构来保存文件描述符,红黑树这个数据结构的增删改的时间复杂度为O(logn),select/poll每次监听套接字,都需要将套接字列表整个复制到内核态,而epoll使用epoll_ctl,每次将一个监听套接字复制到内核态,这明显减少了用户空间到内核态的大量数据拷贝和内存分配。

    epoll使用异步回调的机制,内核态维护一个就绪队列,如果某个socket已经准备好,那么就会通过回调函数触发内核将socket事件加入到就绪事件列表。用户调用epoll_wait函数,就会返回有事件发生的文件描述符的个数。这个过程中,没有像select/poll一样对socket集合进行了O(N)时间复杂度的轮询。提高了检测的效率。

    边缘触发和水平触发

    epoll支持两种事件触发模式:

    • 边缘触发:当被监控的Socket描述符有可读事件发生时,服务器端在调用epoll_wait这个函数时,只会苏醒一次,然后从内核缓冲区中读取所有的数据

    • 水平触发:当被监控的Socket描述符有可读事件发生时,服务器端在调用epoll_wait这个函数时,会不断的苏醒,直到内核中没有数据可读才停止苏醒。

  • 相关阅读:
    21. 合并两个有序链表
    带有无偏移国内源GeoQ的folium
    Appium在小米11真机上进行微信自动化
    LeetCode 0878. 第 N 个神奇数字
    设计模式——策略模式
    使用Chrome 开发者工具提取对应的字符串
    树莓派部署.net core控制台程序
    HCIP第十二天笔记
    sql语句去掉括号及中括号中的内容,大型数据库与access数据库
    【从零开始学习Redis | 第五篇】基于布隆过滤器解决Redis的穿透问题
  • 原文地址:https://blog.csdn.net/sinat_28199083/article/details/132687297