在网络程序中我们通常要处理三种事件,网络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 inlinetypedef 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_valuengx_rbtree_insert_timer_valuengx_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   5p               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();// 删除timerpopTimer();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()
{// 获取超时值最小的timerauto min_timer = top();// 删除timerdelTimer(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 1024struct 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;
}

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

.
.

本人能力有限,代码中难免存在一些Bug,还请见谅。如果有好的建议,敬请提出。

高性能定时器2——红黑树实现相关推荐

  1. 如何快速实现分布式定时器丨红黑树|跳表|堆|时间轮|缓存|锁|事务|架构|高性能|消息队列丨C/C++Linux服务器开发丨C++后端开发

    如何快速实现分布式定时器 视频讲解如下,点击观看: 如何快速实现分布式定时器丨红黑树|跳表|堆|时间轮|缓存|锁|事务|架构|高性能|消息队列丨C/C++Linux服务器开发丨C++后端开发丨中间件 ...

  2. Linux服务器开发,定时器方案红黑树,时间轮,最小堆

    ─────────────────────────────────────────────────────────────── ┌------------┐ │▉▉♥♥♥♥♥♥♥♥ 99% │ ♥❤ ...

  3. 后端开发【一大波有用知识】定时器方案红黑树,时间轮,最小堆

    目录: 一.如何组织定时任务? 定时器收网络IO处理造成误差特别大,该怎么处理? 用何种数据机构存储定时器? 红黑树如何解决相同时间的key值的? 最小堆 时间轮 一个帮助理解单层级时间轮的例子 如何 ...

  4. 网络编程 高性能定时器数据结构分析 | 时间轮 红黑树定时器性能分析 | 为什么要做用户态定时器

    为什么要用户态的定时器? 首先是为什么要做定时器,定时器的主要说的是我们的应用(业务?功能?总之有这个需求)要做一个定时的任务.其实如果不想为什么,好像是理所当然的.我写这个的时候,知乎有一个问题(L ...

  5. 多线程环境下海量定时任务的定时器设计丨时间轮实现丨红黑树,跳表分析

    多线程环境下海量定时任务定时器设计 1. 定时器分析 2. 红黑树,最小堆,跳表实现比较分析 3. 时间轮实现 [Linux后端开发系列]多线程环境下海量定时任务的定时器设计丨时间轮实现丨红黑树,跳表 ...

  6. 数据结构与算法之美笔记——基础篇(中):树,二叉树,二叉查找树,平衡二叉查找树,红黑树,递归树,堆

    树: A 节点就是 B 节点的父节点,B 节点是 A 节点的子节点.B.C.D 这三个节点的父节点是同一个节点,所以它们之间互称为兄弟节点.我们把没有父节点的节点叫作根节点,也就是图中的节点 E.我们 ...

  7. 红黑树:自平衡的二叉查找树

    前一篇博文中写道,二叉查找树是一种具有较高性能的符号表实现,在理想情况下它可以保证查找.插入和删除操作的时间复杂度在对数级别.二叉查找树可以动态地插入和删除元素,在插入和删除操作的过程中,二叉查找树的 ...

  8. 高级数据结构研究-B树系列以及红黑树

    程序员做的越久,越发觉得基本功显得越来越重要了.基本功不扎实会潜移默化的影响你的程序开发,这不是,上次浏览博客,看到了一篇运用B+树实现数据库索引的功能,当时就不明白了,看似毫无关系的两者,怎么会有联 ...

  9. linux算法设计,红黑树的原理分析和算法设计

    红黑树是60年代中期计算机科学界找寻一种算法复杂度稳定,容易实现的数据存储算法的产物.在优先级队列.字典等实用领域都有广泛地应用,更是70年代提出的关系数据库模型--B树的鼻祖.在Linux kern ...

最新文章

  1. java校验ip格式_JAVA IP地址格式验证,使用正则表达式
  2. 独家 | 13大技能助你成为超级数据科学家!(附链接)
  3. opencv(2)- 处理像素值
  4. Python + Steamlit 快速开发可视化 web 页面!
  5. Redis的五大数据类型
  6. hdu 1800 (map)
  7. bzoj 2908. 又是nand(树链剖分+区间NAND+单点修改)
  8. mysql 安装1607_mysql服务启动报1607error
  9. notepad怎么运行php程序,notepad怎么运行php程序
  10. python hstack_python运筹优化(六):多变量规划问题geatpy实践
  11. boost::asio向socket中异步读写数据
  12. 三层交换机转发原理和实验
  13. 【无标题】手机扩容或更换字库后的指纹. 基带. 账号 .解锁等故障分析
  14. 未知的软件异常0xc0000409解决办法
  15. d3.js-V3制作简单的飞线图
  16. 计算机视觉笔记11.26
  17. Machine Learning - A/B Test
  18. 快手+中科大 | 全曝光推荐数据集KuaiRec 2.0版本
  19. Icon是什么,在线实现图片转Icon的方法
  20. 再谈 Go 语言在前端的应用前景

热门文章

  1. JASPAR:转录因子motif数据库
  2. 斐波那契数列C语言详解
  3. 论,golang编程语言性能如何提升?
  4. Snipaste截图工具如何修改默认的截图快捷键
  5. 获取苹果手机udid编号网站
  6. python撞库脚本_python撞库操作的实战脚本源代码
  7. 女员工被阿里录取工资二万六,辞职时被领导挽留:给你4万留下吧!
  8. 二叉树遍历(递归实现前序/中序/后序遍历)
  9. QT学习开发笔记(UDP通信)
  10. 晚睡强迫症..其实真的有——医学上的真正名字叫拖延症