项目中的定时器应用

第一部分:
首先我们需要了解定时器。
我们将每个定时事件分别封装成定时器,并使用某种容器类数据结构,比如链表,排序链表和时间轮,将所有定时器串联起来,以实现对定时事件的统一管理。
在这主要了解时间轮和时间堆。
定时是指一段时间之后触发某段代码的机制,我们可以在这段代码中依次处理所有到期的定时器。
Linux提供了三种定时方法:
1.socket选项SO_RCVTIMEO和SO_SNDTIMEO.
他们是分别用来接收数据超时时间和发送数据超时时间。
所以这两个选项仅对与数据接收和发送相关的socket专用系统调用有效,这些系统调用包括send,sendmsg,recv,recvmsg,accept和connect。
在这里插入图片描述

可以根据系统调用的返回值以及ERRNO来判断时间是否已到,进而决定是否开始处理定时任务。

2.SIGALRM信号。
前面知道由alarm和setitimer函数设置的实时闹钟一旦超时,将触发SIGALRM信号。因此,我们可以利用该信号的信号处理函数来处理定时任务。

基于升序链表的定时器
定时器通常至少要包含两个成员,一个超时时间和一个任务回调函数。如果使用链表作为容器来串联所有的定时器,则每个定时器还要包含指向下一个定时器的指针成员。

#ifndef   它是if not define 的简写,是宏定义的一种,实际上确切的说,这应该是预处理功能三种(宏定义、文件包含、条件编译)中的一种----条件编译。
  在c语言中,对同一个变量或者函数进行多次声明是不会报错的。所以如果h文件里只是进行了声明工作,即使不使用#
ifndef宏定义,多个c文件包含同一个h文件也不会报错。

但是在c++语言中,#ifdef的作用域只是在单个文件中。所以如果h文件里定义了全局变量,即使采用#ifdef宏定义,多个c文件包含同一个h文件还是会出现全局变量重定义的错误。
使用#ifndef可以避免下面这种错误:如果在h文件中定义了全局变量,一个c文件包含同一个h文件多次,如果不加#ifndef宏定义,会出现变量重复定义的错误;如果加了#ifndef,则不会出现这种错误。
示例:
   
#ifndef x //先测试x是否被宏定义过

升序定时器链表


#ifndef LST_TIMER
#define LST_TIMER

#include<time.h>

#define BUFFER_SIZE 64

class util_timer;

using namespace std;

//用户数据结构:客户端socket地址,socket文件描述符,读缓存和定时器
struct client_data {
    sockaddr_in address;
    int sockfd;
    char buf[BUFFER_SIZE];
    util_timer *timer;
};

/定时器类/

class util_timer {
public:
    util_timer() : prev(NULL), next(NULL) {}

public:
    time_t expire;//任务的超时时间
    void (*cb_func)(client_data *);//任务回调函数
    //回调函数处理的客户数据,由定时器的执行者传递给回调函数
    client_data *user_data;
    util_timer *prev;
    util_timer *next;
};

//定时器链表。他是一个升序,双向链表,且带有头节点和尾节点
class sort_timer_lst {
public:
    sort_timer_lst() : head(NULL), tail(NULL) {}

    ~sort_timer_lst() {
        util_timer *tmp = head;
        while (tmp) {
            head = tmp->next;
            delete tmp;
            tmp = head;
        }
    }

    void add_timer(util_timer *timer)//将目标定时器添加到链表中
    {
        if (!timer)return;
        if (!head) {
            head = tail = timer;
            return;
        }
        //如果目标定时器的超时时间小于当前链表中所有定时器的超时时间,则把该定时器插入链表头部,作为链表的新的头节点,否则调用函数将它插入合适的位置,保证链表的升序。
        if (timer->expire < head->expire) {
            timer->next = head;
            head->prev = timer;
            head = timer;
            return;
        }
        add_timer(timer, head);
    }

    /*调整定时器在链表中的位置,当对应的定时任务发生变化时。在这只考虑超时时间延长,就是调整到链表末尾*/
    void adjust_timer(util_timer *timer) {
        if (!timer)return;
        util_timer *tmp = timer->next;
        if (!tmp || (timer->expire < tmp->expire))return;
        //如果是头节点
        if (timer == head) {
            head = head->next;
            head->prev = nullptr;
            timer->next = null;
            add_timer(timer, head);
        }
            //中间节点,都是取出来然后在放进去
        else {
            timer->prev->next = timer->next;
            timer->next->prev = timer->prev;
            add_timer(timer, timer->next);
        }
    }

    void del_timer(util_timer *timer) {
        if (!timer)return;
        if ((timer == head) && (timer == tail)) {
            delete timer;
            head = nullptr;
            tail = nullptr;
            return;
        }
        if (timer == head) {
            head = head->next;
            head->prev = nullptr;
            delete timer;
            return;
        }
        if (timer == tail) {
            tail = tail->prev;
            tail->next = nullptr;
            delete timer;
            return;
        }
        timer->prev->next = timer->next;
        timer->next->prev = timer->prev;
        delete timer;
    }

    /*SIGALRM信号每次被触发就在其信号处理函数中执行一次tick函数,以处理链表上到期的任务*/
    void tick() {
        if (!head)return;
        printf("timer tick\n");
        time_t cur = time(NULL);
        util_timer *tmp = head;
        //从头结点开始依次处理每个定时器,直到遇到一个尚未到期的定时器
        while (tmp) {
            if (cur < tmp->expire) {
                break;
            }
            tmp->cb_func(tmp->user_data);
            head = tmp->next;
            if (head) {
                head->prev = nullptr;
            }
            delete tmp;
            tmp = head;
        }
    }

private:
    //重载添加函数
    void add_timer(util_timer *timer, util_timer *lst_head) {
        util_timer *prev = lst_head;
        util_timer *tmp = prev->next;
        while (tmp) {
            if (timer->expire < tmp->expire) {
                prev->next = timer;
                timer->next = tmp;
                tmp->prev = timer;
                break;
            }
            prev = tmp;
            tmp = tmp->next;
        }
        //插入到尾节点
        if (!tmp) {
            prev->next = timer;
            timer->prev = prev;
            timer->next = nullptr;
            tail = timer;
        }
    }

private:
    util_timer *head;
    util_timer *tail;
};

升序定时器链表的实际应用:用来处理非活动连接。
服务器程序通常要定期处理非活动连接:给客户端发一个重连请求,或者关闭该连接。
3.I/O复用系统调用的超时参数。

#define TIMEOUT 5000
int timeout = TIMEOUT;
time_t start = time(NULL);
time_t end = time(NULL);
while(1)
{
printf("timeout\n");
start = time(NULL);
int number = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, timeout);
if((number<0)&&(errno!=EINTR))
{
printf("epoll failure\n");
break;
}
//如果epoll_wait成功返回0,说明超时时间到,此时可以处理定时任务,并且重置定时时间
if(number==0)
{
timeout = TIMEOUT;
continue;
}
end = time(NULL);
/*如果返回值大于0,我们要减去这次调用的时间,然后获得下次调用的超时参数*/
timeout-=(end-start)*1000;
//重新计算之后也有可能等于0,说明调用返回时候,不仅有文件描述符就绪,而且超时时间也刚好到达
if(timeout<=0)
{
timeout = TIMEOUT;
}
}

第二部分:时间轮和时间堆
1.时间轮
基于排序链表的定时器存在一个问题:添加定时器的效率偏低。
在这里插入图片描述
时间轮的槽间隔si,时间轮共有N个槽,因此它运转一周的时间是Nsi,每个槽指向一条定时器链表,所以每条链表上的定时器具有相同的特征:他们的定时时间相差Nsi的整数倍。利用这个关系将定时器散列到不同的链表中。加入现在指针指向槽cs,要添加一个定时时间为ti的定时器,则该定时器将被插入槽ts对应的链表中:
ts=(cs+(ti/si))%N;
2.时间堆
这种设计思路是:将所有定时器中超时时间最小的一个定时器的超时值作为心搏间隔,这样,一旦心搏函数tick被调用,超时时间最小的定时器必然到期,我们就可以在tick函数中处理该定时器。然后在接着从剩余的定时器中超出超时时间最小的一个。最小堆可以用来实现这种结构。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值