高性能定时器2——红黑树实现

本文详细介绍了如何使用红黑树作为定时器容器,有效管理服务器的定时事件,确保在预定时间触发且不影响核心逻辑。通过R-BTree的自平衡特性,提升服务器性能的关键技术。

在网络程序中我们通常要处理三种事件,网络I/O事件、信号以及定时事件,我们可以使用I/O复用系统调用(select、poll、epoll)将这三类事件进行统一处理。我们通常使用定时器来检测一个客户端的活动状态,服务器程序通常管理着众多定时事件,因此有效地组织这些定时事件,使之能在预期的时间点被触发且不影响服务器的主要逻辑,对于服务器的性能有着至关重要的影响。为此我们需要将每个定时事件分别封装为定时器,并使用某种容器类数据结构,比如:链表、排序链表、最小堆、红黑树以及时间轮等,将所有定时器串联起来,以实现对定时事件的统一管理。此处所说的定时器,确切的说应该是定时容器,定时器容器是容器类数据结构;定时器则是容器内容纳的一个个对象,它是对定时事件的封装,定时容器是用来管理定时器的。

在本文中将主要介绍使用红黑树来实现的定时容器。

1、R-B Tree简介

​ R-B Tree,是Red Black Tree的缩写,也叫做红黑树。红黑树是一种带有颜色属性(红色或黑色)的自平衡的二叉查找树。它查找、插入和删除的时间复杂度为log(N),N为树中元素的数目。

红黑树的特征:

​ 1)节点是红色或黑色;

​ 2)根节点是黑色;

​ 3)所有叶子节点都是黑色(叶子节点是NIL节点,也叫“哨兵”);

​ 4)每个红色节点的两个子节点都是黑色(每个叶子节点到根节点的所有路径上不能有两个连续的红色节点);

​ 5)从任一节点到其每个叶子节点的所有简单路径都包含相同数目的黑色节点。

红黑树会通过旋转以及变色来保持树的平衡。

如下图为一棵红黑树:

​ 最底部的黑色节点为哨兵节点。

在这里插入图片描述

在这只对红黑树做一个简单介绍,红黑树的实现较为复杂,有对红黑树的实现感兴趣的,可以自己研究,nginx的红黑树很值得一读,nginx红黑树的代码在src/core/中,为ngx_rbtree.h和ngx_rbtree.c。红黑树使用还是很广泛的,比如c++ STL中的map以及set底层的实现使用的都是红黑树。

2、Nginx红黑树相关接口介绍

nginx红黑树中节点的定义如下:

typedef struct ngx_rbtree_node_s  ngx_rbtree_node_t;

struct ngx_rbtree_node_s {
    ngx_rbtree_key_t       key;                 // 键值,红黑树排序的依据 uint类型
    ngx_rbtree_node_t     *left;                // 左儿子
    ngx_rbtree_node_t     *right;               // 右儿子
    ngx_rbtree_node_t     *parent;              // 父节点
    u_char                 color;               // 节点的颜色:0表示黑色,1表示红色
    u_char                 data;                // 节点数据
};

nginx红黑树中的节点数据只有一个字节,如果我们要插入自己的数据,就需要在自定义结构体或类中添加一个ngx_rbtree_node_t类型的字段,将该字段作为结构体或类的第一个字段,便于类型的强制转换。例如:

struct my_rbtree_node
{
    ngx_rbtree_node_t node;    // 红黑树节点
   	UserData data;             // 用户自己的数据
}

ngx_rbtree_t结构为承载红黑树的结构体,其定义如下:

struct ngx_rbtree_s {
    ngx_rbtree_node_t     *root;                // 根节点,根节点也是数据元素
    ngx_rbtree_node_t     *sentinel;            // 哨兵节点
    ngx_rbtree_insert_pt   insert;              // 表示红黑树添加元素的函数指针
};

最后一个insert字段是一个函数指针,nginx的代码中已经实现了两种红黑树插入的方法,分别为ngx_rbtree_insert_value和ngx_rbtree_insert_timer_value;

使用步骤:

​ 1)初始化红黑树,使用ngx_rbtree_init来初始红黑树

#define ngx_rbtree_init(tree, s, i)  \   // tree为ngx_rbtree_t类型的指针,s为哨兵节点指针,i为
    ngx_rbtree_sentinel_init(s);     \   // 插入节点的回调函数,可以使用ngx_rbtree_insert_value
    (tree)->root = s;                \   // 和ngx_rbtree_insert_timer_value,也可以自定义
    (tree)->sentinel = s;            \
    (tree)->insert = i

​ 2) 执行插入、删除等操作

// 向红黑树中添加节点
void ngx_rbtree_insert(ngx_rbtree_t *tree, ngx_rbtree_node_t *node);

// 从红黑树中删除节点
void ngx_rbtree_delete(ngx_rbtree_t *tree, ngx_rbtree_node_t *node);

3、红黑树定时容器的实现

​ 定时器的实现可以使用多种数据结构,比如:最小堆,libevent中的定时器使用的就是最小堆;红黑树,nginx的定时器使用红黑树来实现;以及时间轮等。下面介绍的定时容器的实现使用nginx中的红黑树实现代码。

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

红黑树定时容器的几个接口介绍:

​ 1) tick :在tick函数中循环查找超时值最小的定时器,并调用其回调函数,直到找到的定时器的没有超时,就结束循环。

​ 2)addTimer::向容器中添加一个定时器,并返回定时器的指针。

​ 3)delTimer::根据传入的定时器指针删除容器中的一个定时器,并且销毁资源。

​ 4)resetTimer: 重置一个定时器。

​ 5)getMinExpire:获取容器中超时值最小的绝对时间;

代码如下:

timer_common.cpp

#ifndef _LIB_SRC_TIMER_COMMON_H
#define _LIB_SRC_TIMER_COMMON_H

#include <stdio.h>
#include <sys/time.h>

// 获取时间戳 单位:毫秒
time_t getMSec()
{
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return tv.tv_sec * 1000 + tv.tv_usec / 1000;
}

// 定时器数据结构的定义
template <typename _User_Data>
class Timer
{
public:
    Timer() : _user_data(nullptr), _cb_func(nullptr) {};
    Timer(int msec) : _user_data(nullptr), _cb_func(nullptr)
    {
        this->_expire = getMSec() + msec;
    }

    ~Timer()
    {

    }

    void setTimeout(time_t timeout)
    {
        this->_expire = getMSec() + timeout;
    }

    time_t getExpire()
    {
        return _expire;
    }

    void setUserData(_User_Data *userData)
    {
        this->_user_data = userData;
    }

    void handleTimeOut()
    {
        if(_cb_func)
        {
            _cb_func(_user_data);
        }
        
    }

    using TimeOutCbFunc = void (*)(_User_Data *);
    void setCallBack(TimeOutCbFunc callBack)
    {
        this->_cb_func = callBack;
    }

private:
    time_t _expire;                    // 定时器生效的绝对时间            
    _User_Data *_user_data;            // 用户数据
    TimeOutCbFunc _cb_func;           // 超时时的回调函数
};


// 虚基类ITimerContainer
template <typename _UData>
class ITimerContainer 
{
public:
    ITimerContainer() = default;
    virtual ~ITimerContainer() = default;

public:
    virtual void tick() = 0;               
    virtual Timer<_UData> *addTimer(time_t timeout) = 0;
    virtual void delTimer(Timer<_UData> *timer) = 0;
    virtual void resetTimer(Timer<_UData> *timer, time_t timeout) = 0;
    virtual int getMinExpire() = 0;
};

#endif

nginx红黑树代码:

rbtree.h:

#ifndef _NGX_RBTREE_H_INCLUDED_
#define _NGX_RBTREE_H_INCLUDED_

#include <stdint.h>      
#include <stdio.h>

#define ngx_inline inline

typedef intptr_t        ngx_int_t;
typedef uintptr_t       ngx_uint_t;
typedef intptr_t        ngx_flag_t;

typedef ngx_uint_t  ngx_rbtree_key_t;
typedef ngx_int_t   ngx_rbtree_key_int_t;

typedef unsigned char u_char;

typedef struct ngx_rbtree_node_s  ngx_rbtree_node_t;

// 红黑树的节点  
struct ngx_rbtree_node_s {
    ngx_rbtree_key_t       key;                 // 键值,红黑树排序的依据 uint类型
    ngx_rbtree_node_t     *left;                // 左儿子
    ngx_rbtree_node_t     *right;               // 右儿子
    ngx_rbtree_node_t     *parent;              // 父节点
    u_char                 color;               // 节点的颜色:0表示黑色,1表示红色
    u_char                 data;                // 节点数据
};


typedef struct ngx_rbtree_s  ngx_rbtree_t;

// 定义插入节点的函数指针
typedef void (*ngx_rbtree_insert_pt) (ngx_rbtree_node_t *root,
    ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);

// 红黑树结构
struct ngx_rbtree_s {
    ngx_rbtree_node_t     *root;                // 根节点,根节点也是数据元素
    ngx_rbtree_node_t     *sentinel;            // 哨兵节点
    ngx_rbtree_insert_pt   insert;              // 表示红黑树添加元素的函数指针,它决定在添加新节点时的行为究竟是替换还是新增
};


/* 
    初始化红黑树, tree为红黑树容器的指针, s为哨兵节点的指针, i为ngx_rbtree_insert_pt类型的节点添加方法
    Nginx为红黑树已经实现的三种添加节点的方法:
        ngx_rbtree_insert_value
        ngx_rbtree_insert_timer_value
        ngx_str_rbtree_insert_value
    在初始化红黑树时,需要先分配好保存红黑树的ngx_rbtree_t结构体,以及ngx_rbtree_node_t类型的哨兵节点,
    并选择或者自定义ngx_rbtree_insert_pt类型的节点添加函数。
*/
#define ngx_rbtree_init(tree, s, i)                                           \
    ngx_rbtree_sentinel_init(s);                                              \
    (tree)->root = s;                                                         \
    (tree)->sentinel = s;                                                     \
    (tree)->insert = i

// 向红黑树中添加节点,该方法会通过旋转红黑树以及改变节点颜色保持树的平衡
void ngx_rbtree_insert(ngx_rbtree_t *tree, ngx_rbtree_node_t *node);

// 从红黑树中删除节点,该方法会通过旋转红黑树以及改变节点颜色保持树的平衡
void ngx_rbtree_delete(ngx_rbtree_t *tree, ngx_rbtree_node_t *node);

void ngx_rbtree_insert_value(ngx_rbtree_node_t *root, ngx_rbtree_node_t *node,
    ngx_rbtree_node_t *sentinel);
void ngx_rbtree_insert_timer_value(ngx_rbtree_node_t *root,
    ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);

ngx_rbtree_node_t *ngx_rbtree_next(ngx_rbtree_t *tree,
    ngx_rbtree_node_t *node);

// 查找节点
ngx_rbtree_node_t *ngx_rbtree_find(ngx_rbtree_t *tree, ngx_rbtree_node_t *node, 
    ngx_rbtree_node_t *sentinel);

#define ngx_rbt_red(node)               ((node)->color = 1)
#define ngx_rbt_black(node)             ((node)->color = 0)
#define ngx_rbt_is_red(node)            ((node)->color)
#define ngx_rbt_is_black(node)          (!ngx_rbt_is_red(node))
#define ngx_rbt_copy_color(n1, n2)      (n1->color = n2->color)


/* a sentinel must be black */       /* 哨兵节点必须是黑色的 */
#define ngx_rbtree_sentinel_init(node)  ngx_rbt_black(node)     // 初始化哨兵节点,也就是将哨兵节点的颜色置为黑色


// 获取红黑树中key最小的节点
static ngx_inline ngx_rbtree_node_t *
ngx_rbtree_min(ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
{
    if(node == NULL || node == sentinel)
    {
        return NULL;
    }
    
    while (node->left != sentinel) {
        node = node->left;
    }

    return node;
}


/*
    也可以实现自己定义的结构体插入红黑树,只需要将ngx_rbtree_node_t类型的成员作为结构体的第一个成员即可,这样方便
    把自定义的结构体强制转换为ngx_rbtree_node_t类型。
    例如:
    typedef struct {
        ngx_rbtree_node_t;
        int data;
    } test_rbtree_node;
*/


/*
红黑树的特性:
    1)节点是红色或黑色;
    2)根节点是黑色;
    3)所有叶子节点都是黑色(叶子节点是NIL节点,也叫“哨兵”);
    4)每个红色节点的两个子节点都是黑色(每个叶子节点到根节点的所有路径上不能有两个连续的红色节点);
    5)从任一节点到其每个叶子节点的所有简单路径都包含相同数目的黑色节点。

*/

#endif /* _NGX_RBTREE_H_INCLUDED_ */

rbtree.cpp


/*
 * Copyright (C) Igor Sysoev
 * Copyright (C) Nginx, Inc.
 */


#include "rbtree.h"


/*
 * The red-black tree code is based on the algorithm described in
 * the "Introduction to Algorithms" by Cormen, Leiserson and Rivest.
 */


static ngx_inline void ngx_rbtree_left_rotate(ngx_rbtree_node_t **root,
    ngx_rbtree_node_t *sentinel, ngx_rbtree_node_t *node);
static ngx_inline void ngx_rbtree_right_rotate(ngx_rbtree_node_t **root,
    ngx_rbtree_node_t *sentinel, ngx_rbtree_node_t *node);


/*
    红黑树的插入:
        1、普通的二叉树的插入
        2、红黑树的平衡(旋转 + 变色)
*/  
void
ngx_rbtree_insert(ngx_rbtree_t *tree, ngx_rbtree_node_t *node)
{
    ngx_rbtree_node_t  **root, *temp, *sentinel;

    /* a binary tree insert */

    root = &tree->root;
    sentinel = tree->sentinel;

    // 插入第一个节点,节点为黑色
    if (*root == sentinel) {
        node->parent = NULL;
        node->left = sentinel;
        node->right = sentinel;
        ngx_rbt_black(node);
        *root = node;

        return;
    }

    // 插入节点,按照普通的搜索二叉树的方式插入, 插入的节点的颜色是红色
    tree->insert(*root, node, sentinel);

    /* re-balance tree */   /* 重新调整红黑树 */

    while (node != *root && ngx_rbt_is_red(node->parent)) {
        // 插入节点的父节点在左侧
        if (node->parent == node->parent->parent->left) {     
            temp = node->parent->parent->right; 

            // 插入节点的叔叔节点是红色, 也就是存在叔叔节点
            if (ngx_rbt_is_red(temp)) {
                ngx_rbt_black(node->parent);
                ngx_rbt_black(temp);
                ngx_rbt_red(node->parent->parent);
                node = node->parent->parent;

 /*                                                                        gp                         gp            gp 
插入节点的叔叔节点是黑色,此时叔叔节点应该是哨兵节点,也就是没有真正的叔叔节点     /                           /             /             s
如果插入的节点与父节点在同一侧,只需旋转一次                                  p    ==>   p                p    ==>     s       ==>    / \
如果插入的节点与父节点不在同一侧,需要旋转两次:                 旋转一次:   /           / \     旋转两次:   \          /               p   gp
先将父节点旋转,再将爷爷节点旋转                                         s           s  gp                 s       p
            */                                                                       
            } else {
                if (node == node->parent->right) {
                    node = node->parent;
                    ngx_rbtree_left_rotate(root, sentinel, node);
                }

                ngx_rbt_black(node->parent);
                ngx_rbt_red(node->parent->parent);
                ngx_rbtree_right_rotate(root, sentinel, node->parent->parent);
            }

        // 插入节点的父节点在右侧
        } else {                                             
            temp = node->parent->parent->left;

            // 插入节点的叔叔节点是红色,也就是存在叔叔节点
            if (ngx_rbt_is_red(temp)) {
                ngx_rbt_black(node->parent);
                ngx_rbt_black(temp);
                ngx_rbt_red(node->parent->parent);
                node = node->parent->parent;

            /*                                                                         gp                    gp
                插入节点的叔叔节点是黑色,此时叔叔节点应该是哨兵节点,也就是没有真正的叔叔节点    \                     \
                如果插入的节点与父节点在同一侧,只需旋转一次                                    p                     p
                如果插入的节点与父节点不在同一侧,需要旋转两次                  旋转一次:        \     旋转两次:      /
                                                                                             s                 s                                                                                                
            */
            } else {
                if (node == node->parent->left) {
                    node = node->parent;
                    ngx_rbtree_right_rotate(root, sentinel, node);
                }

                ngx_rbt_black(node->parent);
                ngx_rbt_red(node->parent->parent);
                ngx_rbtree_left_rotate(root, sentinel, node->parent->parent);
            }
        }
    }

    ngx_rbt_black(*root);
}


void
ngx_rbtree_insert_value(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node,
    ngx_rbtree_node_t *sentinel)
{
    ngx_rbtree_node_t  **p;

    for ( ;; ) {

        p = (node->key < temp->key) ? &temp->left : &temp->right;

        if (*p == sentinel) {
            break;
        }

        temp = *p;
    }

    *p = node;
    node->parent = temp;
    node->left = sentinel;
    node->right = sentinel;
    ngx_rbt_red(node);
}


void
ngx_rbtree_insert_timer_value(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node,
    ngx_rbtree_node_t *sentinel)
{
    ngx_rbtree_node_t  **p;

    for ( ;; ) {

        /*
         * Timer values
         * 1) are spread in small range, usually several minutes,
         * 2) and overflow each 49 days, if milliseconds are stored in 32 bits.
         * The comparison takes into account that overflow.
         */

        /*  node->key < temp->key */

        p = ((ngx_rbtree_key_int_t) (node->key - temp->key) < 0)
            ? &temp->left : &temp->right;

        if (*p == sentinel) {
            break;
        }

        temp = *p;
    }

    *p = node;
    node->parent = temp;
    node->left = sentinel;
    node->right = sentinel;
    ngx_rbt_red(node);
}

/*
    红黑树的删除操作:
        1、二叉树删除:
            1). 删除叶子节点,直接删除
            2). 删除的节点只有一个子节点,那么用子节点替代
            3). 删除的节点有两个字节点,此时需要找到前驱节点或后继节点来替代,可以转换为1) 2)的情况
        2、调整红黑树
    红黑树的删除最终会转换为删除倒数第一层或倒数第二层
*/
void
ngx_rbtree_delete(ngx_rbtree_t *tree, ngx_rbtree_node_t *node)
{
    ngx_uint_t           red;
    ngx_rbtree_node_t  **root, *sentinel, *subst, *temp, *w;

    /* a binary tree delete */

    root = &tree->root;
    sentinel = tree->sentinel;

    if (node->left == sentinel) {
        temp = node->right;
        subst = node;

    } else if (node->right == sentinel) {
        temp = node->left;
        subst = node;

    } else {
        subst = ngx_rbtree_min(node->right, sentinel);
        temp = subst->right;
    }

    if (subst == *root) {
        *root = temp;
        ngx_rbt_black(temp);

        /* DEBUG stuff */
        node->left = NULL;
        node->right = NULL;
        node->parent = NULL;
        node->key = 0;

        return;
    }

    red = ngx_rbt_is_red(subst);

    if (subst == subst->parent->left) {
        subst->parent->left = temp;

    } else {
        subst->parent->right = temp;
    }

    // 要删除的节点只有左子节点或右子节点
    if (subst == node) {

        temp->parent = subst->parent;

    // 要删除的节点的左子节点和右子节点都存在
    } else {

        if (subst->parent == node) {
            temp->parent = subst;

        } else {
            temp->parent = subst->parent;
        }

        subst->left = node->left;
        subst->right = node->right;
        subst->parent = node->parent;
        ngx_rbt_copy_color(subst, node);

        if (node == *root) {
            *root = subst;

        } else {
            if (node == node->parent->left) {
                node->parent->left = subst;
            } else {
                node->parent->right = subst;
            }
        }

        if (subst->left != sentinel) {
            subst->left->parent = subst;
        }

        if (subst->right != sentinel) {
            subst->right->parent = subst;
        }
    }

    /* DEBUG stuff */
    node->left = NULL;
    node->right = NULL;
    node->parent = NULL;
    node->key = 0;

    if (red) {
        return;
    }

    /* a delete fixup */

    while (temp != *root && ngx_rbt_is_black(temp)) {

        if (temp == temp->parent->left) {
            w = temp->parent->right;

            if (ngx_rbt_is_red(w)) {
                ngx_rbt_black(w);
                ngx_rbt_red(temp->parent);
                ngx_rbtree_left_rotate(root, sentinel, temp->parent);
                w = temp->parent->right;
            }

            if (ngx_rbt_is_black(w->left) && ngx_rbt_is_black(w->right)) {
                ngx_rbt_red(w);
                temp = temp->parent;

            } else {
                if (ngx_rbt_is_black(w->right)) {
                    ngx_rbt_black(w->left);
                    ngx_rbt_red(w);
                    ngx_rbtree_right_rotate(root, sentinel, w);
                    w = temp->parent->right;
                }

                ngx_rbt_copy_color(w, temp->parent);
                ngx_rbt_black(temp->parent);
                ngx_rbt_black(w->right);
                ngx_rbtree_left_rotate(root, sentinel, temp->parent);
                temp = *root;
            }

        } else {
            w = temp->parent->left;

            if (ngx_rbt_is_red(w)) {
                ngx_rbt_black(w);
                ngx_rbt_red(temp->parent);
                ngx_rbtree_right_rotate(root, sentinel, temp->parent);
                w = temp->parent->left;
            }

            if (ngx_rbt_is_black(w->left) && ngx_rbt_is_black(w->right)) {
                ngx_rbt_red(w);
                temp = temp->parent;

            } else {
                if (ngx_rbt_is_black(w->left)) {
                    ngx_rbt_black(w->right);
                    ngx_rbt_red(w);
                    ngx_rbtree_left_rotate(root, sentinel, w);
                    w = temp->parent->left;
                }

                ngx_rbt_copy_color(w, temp->parent);
                ngx_rbt_black(temp->parent);
                ngx_rbt_black(w->left);
                ngx_rbtree_right_rotate(root, sentinel, temp->parent);
                temp = *root;
            }
        }
    }

    ngx_rbt_black(temp);
}


/*
    左旋:
        以某个节点作为旋转点,其右子节点变为旋转节点的父节点,右子节点的左子节点变为旋转节点的右子节点,左子节点保持不变
                4                                               6
               / \                                             / \
              3   6       4作为旋转节点,旋转后  ====>            4  7
                 / \                                          / \
                5   7                                        3   5
    左旋的时候要判断旋转节点有没有父节点:
        如果没有:它为根节点,需要将旋转后的根节点变为它的右子节点
        如 果 有:需要将它父节点的左节点或右节点置为它的右子节点,将右子节点的父节点置为它的父节点
                     p               p                                              p
                    /               /                                              /
                   4                4                     4        p              6
    情况1:        / \              / \          ===>     / \      /      ===>    / \           
                 3   6   ===>     3   5   6             3   5    6              4   7
                / \                      / \                    / \            / \    
               5   7                    5   7                  5   7          3   5

                p               p                                            p
                 \               \                                            \          
                  4               4                   4     p                  6
    情况2:        / \   ===>      / \           ===>  / \     \    ===>        / \             
                 3  6            3   5    6          3  5      6              4   7
                / \                      / \                  / \            / \              
               5   7                    5  7                 5   7          3  5

    情况3:旋转前4为根节点,旋转后7为根节点
*/

static ngx_inline void
ngx_rbtree_left_rotate(ngx_rbtree_node_t **root, ngx_rbtree_node_t *sentinel,
    ngx_rbtree_node_t *node)                                                                
{                                                                                          
    ngx_rbtree_node_t  *temp;                                                 
                                                                                    
    temp = node->right;                                                             
    node->right = temp->left;         // 将旋转点的右子节点的左子节点作为旋转点的右子节点         
                                                                                          
    if (temp->left != sentinel) {     // 如果旋转点的右子节点的左节节点存在,将它的父节点置为旋转点 
        temp->left->parent = node;                                                        
    }                                                                                             
                                                                                   
    temp->parent = node->parent;        // 将旋转点的右子节点的父节点置为它的父节点                     

                                                                             
    if (node == *root) {                // 如果旋转点是根节点,则将根节点置为旋转点的右子节点              
        *root = temp;                                                               
                                        
    } else if (node == node->parent->left) {  // 如果旋转点不是根节点,而且旋转点是左节点,将它的父节点的左子节点置为旋转点的右子节点
        node->parent->left = temp;
                                                                                   
    } else {
        node->parent->right = temp;         // 如果旋转点不是根节点,而且旋转点是右节点,将它的父节点的右子节点置为旋转点的右子节点
    }

    
    temp->left = node;                     // 将旋转点作为它的右子节点的左子节点
    node->parent = temp;                   // 将旋转点的右子节点作为它的父节点
}



/*
    右旋:
        以某个节点作为旋转点,其左子节点变为旋转点的父节点,左子节点的右节点变为旋转节点的左子节点,右子节点保持不变
                6                                                   4
               / \                                                 / \
              4   7       以6作为旋转节点,右旋后 ====>              3   6
             / \                                                      / \
            3   5                                                    5   7
*/
static ngx_inline void
ngx_rbtree_right_rotate(ngx_rbtree_node_t **root, ngx_rbtree_node_t *sentinel,
    ngx_rbtree_node_t *node)
{
    ngx_rbtree_node_t  *temp;

    temp = node->left;
    node->left = temp->right;

    if (temp->right != sentinel) {
        temp->right->parent = node;
    }

    temp->parent = node->parent;

    if (node == *root) {
        *root = temp;

    } else if (node == node->parent->right) {
        node->parent->right = temp;

    } else {
        node->parent->left = temp;
    }

    temp->right = node;
    node->parent = temp;
}


ngx_rbtree_node_t *
ngx_rbtree_next(ngx_rbtree_t *tree, ngx_rbtree_node_t *node)
{
    ngx_rbtree_node_t  *root, *sentinel, *parent;

    sentinel = tree->sentinel;

    if (node->right != sentinel) {
        return ngx_rbtree_min(node->right, sentinel);
    }

    root = tree->root;

    for ( ;; ) {
        parent = node->parent;

        if (node == root) {
            return NULL;
        }

        if (node == parent->left) {
            return parent;
        }

        node = parent;
    }
}

// 查找节点
ngx_rbtree_node_t *ngx_rbtree_find(ngx_rbtree_t *tree, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
{
    if(node == NULL)
    {
        return NULL;
    }

    ngx_rbtree_node_t *temp = tree->root;
    while(temp != sentinel)
    {
        if(node->key > temp->key) {
            temp = temp->right;

        } else if(node->key < temp->key) {
            temp = temp->left;

        } else {
            return temp;
        }
    }

    return NULL;
}

rbtree_timer.hpp

#include <sys/time.h>
#include <stack>
#include "rbtree.h"
#include "timer_common.hpp"

template <typename _User_Data>
struct timer_rbtree_node
{
    ngx_rbtree_node_t node;
    Timer<_User_Data> timer;
};



template <typename _UData>
class RBTreeTimerContainer : public ITimerContainer<_UData>
{
public:
    RBTreeTimerContainer();
    ~RBTreeTimerContainer();

public:
    void tick() override;               
    Timer<_UData> *addTimer(time_t timeout)  override;
    void delTimer(Timer<_UData> *timer)  override;
    void resetTimer(Timer<_UData> *timer, time_t timeout)  override;
    int getMinExpire() override;
    Timer<_UData> *top();
    void popTimer();

private:
    ngx_rbtree_t rbtree;               // 红黑树
    ngx_rbtree_node_t sentinel;        // 红黑树的哨兵节点

};

template <typename _UData>
RBTreeTimerContainer<_UData>::RBTreeTimerContainer()
{
    // 初始化红黑树
    ngx_rbtree_init(&this->rbtree, &this->sentinel, ngx_rbtree_insert_value);
}
    
template <typename _UData>
RBTreeTimerContainer<_UData>::~RBTreeTimerContainer()
{    
    // 遍历红黑树删除节点
	if (rbtree.root == &this->sentinel)
    {
        return;
    }		

	//树非空
	ngx_rbtree_node_t* p = rbtree.root;
	std::stack<ngx_rbtree_node_t*> s;

	while (!s.empty() || p != &this->sentinel)
	{
		if (p != &this->sentinel)
		{
			s.push(p);
			p = p->left;
		}
		else
		{
			p = s.top();
			s.pop();
			// 删除节点
            timer_rbtree_node<_UData> *node = reinterpret_cast< timer_rbtree_node<_UData>* >(p);
            if(node)
            {
                delete node;
            }

			p = p->right;
		}
	}
}

template <typename _UData>
void RBTreeTimerContainer<_UData>::tick()
{
    // 找到最小超时值的timer,然后判断是否超时,如果超时执行回调函数并删除timer。继续上述操作,直到没有超时的timer

    // 获取当前时间
    time_t cur_time = getMSec();
    // 如果红黑树中没有节点,直接返回
    if(rbtree.root == rbtree.sentinel)
    {
        return;
    }

    auto node = ngx_rbtree_min(this->rbtree.root, &this->sentinel);
    timer_rbtree_node<_UData> *timer = reinterpret_cast< timer_rbtree_node<_UData> *>(node);

    while(timer && timer->timer.getExpire() < cur_time)
    {
        // 执行回调函数
        timer->timer.handleTimeOut();
        
        // 删除timer
        popTimer();

        node = ngx_rbtree_min(this->rbtree.root, &this->sentinel);
        timer = reinterpret_cast< timer_rbtree_node<_UData> *>(node);
    }
    
}

template <typename _UData>
Timer<_UData> *RBTreeTimerContainer<_UData>::addTimer(time_t timeout)
{
    // 创建一个节点
    timer_rbtree_node<_UData> *node = new timer_rbtree_node<_UData>;
    node->timer.setTimeout(timeout);
    node->node.key = node->timer.getExpire();            // 用定时器的超时绝对时间最为key

    // 插入节点
    ngx_rbtree_insert(&this->rbtree, &node->node);

    return &node->timer;
}

template <typename _UData>
void RBTreeTimerContainer<_UData>::delTimer(Timer<_UData> *timer)
{
    if(timer == nullptr)
    {
        return ;
    }

    // 强转类型
    timer_rbtree_node<_UData> *temp = reinterpret_cast< timer_rbtree_node<_UData>* >
                                    ( reinterpret_cast<u_char *>(timer) - sizeof(ngx_rbtree_node_t) );
    if(temp == nullptr)
    {
        return ;
    }

    // 从红黑树中删除节点
    ngx_rbtree_delete(&this->rbtree, &temp->node);

    // 销毁数据
    delete temp;
}

template <typename _UData>
void RBTreeTimerContainer<_UData>::resetTimer(Timer<_UData> *timer, time_t timeout)
{
    if(timer == nullptr)
    {
        return ;
    }
    
    // 强转类型
    timer_rbtree_node<_UData> *temp = reinterpret_cast< timer_rbtree_node<_UData>* >
                                        ( reinterpret_cast<u_char *>(timer) - sizeof(ngx_rbtree_node_t) );
    if(temp == nullptr)
    {
        return ;
    }

    // 从红黑树中删除节点
    ngx_rbtree_delete(&this->rbtree, &temp->node);

    // 重新设置数据并添加到红黑树
    temp->timer.setTimeout(timeout);
    ngx_rbtree_insert(&this->rbtree, &temp->node);
}

// 获取定时器容器中超时值最小的绝对时间
template <typename _UData>
int RBTreeTimerContainer<_UData>::getMinExpire()
{
    Timer<_UData> *temp = top();
    if(temp)
    {
        return temp->getExpire();
    }

    return -1;
}

// 获取超时时间最小的timer
template <typename _UData>
Timer<_UData> *RBTreeTimerContainer<_UData>::top()
{
    if(rbtree.root == rbtree.sentinel)
    {
        return nullptr;
    }
    auto res = ngx_rbtree_min(this->rbtree.root, &this->sentinel);
    
    // 强转类型
    timer_rbtree_node<_UData> *node = reinterpret_cast< timer_rbtree_node<_UData>* >(res);

    return node ? &node->timer : nullptr; 
}

template <typename _UData>
void RBTreeTimerContainer<_UData>::popTimer()
{
    // 获取超时值最小的timer
    auto min_timer = top();

    // 删除timer
    delTimer(min_timer);
}

测试代码:

​ 下面代码是使用epoll实现的一个回射服务器,在服务端将检测非活跃连接,每个客户端都有一个定时器,超时时间为15s,当客户端与服务器在15s内没有数据交互,服务端就会踢掉相应的客户端。客户端发送数据后,服务端将会重置其定时器。

test_rbtree_timer.cpp

#include <iostream>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include "rbtree_timer.hpp"


using std::cout;
using std::endl;

#define PORT 6666
#define MAX_EVENTS 1024
#define MAX_BUF_SIZE 1024

struct Event;

using readHandle = void(*)(Event *, ITimerContainer<Event> *);
using writeHandle = void(*)(Event *, ITimerContainer<Event> *);

// 自定义结构体,用来保存一个连接的相关数据
struct Event
{
    int fd;
    char ip[64];
    uint16_t port;
    epoll_event event; 

    void *timer;

    char buf[MAX_BUF_SIZE];
    int buf_size;

    readHandle read_cb;
    writeHandle write_cb;
};

int epfd;

// 超时处理的回调函数
void timeout_handle(Event *cli)
{
    if(cli == nullptr)
    {
        return ;
    }
    
    cout << "Connection time out, fd:" << cli->fd << " ip:[" << cli->ip << ":" << cli->port << "]" << endl;

    epoll_ctl(epfd, EPOLL_CTL_DEL, cli->fd, &cli->event);

    close(cli->fd);

    delete cli;
}

void err_exit(const char *reason)
{
    cout << reason << ":" << strerror(errno) << endl;
    exit(1);
}

// 设置非阻塞
int setNonblcoking(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 setReusedAddr(int fd)
{
    int reuse = 1;
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
}

// 初始化server socket
int socket_init(unsigned short port, bool reuseAddr)
{
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if(fd < 0)
    {
        err_exit("socket error");
    }

    if(reuseAddr)
    {
        setReusedAddr(fd);
    }

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

    int ret = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
    if(ret < 0)
    {
        err_exit("bind error");
    }

    setNonblcoking(fd);

    ret = listen(fd, 128);
    if(ret < 0)
    {
        err_exit("listen error");
    }

    return fd;
}

void readData(Event *ev, ITimerContainer<Event> *htc)
{
    ev->buf_size = read(ev->fd, ev->buf, MAX_BUF_SIZE - 1);
    if(ev->buf_size == 0)
    {
        close(ev->fd);
        htc->delTimer((Timer<Event> *)ev->timer);
        epoll_ctl(epfd, EPOLL_CTL_DEL, ev->fd, &ev->event);
        cout << "Remote Connection has been closed, fd:" << ev->fd << " ip:[" << ev->ip << ":" << ev->port << "]" << endl;
        delete ev;
    }

    ev->event.events = EPOLLOUT;
    epoll_ctl(epfd, EPOLL_CTL_MOD, ev->fd, &ev->event);
}

void writeData(Event *ev, ITimerContainer<Event> *htc)
{
    write(ev->fd, ev->buf, ev->buf_size);

    
    ev->event.events = EPOLLIN;
    epoll_ctl(epfd, EPOLL_CTL_MOD, ev->fd, &ev->event);

    // 重新设置定时器
    htc->resetTimer((Timer<Event> *)ev->timer, 15000);
}

// 接收连接回调函数
void acceptConn(Event *ev, ITimerContainer<Event> *htc)
{
    Event *cli = new Event;
    struct sockaddr_in cli_addr;
    socklen_t sock_len = sizeof(cli_addr);
    int cfd = accept(ev->fd, (struct sockaddr *)&cli_addr, &sock_len);
    if(cfd < 0)
    {
        cout << "accept error, reason:" << strerror(errno) << endl;
        return;
    } 
    setNonblcoking(cfd);

    cli->fd = cfd;
    cli->port = ntohs(cli_addr.sin_port);
    inet_ntop(AF_INET, &cli_addr.sin_addr, cli->ip, sock_len);
    cli->read_cb = readData;
    cli->write_cb = writeData;

    auto timer = htc->addTimer(15000);      //设置客户端超时值15秒
    timer->setUserData(cli);
    timer->setCallBack(timeout_handle);
    cli->timer = (void *)timer;

    cli->event.events = EPOLLIN;
    cli->event.data.ptr = (void *) cli;
    epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &cli->event);

    cout << "New Connection, ip:[" << cli->ip << ":" << cli->port << "]" << endl;
}

int main(int argc, char *argv[])
{
    int fd = socket_init(PORT, true);
    Event server;
    server.fd = fd;
    
    epfd = epoll_create(MAX_EVENTS);
    if(epfd < 0)
    {
        err_exit("epoll create error");
    }

    server.event.events = EPOLLIN;
    server.event.data.ptr = (void *)&server;

    epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &server.event);

    ITimerContainer<Event> *htc = new RBTreeTimerContainer<Event>;

    struct epoll_event events[MAX_EVENTS];
    int nready = 0;

    int timeout = 10000;      //设置超时值为10秒
    while(1)
    {
        // 将定时容器中定时时间最短的时长作为epoll_wait的最大等待时间
        auto min_expire = htc->getMinExpire();
        timeout = (min_expire == -1) ? 10000 : min_expire - getMSec();

        nready = epoll_wait(epfd, events, MAX_EVENTS, timeout);
        if(nready < 0)
        {
            cout << "epoll wait error, reason:" << strerror(errno) << endl;
        } 
        else if(nready > 0)
        {
            // 接收新的连接
            for(int i = 0; i < nready; i++)
            {
                Event *ev =  (Event *) events[i].data.ptr;
                // 接受新的连接
                if(ev->fd == fd )
                {
                    acceptConn(ev, htc);
                }
                else if(ev->event.events & EPOLLIN)
                {
                    ev->read_cb(ev, htc);
                }
                else if(ev->event.events & EPOLLOUT)
                {
                    ev->write_cb(ev, htc);
                }
            }
        }
        else
        {
            htc->tick();
        }
    }
    
    delete htc;

    return 0;
}

.
其它相关博客:
最小堆实现的定时器
时间轮实现的定时器

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值