• 【web server】基于升序链表的定时器


    基于升序链表的定时器,主要用来定时关闭不活跃的连接,避免占用过多的系统资源。整体思路是这样:
    定时器链表中长事件不活跃的fd(file descriptor)称为超时过期的fd
    那这里怎么理解超时过期呢?
    比如,对于一个新来客户端连接fd,我们设置它的时间为当前时间秒数加15秒:

    //设置该用户的超时时间
    time_t cur = time(NULL);
    timer->expire = cur + 3 * 5;
    
    • 1
    • 2
    • 3

    将他加入到升序链表中(链表依据时间升序)
    之后如果该fd活跃了,再将时间更新为活跃时间秒数向后加15秒,即一个活跃的fd时间总是大于当前时间的。
    举个栗子:

    当前时间13:00:00   来了一个新连接fd值为10
    于是值为10的fd的时间就记为13:00:15
    当前时间13:00:05 值为10的fd发送了一条消息
    于是值为10的fd的时间就记为13:00:20
    
    • 1
    • 2
    • 3
    • 4

    若该fd之后再也不活跃了,它的时间也就不会被更新,那么过了15秒后它就被认定为超时过期。所以在这里还有一个定时器,定时检查哪些fd时间比当前时间小,该fd便是超时过期的fd,之后关闭该fd

    这样看来,后来加入的,在链表后面的fd的时间也就越大,整个链表也就是升序的。
    来看一下关键性的代码:
    fd活跃需要调整或移除某fd的时间时:

    if (events[i].events & EPOLLIN) {
        //可读事件
        DBG("read\n");
        util_timer *timer = users_timer[iSockFd].timer;
    
        //主线程读,proactor模式
        if (m_pHttpUsers[iSockFd].read()) {
            m_pThreadPool->append(m_pHttpUsers + iSockFd);
            if (timer) {
                //调整定时器链表
                adjust_timer(timer);
            }
        } else {
            m_pHttpUsers[iSockFd].close_conn();
            if (timer) {
                //调整定时器链表结点
                deal_timer(timer, iSockFd);
            }
        }
    } else if (events[i].events & EPOLLOUT) {
        //可写事件
        DBG("out\n");
        util_timer *timer = users_timer[iSockFd].timer;
        //主线程写,proactor模式
        if (m_pHttpUsers[iSockFd].mwrite()) {
            if (timer) {
                adjust_timer(timer);
            }
        } else {
            m_pHttpUsers[iSockFd].close_conn();
            if (timer) {
                deal_timer(timer, iSockFd);
            }
        }
    }
    
    //调整定时链表
    void Server::adjust_timer(util_timer *timer)
    {
        time_t cur = time(NULL);
        timer->expire = cur + 3 * TIMESLOT;
        m_timer_lst.adjust_timer(timer);
    
        LOG_INFO("%s", "adjust timer once");
    }
    
    //移除定时器链表结点
    void Server::deal_timer(util_timer *timer, int sockfd)
    {
        timer->cb_func(&users_timer[sockfd]);
        if (timer)
        {
            m_timer_lst.del_timer(timer);
        }
    
        LOG_INFO("close fd %d", users_timer[sockfd].sockfd);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    而定时操作是通过linux系统自身的alarm系统调用,同时添加时钟信号处理函数来实现:

    //设置信号处理函数
    addsig(SIGALRM, sig_handler, false);
    alarm(TIMESLOT);
    
    • 1
    • 2
    • 3

    时钟信号处理函数中遍历定时器链表,关闭超时的fd,并删除该结点。

    void sort_timer_lst::tick()
    {
        if (!head)
        {
            return;
        }
        
        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 = NULL;
            }
            delete tmp;
            tmp = head;
        }
    }
    
    void cb_func(client_data *user_data)
    {
        epoll_ctl(Http::s_iEpollfd, EPOLL_CTL_DEL, user_data->sockfd, 0);
        assert(user_data);
        //关闭该fd
        close(user_data->sockfd);
        Http::s_iUserCount--;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    以上便是整个基于升序链表定时器的核心,剩下的便是对链表的基本操作。
    更多实现细节,可以在https://gitee.com/gao-yuelong/web-server中查看。

  • 相关阅读:
    Linux字符设备驱动开发(一)
    互联网医院系统:数字化时代中医疗服务的未来
    传奇开服教程完整版GOM引擎超详细的单机架设图文教程(小白一看就会)
    搁置收购推特后,马斯克盛赞微信:什么都能做、还没有垃圾信息
    103.192.211.X怎么设置一台服务器跳转到第三方服务器上访问
    MySQL的DDL语句
    HDU——2097.sky数、2098.分拆素数和、2099.整除的尾数
    nginx负载均衡实例
    快递鸟顺丰、申通物流查询类通用接口文档
    太简单了,一文彻底搞懂Jenkins的用法
  • 原文地址:https://blog.csdn.net/gaoyuelon/article/details/126900846