muduo 31 timer定时器模块

目录

muduo中定时器模块的特点

muduo中的定时器系统

muduo中定时器实现的逻辑:

Timer类

TimerQueue类

使用timerfd实现定时功能

timer_create

timerfd_settime

创建TimerQueue

删除定时器管理对象​编辑

插入定时器流程

处理到期定时器


muduo中定时器模块的特点

  1. 整个TimerQueue只使用一个timerfd来观察定时事件,并且每次重置timerfd时只需跟set中第一个节点比较即可,因为set本身是一个有序队列。
  2. 整个定时器队列采用了muduo典型的事件分发机制,可以使的定时器的到期时间像fd一样在Loop线程中处理。
  3. 之前Timestamp用于比较大小的重载方法在这里得到了很好的应用。

muduo中的定时器系统

          在muduo的定时器系统中,一共由四个类:Timestamp,Timer,TimeId,TimerQueue组成。其中最关键的是Timer和TimerQueue两个类。该项目中没有使用Timeld类。

        TimerQueue类,是整个定时器设施的核心,其他三个类简介其作用。 其中Timestamp是一个以int64_t表示的微秒级绝对时间,而Timer则表示一个定时器的到时事件,是否具有重复唤醒的时间等,TimerId表示在在TimerQueue中对Timer的索引

muduo中定时器实现的逻辑:

①:Timer类:Timer类包含了一个超时时间戳和一个回调函数。当超时时间戳到达时,调用回调函数出发定时事件。

②:TimerQueue类:TimerQueue类是一个基于事件戳排序的定时器容器。它使用了最小堆(MinHeap)数据结构来保证定时器按照超时时间的顺序进行排列。TimerQueue类提供了添加、删除和获取最近超时的定时器的接口。

③:EventLoop类:EventLoop类是muduo网络库的核心组件,负责事件的循环和处理。其中包括定时器事件的管理。EventLoop有一个成员变量TimerQueue timerQueue_,用于存储定时器对象。EventLoop会在事件循环中监测定时器队列中最近超时的定时器,并调用其回调函数。

Timer类

  1. 定时器到期后需要回调函数;
  2. 定时器需要记录我们的超时时间;
  3. 如果是重复事件(比如每间隔5秒扫描一次用户连接),我们还需要记录超时间间隔;
  4. 对应的,我们需要一个bool类型的值标注这个定时器是一次性的还是重复的。

muduo 31 timer定时器模块_第1张图片

对于不是一次性的定时器,我们通过restart方法,观察定时器的构造函数中

repeat_(interval > 0.0) // 一次性定时器设置为0

如果是需要重新利用的定时器,会调用restart方法,我们设置其下一次超时时间为「当前时间 + 间隔时间」。如果是「一次性定时器」,那么就会自动生成一个空的 Timestamp,其时间自动设置为 0.0

muduo 31 timer定时器模块_第2张图片

TimerQueue类

TimerQueue类管理作为管理定时器的结构。其内部使用 STL 容器 set 来管理定时器。我们以时间戳作为键值来获取定时器。set 内部实现是红黑树,红黑树中序遍历便可以得到按照键值排序过后的定时器节点。小顶堆,说明最小的事件戳(即最早出发的定时器位于容器顶部)

using Entry = std::pair;
using TimerList = std::set;

muduo 31 timer定时器模块_第3张图片

①:整个TimerQueue之打开一个timefd,用以观察定时器队列队首的到期事件。其原因是因为set容器是一个有序队列,以<排序,就是说整个队列中,Timer的到期时间时从小到大排列的,正是因为这样,才能做到节省系统资源的目的。

②:整个定时器队列采用了muduo典型的事件分发机制,可以使得定时器的到期时间像fd一样在Loop线程中处理。

使用timerfd实现定时功能

timerfd与IO多路复用机制(如epoll)结合使用,可以实现基于事件得事件驱动编程。当定时器到期时,可以通过epoll等机制监视timerfd得可读事件,并触发相应得事件处理逻辑。当超时事件发生时,该文件描述符就变为可读。

muduo 31 timer定时器模块_第4张图片

timer_create
timerfd_create(CLOCK_MONOTONIC,TFD_NONBLOCK | TFD_CLOEXEC);

muduo 31 timer定时器模块_第5张图片

timerfd_settime
int timerfd_settime(int fd,int flags
                    const struct itimerspec *new_value
                    struct itimerspec *old_value);
                    //成功返回0

 我们使用此函数启动或停止定时器。muduo 31 timer定时器模块_第6张图片

创建TimerQueue

  1. 通过timer_create创建timerfd
  2. TimerQueue类初始化后,设置其timerfdChannel绑定读事件,并置于可读状态

muduo 31 timer定时器模块_第7张图片

删除定时器管理对象muduo 31 timer定时器模块_第8张图片

插入定时器流程

1、EventLoop调用方法,加入一个定时器事件,传入定时器回调函数,超时时间和间隔时间(为0.0则为一次性定时器),addTimer方法根据这些属性构造新得定时器。

2、定时器队列内部插入此定时器,并判断这个定时器得超时时间是否比先前得都早。如果是最早触发的,就会调用resetTimerfd方法重新设置tiemrfd_的触发时间。内部会根据超时时间和现在时间计算出新的超时时间。muduo 31 timer定时器模块_第9张图片

 内部实现的插入方法获取此定时器的超时时间,如果比先前的时间小就说明第一个触发。那么我们会设置好布尔变量。因此这涉及到timerfd_的触发时间。

muduo 31 timer定时器模块_第10张图片

重置timerfd_  :通过计算时间差使用timerfd_settime将新的到期时间设置到指定定时器事件得文件描述符上muduo 31 timer定时器模块_第11张图片

处理到期定时器

  1. EventLoop获取活跃的activeChannel,并分别调取它们绑定的回调函数。这里对于timerfd_,就是调用了handleRead方法
  2. handleRead方法获取已经超时的定时器组数组,遍历到期的定时器并调用内部绑定的回调函数。之后调用reset方法重新设置定时器
  3. reset方法内部判断这些定时器是否是可重复使用的,如果是则继续插入定时器管理队列,之后自然会触发事件。如果不是,那么就删除。如果重新插入了定时器,记得还需重置timerfd_。

muduo 31 timer定时器模块_第12张图片

①:ReadTimerFd读取定时器文件描述符(timerfd)的值:muduo 31 timer定时器模块_第13张图片

②: getExpired获取已经到期的定时器列表:

muduo 31 timer定时器模块_第14张图片

  • getExpired函数接收一个时间戳参数now,表示当前时间。
  • 首先,创建一个空的expired向量,用于存储已经到期的定时器节点。
  • 创建一个临时的定时器节点sentry,其时间戳为now,指针为一个特殊值UINTPTR_MAX,用于作为一个哨兵节点。(这里设置所有定时器都不会超时,可以自己定义)
  • 使用lower_bound函数在timers_容器中查找第一个大于或等于sentry的节点,返回一个迭代器指向该位置,存储在end中。
  • 使用std::copy函数将timers_容器中从起始位置到end位置的节点复制到expired向量中。
  • 使用erase函数从timers_容器中删除从起始位置到end位置的节点。(set小根堆,越小说明发生越早,所以删除前半段)
  • 最后,返回存储了已删除的到期定时器节点的expired向量。

③:遍历需要删除的定时器,执行回调函数:muduo 31 timer定时器模块_第15张图片

④:重新设置定时器之前,调用reset函数对已到期的定时器进行处理:

muduo 31 timer定时器模块_第16张图片

 

你可能感兴趣的:(重写Muduo网络库项目,linux,服务器)