linux网络编程二十二:高性能定时器之时间轮

之前我们提到,基于排序链表的定时器存在一个问题:添加定时器的效率偏低。下面我们讨论的时间轮解决了这个问题。

如图,这是一种简单的时间轮:

linux网络编程二十二:高性能定时器之时间轮_第1张图片


轮中的实线指针指向轮子上的一个槽(slot),它以恒定的速度顺时针转动,每转动一步就指向下一个槽,每次转动称为一个滴答(tick)。

一个滴答的时间称为是间轮的槽间隔si(slot interval),它实际上就是心跳时间。

该轮共有N个槽,因此它运转一周的时间是N×si 。每个槽指向一条定时器链表,每条链表上的定时器具有相同的特性:它们的定时时间相差N×si的整数倍。时间轮正是利用这个关系将定时器散列到不同的链表中。

假如现在指针指向槽cs,我们要添加一个定时时间为ti的定时器,则该定时器将被插入ts(timer slot)对应的链表中:ts = (cs + (ti / si)) %N

基于排序链表的定时器使用唯一的一条链表来管理所有定时器,所以插入操作的效率随着定时器数目的增多而降低。而时间轮使用哈希表的思想,将定时器散列到不同的链表上。

这样每条链表上的定时数目都将明显减少,插入操作的效率受定时器数目的影响较少。

很显然,对时间轮而言,要提高定时精度,就要使si值足够小;要提高执行效率,则要求N值足够大。


下面我们实现一个简单的时间轮,只实现一个轮子。复杂的时间轮可以有多个,不同的轮子有不同粒度。相邻的两个轮子,精度高的转一圈,精度低的仅往前移动一槽,就像水表一样。

对时间轮而言,添加一个定时器的时间复杂度是O(1), 删除一个定时器的时间复杂度是O(1),执行一个定时器的时间复杂度是O(n)。

但实际上执行一个定时器的效率要比O(n)高得多,因为时间轮将所有的定时器散列到了不同的链表上,时间轮的槽越多,等于散列表的入口越多,从而每条链表上的定时器数量越少。此外,我们的代码仅用了一个时间轮,当使用多个轮子来实现时,它的时间复杂度将接近O(1)。

关于linux下定时器的实现方式对比,大家可以看看这篇文章,写的挺不错的:http://www.ibm.com/developerworks/cn/linux/l-cn-timers/


1. 代码:

//tw_timer.h
#ifndef __TIME_WHEEL__
#define __TIME_WHEEL__

#include 
#include 
#include 

#define BUFFER_SIZE 64

class tw_timer;

//客户端数据
struct client_data
{
    sockaddr_in address;
    int sockfd;
    char buf[BUFFER_SIZE];
    tw_timer *timer;
};

//定时器
class tw_timer
{
public:
    tw_timer(int rot, int ts)
        :next(NULL), prev(NULL), rotation(rot), time_slot(ts) {}

public:
    int rotation;                       //定时器在时间轮上转多少圈后生效
    int time_slot;                      //定时器属于时间轮上的哪个槽

    void (*cb_func)(client_data*);      //定时器的回调函数
    client_data *user_data;             //客户端数据
    
    tw_timer *prev;                     //指向上一个定时器
    tw_timer *next;                     //指向下一个定时器
};

//时间轮
class time_wheel
{
public:
    time_wheel();
    ~time_wheel();

    tw_timer* add_timer(int timeout);   //根据定时值创建定时器,并插入合适的位置
    void del_timer(tw_timer *timer);    //删除目标定时器
    void tick();                //时间到后调用该函数,时间轮向前滚动一个槽间隔
    
private:
    static const int N = 60;    //时间轮上槽的数目
    static const int TI = 1;    //槽间隔时间,即每1秒时间轮转动一次

    int cur_slot;               //时间轮的当前槽
    tw_timer *slots[N];         //时间轮的槽,其中每个元素指向一个定时器链表

};


#endif



//tw_timer.cpp

#include "tw_timer.h"

time_wheel::time_wheel():cur_slot(0)
{
    //初始化每个槽的头结点
    for (int i = 0; i < N; ++i)
        slots[i] = NULL;
}

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 NULL;

    int ticks = 0;              //待插入定时器所需要总ticks
    if (timeout < TI)
        ticks = 1;
    else
        ticks = timeout / TI;

    int rotation = ticks / N;   //计算待插入的定时器在时间轮上要转动多少圈后触发
    int ts = (cur_slot + ticks) % N; //计算待持入定时器应该被插入的位置
    //int ts = (cur_slot + (ticks %N)) % N;

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

    //如果槽为空,则它新定时器插入,并设置为该槽的头节点
    if (!slots[ts]) {
        printf("add timer, rotation is %d, ts is %d, cur_slot is %d\n",
                rotation, ts, cur_slot);
        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 = NULL;

        delete timer;
    }
    else {
        timer->prev->next = timer->next;
        if (timer->next)
            timer->next->prev = timer->prev;

        delete timer;
    }
}

void time_wheel::tick()
{
   //取得时间轮上当前槽的头结点
   tw_timer *tmp = slots[cur_slot];
   printf("current slot is %d\n", cur_slot);

   while (tmp) {
       printf("tick the timer once\n");

       //如果定时器的rotation值大于0,则未到时,不处理
       if (tmp->rotation > 0) {
           tmp->rotation--;
           tmp = tmp->next;
       }
       else {
           tmp->cb_func(tmp->user_data);
           if (tmp == slots[cur_slot]) {
               printf("delete header in cur_slot\n");
               slots[cur_slot] = tmp->next;

               delete tmp;

               if (slots[cur_slot])
                   slots[cur_slot]->prev = NULL;

               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;
}

    


//nonactive_conn.cpp
//关闭非活动连接
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include "tw_timer.h"

#define FD_LIMIT 65535
#define MAX_EVENT_NUMBER 1024
#define TIMESLOT 100

static int pipefd[2];
static time_wheel client_conn_time_wheel;
static int epollfd = 0;

int setnonblocking(int fd);             //设置非阻塞
int addfd(int epollfd, int fd);         //添加描述符事件

void sig_handler(int sig);              //信号处理函数
void addsig(int sig);                   //添加信号处理函数

void timer_handler();                   //定时器任务
void cb_func(client_data *user_data);   //定时器回调函数


int main(int argc, char *argv[])
{
    if (argc != 2) {
        fprintf(stderr, "Usage: %s port\n", argv[0]);
        return 1;
    }

    int port = atoi(argv[1]);

    int ret = 0;
    int error;

    struct sockaddr_in address;
    bzero(&address, sizeof(address));
    address.sin_family = AF_INET;
    address.sin_port = htons(port);
    address.sin_addr.s_addr = htonl(INADDR_ANY);

    int sockfd = socket(PF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        fprintf(stderr, "create socket failed\n");
        return 1;
    }

    int reuse = 1;
    ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
    if (ret == -1) {
        error = errno;
        while ((close(sockfd) == -1) && (errno == EINTR));
        errno = error;
        return 1;
    }

    if ( (bind(sockfd, (struct sockaddr*)&address, sizeof(address)) == -1) || 
        (listen(sockfd, 5) == -1)) {
        error = errno;
        while ((close(sockfd) == -1) && (errno ==  EINTR));
        errno = error;
        return 1;
    }

    int epollfd = epoll_create(5);
    if (epollfd == -1) {
        error = errno;
        while ((close(sockfd)) && (errno == EINTR));
        errno = error;
        return 1;
    }

	ret = socketpair(PF_UNIX, SOCK_STREAM, 0, pipefd);
	if (ret == -1) {
		error = errno;
		while ((close(sockfd) == -1) && (errno == EINTR));
		errno = error;
		return 1;
	}

    epoll_event events[MAX_EVENT_NUMBER];
    
    setnonblocking(pipefd[1]);
    addfd(epollfd, pipefd[0]);
	addfd(epollfd, sockfd);

    //添加信号处理
    addsig(SIGALRM);
    addsig(SIGTERM);

    bool stop_server = false;

    client_data *users = new client_data[FD_LIMIT];
    bool timeout = false;

    alarm(1);

    printf("server start...\n");

    while (!stop_server) {
        int number = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);
        if (number < 0 && errno != EINTR) {
            fprintf(stderr, "epoll_wait failed\n");
            break;
        }

        for (int i = 0; i < number; i++) {
            int listenfd = events[i].data.fd;
            
            if (listenfd == sockfd) {
                struct sockaddr_in client_address;
                socklen_t client_addrlength = sizeof(client_address);

                int connfd;

                while ( ((connfd = accept(listenfd, (struct sockaddr*)&client_address, &client_addrlength)) == -1) &&
                        (errno == EINTR));
		
                addfd(epollfd, connfd);

                users[connfd].address = client_address;
                users[connfd].sockfd = connfd;
                
                tw_timer *timer = NULL;
                timer = client_conn_time_wheel.add_timer(TIMESLOT);
                if (timer) {
                    timer->user_data = &users[connfd];
                    timer->cb_func = cb_func;
					users[connfd].timer = timer;
			  fprintf(stderr, "client: %d add tw_timer successed\n", connfd);
                }
                else {
                    fprintf(stderr, "client: %d add tw_timer failed\n", connfd);
                }

            }
            else if((listenfd == pipefd[0]) && (events[i].events & EPOLLIN)) {
                int sig;
                char signals[1024];

		        ret = recv(pipefd[0], signals, sizeof(signals), 0);
                if (ret == -1) {
                    continue;
                }
                else 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;
                                break;
                            }
                        default:
                            break;
                        }
                    }
                }
            }
            else if (events[i].events & EPOLLIN) {
		        memset(users[listenfd].buf, '\0', BUFFER_SIZE);

                ret = recv(listenfd, users[listenfd].buf, BUFFER_SIZE-1, 0);
                printf("get %d bytes of client data: %s from %d\n",
                        ret, users[listenfd].buf, listenfd);

                tw_timer *timer = users[listenfd].timer;

                if (ret < 0) {
                    if (errno != EAGAIN) {
                        cb_func(&users[listenfd]);
                        if (timer)
                            client_conn_time_wheel.del_timer(timer);
                    }
                }
                else if (ret == 0) {
                    cb_func(&users[listenfd]);
                    if (timer)
                        client_conn_time_wheel.del_timer(timer);
                }
                else {
                    if (timer) {
                        printf("conntioned..to do adjuest timer\n");
                    }
                }
            }
            else {
            
            }
             
        }
        
        if (timeout) {
	        timer_handler();
            timeout = false;
        }

    }
    
    close(sockfd);
    close(pipefd[1]);
    close(pipefd[0]);
    delete[] users;


    return 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;
}

int addfd(int epollfd, int fd)
{
    epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN | EPOLLET;
    epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
    setnonblocking(fd);
}

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

void addsig(int sig)
{
    struct sigaction sa;
    memset(&sa, '\0', sizeof(sa));
    sa.sa_handler = sig_handler;
    sa.sa_flags |= SA_RESTART;

    sigfillset(&sa.sa_mask);
    
    assert(sigaction(sig, &sa, NULL) != -1);
}

void timer_handler()
{
    client_conn_time_wheel.tick();
    alarm(1);
}

void cb_func(client_data *user_data)
{
    epoll_ctl(epollfd, EPOLL_CTL_DEL, user_data->sockfd, 0);
    assert(user_data);
    close(user_data->sockfd);
    printf("close fd %d\n", user_data->sockfd);
}






参考:《linux高性能服务器编程》




你可能感兴趣的:(linux网络编程)