高性能定时器-------时间轮

          基于排序链表的定时器(https://blog.csdn.net/destory27/article/details/81748580)存在一个问题:添加定时器的效率偏低。

         如图所示时间轮内,指针指向轮子上的一个槽,它以恒定的速度顺时针旋转,每旋转一步就指向先一个槽.该时间轮共有N个槽,旋转一周的时间是N*Si,每个槽指向一条定时器链表,没条链表上的定时器具有相同的特征:它们的定时时间相差N*Si的整数倍.时间轮正是利用这个关系将定时器散列到不同的链表中。假如现在指针指向槽cs,我们要添加一个定时时间为ti的定时器,则该定时器将被插入槽ts对应的链表:     ts = (cs + ti / si) % N.

        基于排序链表的定时器使用唯一的一条链表来管理所有的定时器,所有插入操作的效率随着定时器数目的增多而降低.而时间轮使用哈希表思想,将定时器散列到不同的链表上.对于时间轮,要提高定时精度,就要使si值足够小;要提高执行效率,则要求N值足够大.

#ifndef _TIME_WHEEL_TIMER
#define _TIME_WHEEL_TIMER
//时间轮
#include <time.h>
#include <netinet/in.h>
#include <iostream>

#define BUFFER_SIZE  64

class tw_timer;  //前向声明

//绑定socket和定时器
struct client_data{
    struct sockaddr_in address;  //addr
    int sockfd;                
    char buf[BUFFER_SIZE];
    tw_timer *timer;
};

/********定时器类*********/
class tw_timer{
    public:
        tw_timer(int rot, int ts)
            :rotation(rot),
             time_slot(ts)
        {}
        
        void (*cb_func)(client_data *);  //定时器回调函数

    public:
        int rotation; //时间轮转多少圈后生效
        int time_slot;     //属于时间轮上哪个槽
        client_data *user_data{nullptr};   //客户数据
        tw_timer *next{nullptr};   //指向下一个定时器
        tw_timer *prev{nullptr};   //指向前一个定时器

};

class time_wheel{
    public:
        time_wheel();
        ~time_wheel();

        tw_timer *add_timer(int timeout);   //根据定时值timeout创建一个定时器 插入合适的槽中
        void del_timer(tw_timer *timer);    //删除目标定时器timer
        void tick();                       //SI时间到后,调用该函数 时间轮前滚动一个槽的间隔

    private:
        static const int N = 60;  //时间轮上槽的数目
        static const int SI = 1; //槽间隔1S
        tw_timer *slots[N];    //槽
        int cur_slot{0}; //当前槽
};

time_wheel::time_wheel()
{
    for(int i = 0; i < N; ++i)
        slots[i] = nullptr;
}

time_wheel::~time_wheel()
{
    for(int i = 0; i < N; ++i)
    {
        tw_timer *tmp = slots[i];
        while(tmp)
        {
            slots[i] = tmp->next;
            delete tmp;
            tmp = slots[i];
        }
    }
}

//创建定时器
tw_timer* time_wheel::add_timer(int timeout)
{
    if(timeout < 0)
        return nullptr;
    int ticks = 0;

    //根据插入定时器的超时值计算它将在时间轮多少个滴答后被触发
    //并将该滴答数存于ticks中
    if(timeout < SI)
        ticks = 1;
    else
        ticks = timeout / SI;
    //计算多少圈后被触发
    int rotation = ticks / N;
    //计算插入哪个槽中
    int ts = (cur_slot + (ticks % N)) % N;

    //创建定时器 在时间轮转动rotation 圈后触发  且位于第ts个槽上 
    tw_timer *timer = new tw_timer(rotation, ts);

    if(!slots[ts])
    {
        std::cout << "add timer, rotation is " << rotation << " , ts is " << ts << " cur_slot is " << cur_slot << std::endl;

        slots[ts] = timer;
    }
    else
    {
        timer->next = slots[ts];
        slots[ts]->prev = timer;
        slots[ts] = timer;
    }

    return timer;
}

//删除目标定时器
void time_wheel::del_timer(tw_timer *timer)
{
    if(!timer)
        return;

    int ts = timer->time_slot;   //在哪个槽上

    if(timer == slots[ts])   //如果槽中第一个结点是要删除结点
    {
        slots[ts] = slots[ts]->next;
        if(slots[ts])
            slots[ts]->prev = nullptr;
        delete timer;
    }
    else
    {
        timer->prev->next = timer->next;
        if(timer->next)
            timer->next->prev = timer->prev;
        timer->next = nullptr;
        timer->prev = nullptr;
        delete timer;
    }
    
    return ;
}

//SI时间到后 调用该函数 
void time_wheel::tick()
{
    tw_timer *tmp = slots[cur_slot];  //取得当前槽的头结点

    std::cout << "Current slot is " << cur_slot << std::endl;
    while(tmp)
    {
        std::cout << "tick the timer once" << std::endl;
        if(tmp->rotation > 0)
        {
            tmp->rotation--;
            tmp = tmp->next;
        }
        else
        {
            tmp->cb_func(tmp->user_data);
            if(tmp == slots[cur_slot])
            {
                std::cout << "delete handle in cur_slot" << std::endl;
                slots[cur_slot] = tmp->next;
                delete tmp;
                if(slots[cur_slot])
                    slots[cur_slot]->prev = nullptr;
                tmp = slots[cur_slot];
            }
            else
            {
                tmp->prev->next = tmp->next;
                if(tmp->next)
                    tmp->next->prev = tmp->prev;
                tw_timer *tmp2 = tmp->next;
                delete tmp;
                tmp = tmp2;
            }
        }
    }

    cur_slot = ++cur_slot % N;
    
    return;
}

#endif


服务器测试程序:(epoll)

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <assert.h>
#include <signal.h>
#include <string.h>
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include "TIME_WHEEL_TIMER.hpp"

#define FD_LIMIT 65535
#define MAX_EVENT_NUMBER   1024
#define TIMESLOT  1 
static int pipefd[2];
static time_wheel tw;
static int epollfd = 0;


//设置非阻塞
int setnonblocking(int fd)
{
    int old_option = fcntl(fd, F_GETFL);
    int new_option = old_option | O_NONBLOCK;
    fcntl(fd, F_SETFL, new_option);
    return old_option;
}

void addfd(int epollfd, int fd)
{
    struct epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN | EPOLLET;   //ET模式
    epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);   //添加到事件列表
    setnonblocking(fd);   //设置非阻塞
    
    return ;
}

void sig_handler(int sig)
{
    int save_errno = errno;
    int msg = sig;
    send(pipefd[1], (char*)&msg, 1, 0);
    errno = save_errno;
}

//信号处理
void addsig(int sig)
{
    struct sigaction sa;
    (struct sigaction *)memset(&sa, '\0', sizeof(struct sigaction));
    sa.sa_handler = sig_handler;
    sa.sa_flags |= SA_RESTART;
    sigfillset(&sa.sa_mask);
    assert(sigaction(sig, &sa, NULL) != -1);

    return ;
}

//
void timer_handler()
{
    tw.tick();

    alarm(TIMESLOT);
}

//回调函数
void cb_func(client_data *user_data)
{
    epoll_ctl(epollfd, EPOLL_CTL_DEL, user_data->sockfd, 0);
    assert(user_data);
    close(user_data->sockfd);
    std::cout << "close fd " << user_data->sockfd << std::endl;
}

int main(int argc, char **argv)
{
    if(argc <= 2)   //使用IP port
    {
        std::cout << "usage:" << argv[0] << " ipaddr port" << std::endl;
        return -1;
    }

    struct sockaddr_in address;
    bzero(&address, sizeof(struct sockaddr_in));
    address.sin_family = AF_INET;
    inet_pton(AF_INET, argv[1], &address.sin_addr);
    address.sin_port = htons(atoi(argv[2]));


    //socket
    int listenfd = socket(PF_INET, SOCK_STREAM, 0);
    assert(listenfd != -1);

    int reuse = 1;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
    //bind
    int ret = bind(listenfd, (struct sockaddr*)&address, sizeof(struct sockaddr_in));
    assert(ret != -1);

    //listen
    ret = listen(listenfd, 5);
    assert(ret != -1);

    //事件集合
    struct epoll_event events[MAX_EVENT_NUMBER];
    //列表套接字
    int epollfd = epoll_create(5);
    assert(epollfd != -1);
    
    addfd(epollfd, listenfd);  //添加到事件列表

    //双向通信
    ret = socketpair(PF_UNIX, SOCK_STREAM, 0, pipefd);
    assert(ret != -1);
    setnonblocking(pipefd[1]);
    addfd(epollfd, pipefd[0]);


    
    //设置信号处理函数
    addsig(SIGALRM);
    addsig(SIGTERM);
    bool stop_server = false;

    client_data *users = new client_data[FD_LIMIT];
    bool timeout = false;
    alarm(TIMESLOT);    //定时

    while(!stop_server)
    {
        int num = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);
        if(num < 0 && errno != EINTR)
        {
            std::cout << "epoll failure" << std::endl;
            break;
        }

        for(int i = 0; i < num; ++i)
        {
            int sockfd = events[i].data.fd;
            if(sockfd == listenfd)   //新的连接请求
            {
                struct sockaddr_in client;
                socklen_t len = sizeof(struct sockaddr_in);
                int connfd = accept(listenfd, (struct sockaddr*)&client, &len);
                assert(connfd != -1);

                addfd(epollfd, connfd);
                users[connfd].address = client;
                users[connfd].sockfd = connfd;

                //创建定时器
                tw_timer *timer = tw.add_timer(5);
                timer->user_data = &users[connfd];
                timer->cb_func = cb_func;      //回调函数
                users[connfd].timer = timer;
                break;
            
            }
            else if(sockfd == pipefd[0] && events[i].events & EPOLLIN)  //处理信号
            {
                int sig;
                char signals[1024];
                ret = recv(pipefd[0], signals, sizeof(signals), 0);
                assert(ret != -1);

                if(ret == 0)
                    continue;
                else
                {
                    for(int i = 0; i < ret; ++i)
                        switch(signals[i])
                        {
                            case SIGALRM:
                                timeout = true; //有定时任务需要处理
                                break;
                            case SIGTERM:
                                stop_server = true;
                        }
                }
            
            }
            else if(events[i].events & EPOLLIN)  //处理客户连接上接收的数据
            {
                memset(users[sockfd].buf, '\0', BUFFER_SIZE);
                ret = recv(sockfd, users[sockfd].buf, BUFFER_SIZE - 1, 0);
                std::cout << "get " << ret << " bytes os client data " << users[sockfd].buf << " from " << sockfd << std::endl;

               tw_timer *timer = users[sockfd].timer;
                if(ret < 0)          //删除定时器  关闭描述符
                {
                    if(errno != EAGAIN)
                    {
                        cb_func(&users[sockfd]);
                        if(timer)
                            tw.del_timer(timer);
                    }
                }
                else if(ret == 0)
                {
                    cb_func(&users[sockfd]);
                    if(timer)
                        tw.del_timer(timer);
                }
                else
                {
                    //调整定时器 延迟关闭
                    if(timer)
                    {
                        time_t cur = time(NULL);
                        timer->rotation = 1;
                        std::cout << "adjust timer once" << std::endl;
                    }
                    
                }
            
            }
            else
            {
                ;
            }
        }
        if(timeout)
        {
            timer_handler();
            timeout = false;
        }

    }

    close(listenfd);
    close(pipefd[1]);
    close(pipefd[0]);
    delete []users;

    return 0;
}

客户端可使用telnet测试。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值