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

前面我们讨论的定时方案都是以固定的频率调用心跳函数tick,并在其中依次检测到期的定时器,然后执行到期定时器上的回调函数。

设计定时器的另外一种思路是:将所有定时器中超时时间最小的一个作为心跳间隔。这样,一旦心跳函数tick被调用,超时时间最小的定时器必然到期,我们就可以在tick函数中处理该定时器。然后再次从剩余的定时器中找出超时时间最小的一个,并将这段最小时间设置为下一次心跳间隔。如此反复,就实现了较为精确的定时。

最小堆很适合处理这种定时方案,最小堆是指每个节点的值都小于或等于其子节点的值的完全二叉树。

下图所示了一个具有6个元素的最小堆:
linux网络编程二十三:高性能定时器之时间堆_第1张图片

树的基本操作是插入节点和删除节点。对最小堆而言,它们都很简单。为了将一个元素X插入最小堆,我们可以在树的下一个空闲位置创建一个空穴。如果X可以放在空穴中而不被破坏堆的序,则插入完成。否则就执行上虑操作,即交换空穴和它的父节点上的元素。不断执行上述过程,直到X可以被放入空穴,则插入操作完成。

最小堆的删除操作是指删除其根节点上的元素,并且不破坏堆序性质。执行删除操作时,我们需要先在根节点处创建一个空穴;由于堆现在少了一个元素,因此我们可以把堆的最后一个元素X移动到该堆的某个地方。如果X可以被放入空穴,则删除操作完成;否则就执行下虑操作,即交换空穴和它的两个子节点中的较小者。不断进行上述操作,直到X可以被放入空穴,则删除操作完成。

由于最小堆是一种完全二叉树,所以我们可以用数组来组织其中的元素。

下图所示的是最小堆的数组表示:


对于数组中的任意一个位置i上的元素,其左子节点在位置2i+1上,其右子节点在2i+2上,其父节点则在[(i-1)/2](i>0)上。与用链表来表示堆相比,用数组表示堆不仅节省空间,而且更容易实现堆的插入、删除操作。

假设我们已经有一个包含N个元素的数组,现在要把它初始化为一个最小堆。那么最简单的方法是:初始化一个空堆,然后将数组中的每个元素插入该堆中。不过这样做的效率偏低。实际上,我们只需要对数组中的第[(N-1)/2]到0个元素执行一虑操作,即可确保该数组构成一个最小堆。

这是因为对包含N个元素的完全二叉树而言,它具有[(N-1)/2]个非叶子节点,这些非叶子节点正是该完全二叉树的第0个到第[(N-1)/2]个节点。
我们只要确保这些非叶子节点构成的子节点都具有堆序性质,整个树就具有堆序性质。

对时间堆而言,添加一个定时器的时间复杂度是O(lgn),删除一个定时器的时间复杂度是O(1),执行一个定时器的时间复杂度是O(1),因此,时间堆的效率是很高的。

1. 下面我们用最小堆实现一个定时器,其中最小堆用数组来表示。

//time_heap.h
#ifndef __TIME_HEAD__
#define __TIME_HEAD__

#include 
#include 
#include 


#define BUFFER_SIZE 64

class heap_timer;

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

//定时器
class heap_timer
{
public:
    heap_timer(int delay)
    {
        expire = time(NULL) + delay;
    }

public:
    time_t expire;                  //定时器生效的绝对时间
    void (*cb_func)(client_data*);  //定时器的回调函数
    client_data *user_data;         //客户端数据
};

//时间堆
class time_heap
{
public:
    //构造之一:初始化一个大小为cap的空堆
    time_heap(int cap);

    //构造之二:用已用数组来初始化堆
    time_heap(heap_timer **init_array, int size, int capacity);
    
    //销毁时间堆
    ~time_heap();

    //添加定时器timer
    int add_timer(heap_timer *timer);
    
    //删除定时器timer
    void del_timer(heap_timer *timer);

    //获得堆顶部的定时器
    heap_timer* top() const;

    //删除堆顶部的定时器
    void pop_timer();

    //心跳函数
    void tick();

    //堆是否为空
    bool empty()const { return cur_size == 0; }

private:
    //最小堆的下虑操作,
    //确保堆数组中认第hole个节点作为根的子树拥有最小堆性质
    void percolate_down(int hole);
    
    //将堆数组容量扩大1倍
    void resize();
private:
    heap_timer **array;     //堆数组
    int capacity;           //堆数组的空量
    int cur_size;           //堆数组当前包含元素个数

};

#endif



//time_heap.cpp
#include "time_heap.h"


time_heap::time_heap(int cap)
    :capacity(cap), cur_size(0)
{
    //创建堆数组
    array = new heap_timer*[capacity];
    if (!array) {
        fprintf(stderr, "init heap_timer failed.\n");
        return;
    }

    for (int i = 0; i < capacity; ++i)
        array[i] = NULL;
}

time_heap::time_heap(heap_timer **init_array, int size, int capacity)
    :capacity(capacity), cur_size(size)
{
    if (capacity < size) {
        fprintf(stderr, "init heap_timer 1 for init_array failed.\n");
        return;
    }

    //创建堆数组
    array = new heap_timer*[capacity];
    if (!array) {
        fprintf(stderr, "init heap_timer 2 failed.\n");
        return;
    }

    for (int i = 0; i < capacity; ++i)
        array[i] = NULL;

    if (size != 0) {
        //初始化堆数组
        for (int i = 0; i < size; i++)
            array[i] = init_array[i];

        //对数组中第 (cur_size-1)/2 ~ 0 个元素执行下虑操作
        for (int i = (cur_size-1)/2; i >=0; --i)
            percolate_down(i);
    }
}

time_heap::~time_heap()
{
    for (int i = 0; i < cur_size; ++i)
        delete array[i];

    delete[] array;
}

int time_heap::add_timer(heap_timer *timer)
{
   if (!timer)
       return -1;

   //如果堆数组不够大,将其扩大1倍
   if (cur_size >= capacity)
       resize();

   //新插入一个元素,当前堆大小加1, hole是新建空穴的位置
   int hole = cur_size++;
   int parent = 0;

   //对从空穴到根节点的路径上的所有节点执行上虑操作
   for (; hole > 0; hole = parent) {
       parent = (hole -1) /2;

       if (array[parent]->expire <= timer->expire)
           break;

       array[hole] = array[parent];
   }

   array[hole] = timer;

   return 0;
}

void time_heap::del_timer(heap_timer *timer)
{
    if (!timer)
        return;

    //仅将定时器的回调函数置为空,即所谓的延迟销毁。
    //这将节省真正删除该定时器的开销,但这样做容易使堆数组膨胀
    timer->cb_func = NULL;
}

heap_timer* time_heap::top() const
{
    if (empty())
        return NULL;

    return array[0];
}

void time_heap::pop_timer()
{
    if (empty())
        return;

    if (array[0]) {
        delete array[0];

        //将原来的堆顶元素替换为堆数组中最后一个元素
        array[0] = array[--cur_size];

        //对新的堆顶元素执行下虑操作
        percolate_down(0);
    }
}

void time_heap::tick()
{
    heap_timer *tmp = array[0];
    time_t cur = time(NULL);

    //循环处理到期定时器
    while (!empty()) {
        if (!tmp)
            break;

        //如果堆顶定时期没到期,则退出循环
        if (tmp->expire > cur)
            break;

        //否则就执行堆顶定时器中的回调函数
        if (array[0]->cb_func)
            array[0]->cb_func(array[0]->user_data);

        //删除堆元素,同时生成新的堆顶定时器
        pop_timer();
        
        tmp = array[0];
    }
}

void time_heap::percolate_down(int hole)
{
   heap_timer *tmp = array[hole];
   int child = 0;

   for (; ((hole*2)+1) <= (cur_size-1); hole = child) {
       child = hole * 2 + 1;

       if (child < (cur_size-1) &&
               array[child+1]->expire < array[child]->expire) {
           ++child;
       }

       if (array[child]->expire < tmp->expire)
           array[hole] = array[child];
       else
           break;
   }

   array[hole] = tmp;
}

void time_heap::resize()
{
    heap_timer **tmp = new heap_timer*[2 * capacity];

    for (int i = 0; i < (2*capacity); ++i)
        tmp[i] = NULL;

    if (!tmp) {
        fprintf(stderr, "resize() failed.\n");
        return;
    }

    capacity = 2 * capacity;

    for (int i = 0; i < cur_size; ++i)
        tmp[i] = array[i];

    delete[] array;
    array = tmp;
}




//time_heap_main.cpp
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include "time_heap.h"

#define FD_LIMIT 65535
#define MAX_EVENT_NUMBER 1024
#define TIMESLOT 10 

static int pipefd[2];
static time_heap client_time_heap(1024);
static int epollfd = 0;

static int is_sig_alarm = 0;

int do_error(int fd, int *error);       //处理错误

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)
        return do_error(sockfd, &error);

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

    epollfd = epoll_create(5);
    if (epollfd == -1)
        return do_error(sockfd, &error);

    ret = socketpair(PF_UNIX, SOCK_STREAM, 0, pipefd);
    if (ret == -1)
        return do_error(sockfd, &error);

    setnonblocking(pipefd[1]);

    addfd(epollfd, pipefd[0]);
    addfd(epollfd, sockfd);
    
    addsig(SIGALRM);
    addsig(SIGTERM);
    addsig(SIGPIPE);

    bool stop_server = false;
    bool timeout = false;

    epoll_event events[MAX_EVENT_NUMBER];
    client_data *users = new client_data[FD_LIMIT];

    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;
                
                heap_timer *timer = new heap_timer(TIMESLOT);
                if (timer) {
                    timer->cb_func = cb_func;
                    timer->user_data = &users[connfd];
                    
                    ret = client_time_heap.add_timer(timer);
                    if (ret == -1) {
                        fprintf(stderr, "client:%d add heap_timer failed.\n", connfd);
                    }
                    else {
                        if (is_sig_alarm == 0) {
                            alarm(TIMESLOT);
                            is_sig_alarm = 1;
                        }
                        
                        users[connfd].timer = timer;
                        printf("client:%d add heap_timer successed.\n", connfd);
                    }
                }
                else {
                    fprintf(stderr, "client:%d add heap_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);

                heap_timer *timer = users[listenfd].timer;
                if (!timer) {
                    fprintf(stderr, "timer of client %d is wrong.\n",
                            listenfd);
                }
                else if (ret < 0) {
                    if (errno != EAGAIN) {
                        cb_func(&users[listenfd]);                        
                        client_time_heap.del_timer(timer);
                        client_time_heap.pop_timer();
                    }
                }
                else if (ret == 0) {
                    cb_func(&users[listenfd]);
                    client_time_heap.del_timer(timer);
                    client_time_heap.pop_timer();
                }
                else {
                    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 do_error(int fd, int *error)
{
    fprintf(stderr, "error: %s\n", strerror(errno));
    *error = errno;
    while ((close(fd) == -1) && (errno == EINTR));
    errno = *error;
    return 1;
}

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_time_heap.tick();

    heap_timer *tmp = NULL;
    if ((is_sig_alarm == 0) && (tmp = client_time_heap.top())) {
        time_t delay = tmp->expire - time(NULL);
        if (delay <= 0)
            delay = 1;

        alarm(delay);
        is_sig_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);
    
    is_sig_alarm = 0;

    printf("close fd %d\n", user_data->sockfd);
}





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




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