libevent版本号:2.1.8-stable
libevent是一个优秀的开源库,我曾尝试过至少4次逐行理解代码,但都以失败告终,代码量还是有一些的(其实是水平有限23333),有一个说法---人能理解的代码上限是一万行,不在乎真假,我先采取逐功能阅读的方式来理解这个库的代码以及逻辑。
本文主要记录libevent的超时机制是如何利用minheap实现的,minheap的实现不在此展开,但是minheap的一些操作函数比较重要会做一些介绍。
关于最小堆的一篇不错的文章:https://www.cnblogs.com/MOBIN/p/5374217.html
一.minheap数据结构以及基本操作函数
最小堆结构体(minheap-internal.h):
typedef struct min_heap
{
struct event** p;//首地址
unsigned n, a;//n:元素个数,a:元素容量
} min_heap_t;
一些基本操作(minheap-internal.h):
static inline void min_heap_ctor_(min_heap_t* s);//初始化堆,调用前需要malloc
static inline void min_heap_dtor_(min_heap_t* s);//删除堆,free
static inline void min_heap_elem_init_(struct event* e);//初始化超时事件,min_heap_idx = -1
static inline int min_heap_elt_is_top_(const struct event *e);//超时事件是否为顶
static inline int min_heap_empty_(min_heap_t* s);//堆是否为空
static inline unsigned min_heap_size_(min_heap_t* s);//获取当前事件个数,min_heap_t中的n
static inline struct event* min_heap_top_(min_heap_t* s);//获取堆顶事件
static inline int min_heap_reserve_(min_heap_t* s, unsigned n);//调整分配内存
static inline int min_heap_push_(min_heap_t* s, struct event* e);//压入新的事件
static inline struct event* min_heap_pop_(min_heap_t* s);//弹出堆顶事件
static inline int min_heap_adjust_(min_heap_t *s, struct event* e);//调整e在最小堆的位置
static inline int min_heap_erase_(min_heap_t* s, struct event* e);//在最小堆中销毁e,并用堆的最后一个堆元素进行替换和调整
static inline void min_heap_shift_up_(min_heap_t* s, unsigned hole_index, struct event* e);//将某个叶子结点的堆元素上浮到合适的位置,用于堆元素的插入
static inline void min_heap_shift_up_unconditional_(min_heap_t* s, unsigned hole_index, struct event* e);和min_heap_shift_up_类似,只是一次无条件上浮
static inline void min_heap_shift_down_(min_heap_t* s, unsigned hole_index, struct event* e);//将某个叶子结点的堆元素下浮到合适的位置,用于堆元素的取出
二.timeout
下面是一个定时器例子:
// libevent头文件
#include <event.h>
#include <stdio.h>
#include <iostream>
using namespace std;
// 定时事件回调函数
void onTime(int sock, short event, void *arg)
{
cout << "on time!" << endl;
struct timeval tv;
tv.tv_sec = 1;
tv.tv_usec = 0;
// 重新添加定时事件(定时事件触发后默认自动删除)
event_add((struct event*)arg, &tv);
}
void test_timer()
{
// 初始化
event_init();
struct event evTime;
// 设置定时事件
evtimer_set(&evTime, onTime, &evTime);
struct timeval tv;
tv.tv_sec = 1;
tv.tv_usec = 0;
// 添加定时事件
event_add(&evTime, &tv);
// 事件循环
event_dispatch();
}
排除与超时无关的功能和代码,我们来看一些libevent内部的代码。
存放超时事件的结构体(event-internal.h):
struct event_base {
......
/** Priority queue of events with timeouts. */
struct min_heap timeheap;
......
};
初始化timeheap的函数过程:
event_init()/event_base_new() --> event_base_new_with_config(cfg) -->min_heap_ctor_(&base->timeheap)
反始化timeheap的函数过程:
event_base_free() --> event_base_free_(base, 1) --> min_heap_dtor_(&base->timeheap)
初始化事件元素的函数过程:
evtimer_set(ev, cb, arg)/event_set((ev), -1, 0, (cb), (arg)) --> event_assign(...) --> min_heap_elem_init_(ev)
timeheap push事件元素的函数过程:
event_add(ev, tv) --> event_add_nolock_(ev, tv, 0)-->event_queue_insert_timeout(base, ev)-->min_heap_push_(&base->timeheap, ev)
timeheap pop堆顶事件元素的函数过程:
event_dispatch() --> event_loop(0) --> event_base_loop(current_base, 0) --> timeout_process(base) --> min_heap_top_(&base->timeheap)
三.事件处理 event_base_loop :
int
event_base_loop(struct event_base *base, int flags)
{
struct timeval tv;
struct timeval *tv_p;
int res, done, retval = 0;
......
base->running_loop = 1;
clear_time_cache(base);//清除时间缓存,好让下面的gettime获得当前系统时间而不是获得缓存
的时间
done = 0;
base->event_gotterm = base->event_break = 0;
......
while (!done) {//进入事件主循环
//这两个是用来中断循环的标识
if (base->event_gotterm) {//event_loopexit_cb()可设置
break;
}
if (base->event_break) { //event_base_loopbreak()可设置
break;
}
tv_p = &tv; //校准时间
if (!N_ACTIVE_CALLBACKS(base) && !(flags & EVLOOP_NONBLOCK)) {//如果激活事件数量为0 &&
timeout_next(base, &tv_p);//获取堆顶事件
} else {
/*
* if we have active events, we just poll new events
* without waiting.
*/
evutil_timerclear(&tv);//有就绪事件则无需等待epoll_wait,时间设置为0
}
......
event_queue_make_later_events_active(base);
//清理时间缓存
clear_time_cache(base);
// 调用模型的epoll_wait/select/poll 等tv_p是刚才计算的最小时间间隔
res = evsel->dispatch(base, tv_p);
//更新base中的时间, 下面就是调用定时事件和IO事件。
update_time_cache(base);
//判断定时器事件是否发生了,若发生就将事件加入激活队列
timeout_process(base);
if (N_ACTIVE_CALLBACKS(base)) {//有激活事件
int n = event_process_active(base);//按优先级从高到低处理激活事件,回调函数
if ((flags & EVLOOP_ONCE)//执行完活跃事件的回调就退出。阻塞
&& N_ACTIVE_CALLBACKS(base) == 0
&& n != 0)
done = 1;
} else if (flags & EVLOOP_NONBLOCK)//没有激活事件,不阻塞,退出
done = 1;
}
.......
}
}
参考:
libevent源码分析(9)--2.1.8--事件注册和删除 https://blog.youkuaiyun.com/beitiandijun/article/details/72834786