libevent的大框框
2016-03-13 16:02
162 查看
libevent库(框架)主要统一管理三类事件,timeout事件,io事件,signal事件。
利用各种数据结构构建了一些容器(链表,最小堆等),将各种事件按逻辑放在这些容器(common_timeout_queues,event_signal_map,event_io_map,eventqueue ,activequeues等)中。
建立了消息循环,其中监察者从已注册事件容器(eventqueue )中轮询各个事件触发条件是否达成,若是触发条件成立,则将事件从已注册事件容器(eventqueue )中删除,放入已触发事件容器(activequeues)中,执行者从已触发事件容器拿取事件执行事件回调过程,并根据事件类型做后续处理。
//--------------------------------------------------------------------------------------------------------------------
timeout事件
挂载在 base->common_timeout_queues和 eventqueue 上,common_timeout_queues是一个二维表 ,横向是超时时间,纵向是相应超时事件注册的事件队列。
下面是一个用tailqueue尾队列(双向链表)写的二维表的demo(在linux下用gcc编译运行),queue.h头文件在/usr/include/sys/queue.h
横坐标是department部门,纵向是person。
下面是一个用strcut list_head(双向链表,常用于linux内核中,libevent没用到)写的二维表的demo
主循环中的timeout_process函数(监察者)会判断超时事件是否超时, 若是超时则将事件从common_timeout_queues和 eventqueue中删除 ,并挂载到 activequeues上,等待统一处理。
主循环event_process_active函数(执行者)会循环处理挂载在base->activequeues队列上的事件,并作相应的后续处理。
//---------------------------------------------------------------------------
signal 事件
首先 signal 是全局的,数量和条目是固定的,所以不存在自己创造一个,不同于io事件和timeout事件,它们是任意的当然是有穷多个的。
evsig_init 函数创建 base->sig.ev_signal_pair (实际是一对sokect文件描述符),并设置其 o_nonblock和fd_cloexec 标志位 ,用创建的文件描述符向event_base注册一个内部io读事件,这个事件一直等待,触发后调用 base->sig.ev_signal回调函数 evsig_cb读 ev_signal_pair 并且根据读的内容激活
base->sigmap 中相应的事件。
signal 的operations 只有两种 evsig_add调用和 evsig_del调用,其实质是用 signal函数或者sigaction函数向系统注册或注销信号回调函数evsig_handler,此回调函数往 base->sig.ev_signal_pair[0] (实际是sokect文件描述符) 中写数据,用以触发初始化时注册的io读事件。
struct event_signal_map 实际上是一个二维表 ,横向坐标是信号的数值,纵向是相应信号上注册事件的队列。
实际上信号事件被映射成了io读的事件
//----------------------------------------------------------------------
io 事件
struct event_io_map == event_signal_map 实际上是一个二维表 ,横向坐标是打开的文件描述符,纵向是相应文件描述符上注册的事件队列。
base->evsel以 select 为例,注册事件的读写文件描述符会被添加到 readset 或者 writeset(全局)中去,且事件会被挂载到base->io二维表上去,同时挂载到eventqueue上,在主循环的event_base_loop函数中,res = evsel->dispatch(base, tv_p)会调用select系统调用,参数为全局的 readset 和writeset ,当select返回后,遍历base->io
二维表,将就绪的io事件从eventqueue中删除并添加到 activequeu
4000
es中间去,等待统一处理。
//-------------------------------------------------------------------------
事件状态机,挂在eventqueue上面是 enabled 状态,挂在activequeues 是被激活等待执行
状态,还有许多小状态机。状态转移的触发条件是select返回 ,signal产生和轮询超时。
首先简化了程序员的工作量,不用关心一些细节,更多的关注业务逻辑,关注设计。
统一事件源 , 不用一个fd调用用一个select 或者poll 等io复用函数,而是所有fd 用一个select或者poll 等io复用函数,不会多次触发内核异常,使上下文多次从用户态转入内核态,增加效率 。
signal 有点类似于单片机的中断 ,在linux驱动设计中中断中要做尽量少的工作(尽量快速),且不能调用引起等待或挂起的函数(引起死循环),中断分上下两部分 , 此处类似于这个设计,在signal回调中干少量工作 ,只是通知有信号产生(上中断) ,真正的工作在主循环中执行(下中断),而且在signal回调函数中有许多调用是不安全的,且信号会中断程序执行,且信号不会计数产生次数。
统一时钟源,便于统一管理,提高实效。
值得学习的地方:尾队列使用 ,singal和io对event 的映射,eventqueue和activequeues两个队列的设计。
利用各种数据结构构建了一些容器(链表,最小堆等),将各种事件按逻辑放在这些容器(common_timeout_queues,event_signal_map,event_io_map,eventqueue ,activequeues等)中。
建立了消息循环,其中监察者从已注册事件容器(eventqueue )中轮询各个事件触发条件是否达成,若是触发条件成立,则将事件从已注册事件容器(eventqueue )中删除,放入已触发事件容器(activequeues)中,执行者从已触发事件容器拿取事件执行事件回调过程,并根据事件类型做后续处理。
//--------------------------------------------------------------------------------------------------------------------
timeout事件
挂载在 base->common_timeout_queues和 eventqueue 上,common_timeout_queues是一个二维表 ,横向是超时时间,纵向是相应超时事件注册的事件队列。
下面是一个用tailqueue尾队列(双向链表)写的二维表的demo(在linux下用gcc编译运行),queue.h头文件在/usr/include/sys/queue.h
横坐标是department部门,纵向是person。
#include <stdio.h> #include <stdlib.h> #include <sys/queue.h> struct person{ char pername[20]; TAILQ_ENTRY(person) entries; }; struct department{ char depname[20]; TAILQ_HEAD(, person) persons; }; struct company{ struct department **deps; int ndeps; }; #define DEPARTMENT_NUM 5 #define PERSON_NUM 10 int main() { int i,j; struct company comp; comp.ndeps = DEPARTMENT_NUM; comp.deps = malloc(sizeof(struct department*)*comp.ndeps); for(i=0; i<comp.ndeps; i++){ comp.deps[i] = (struct department*)malloc(sizeof(struct department)); sprintf(comp.deps[i]->depname,"dep %d",i); TAILQ_INIT(&(comp.deps[i]->persons)); for(j=0; j<PERSON_NUM; j++){ struct person *per = malloc(sizeof(struct person)); sprintf(per->pername,"per name %d %d",i,j); TAILQ_INSERT_TAIL(&(comp.deps[i]->persons),per,entries); } } struct person *item; for(i=0; i<comp.ndeps; i++){ printf("\n%s\n",comp.deps[i]->depname); TAILQ_FOREACH(item, &(comp.deps[i]->persons), entries){ printf("%s\n",item->pername); } } for(i=0; i<comp.ndeps; i++){ while(!TAILQ_EMPTY(&(comp.deps[i]->persons))){ struct person *tmp = TAILQ_FIRST(&(comp.deps[i]->persons)); TAILQ_REMOVE(&(comp.deps[i]->persons), tmp, entries); free(tmp); } free(comp.deps[i]); } free(comp.deps); }
下面是一个用strcut list_head(双向链表,常用于linux内核中,libevent没用到)写的二维表的demo
#include <stdio.h> #include <stdlib.h> #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) #define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );}) #define _LIST_FOREACH(item, head) \ for ((item) = (head)->next; (item) != (head); (item) = (item)->next) #define _LIST_EMPTY(head) ((head) == (head)->next) #define _LIST_FIRST(head) ((head)->next) #define _LIST_TAIL(head) ((head)->prev) #define _LIST_INIT(head) do{ \ (head)->next = (head); \ (head)->prev = (head); \ } while(0) #define _LIST_INSERT_TAIL(head, item) do{ \ (item)->next = (head); \ (item)->prev = (head)->prev; \ (head)->prev->next = (item); \ (head)->prev = (item); \ } while(0) #define _LIST_REMOVE(item) do{ \ (item)->next->prev = (item)->prev; \ (item)->prev->next = (item)->next; \ } while(0) struct list_head{ struct list_head *next,*prev; }; struct person{ char pername[20]; struct list_head entries; }; struct department{ char depname[20]; struct list_head persons; }; struct company{ struct department **deps; int ndeps; }; #define DEPARTMENT_NUM 5 #define PERSON_NUM 10 int main() { int i,j; struct company comp; comp.ndeps = DEPARTMENT_NUM; comp.deps = malloc(sizeof(struct department*)*comp.ndeps); for(i=0; i<comp.ndeps; i++){ comp.deps[i] = (struct department*)malloc(sizeof(struct department)); sprintf(comp.deps[i]->depname,"dep %d",i); _LIST_INIT(&(comp.deps[i]->persons)); for(j=0; j<PERSON_NUM; j++){ struct person *per = malloc(sizeof(struct person)); sprintf(per->pername,"per name %d %d",i,j); _LIST_INSERT_TAIL(&(comp.deps[i]->persons),&(per->entries)); } } struct person *tmp; struct list_head *item; for(i=0; i<comp.ndeps; i++){ printf("\n%s\n",comp.deps[i]->depname); _LIST_FOREACH(item, &(comp.deps[i]->persons)){ tmp = container_of(item,struct person, entries); printf("%s\n",tmp->pername); } } for(i=0; i<comp.ndeps; i++){ while(!_LIST_EMPTY(&(comp.deps[i]->persons))){ struct list_head *list_tmp = _LIST_FIRST(&(comp.deps[i]->persons)); tmp = container_of(list_tmp,struct person, entries); _LIST_REMOVE(list_tmp); free(tmp); } free(comp.deps[i]); } free(comp.deps); }
主循环中的timeout_process函数(监察者)会判断超时事件是否超时, 若是超时则将事件从common_timeout_queues和 eventqueue中删除 ,并挂载到 activequeues上,等待统一处理。
主循环event_process_active函数(执行者)会循环处理挂载在base->activequeues队列上的事件,并作相应的后续处理。
//---------------------------------------------------------------------------
signal 事件
首先 signal 是全局的,数量和条目是固定的,所以不存在自己创造一个,不同于io事件和timeout事件,它们是任意的当然是有穷多个的。
evsig_init 函数创建 base->sig.ev_signal_pair (实际是一对sokect文件描述符),并设置其 o_nonblock和fd_cloexec 标志位 ,用创建的文件描述符向event_base注册一个内部io读事件,这个事件一直等待,触发后调用 base->sig.ev_signal回调函数 evsig_cb读 ev_signal_pair 并且根据读的内容激活
base->sigmap 中相应的事件。
signal 的operations 只有两种 evsig_add调用和 evsig_del调用,其实质是用 signal函数或者sigaction函数向系统注册或注销信号回调函数evsig_handler,此回调函数往 base->sig.ev_signal_pair[0] (实际是sokect文件描述符) 中写数据,用以触发初始化时注册的io读事件。
struct event_signal_map 实际上是一个二维表 ,横向坐标是信号的数值,纵向是相应信号上注册事件的队列。
实际上信号事件被映射成了io读的事件
//----------------------------------------------------------------------
io 事件
struct event_io_map == event_signal_map 实际上是一个二维表 ,横向坐标是打开的文件描述符,纵向是相应文件描述符上注册的事件队列。
base->evsel以 select 为例,注册事件的读写文件描述符会被添加到 readset 或者 writeset(全局)中去,且事件会被挂载到base->io二维表上去,同时挂载到eventqueue上,在主循环的event_base_loop函数中,res = evsel->dispatch(base, tv_p)会调用select系统调用,参数为全局的 readset 和writeset ,当select返回后,遍历base->io
二维表,将就绪的io事件从eventqueue中删除并添加到 activequeu
4000
es中间去,等待统一处理。
//-------------------------------------------------------------------------
事件状态机,挂在eventqueue上面是 enabled 状态,挂在activequeues 是被激活等待执行
状态,还有许多小状态机。状态转移的触发条件是select返回 ,signal产生和轮询超时。
首先简化了程序员的工作量,不用关心一些细节,更多的关注业务逻辑,关注设计。
统一事件源 , 不用一个fd调用用一个select 或者poll 等io复用函数,而是所有fd 用一个select或者poll 等io复用函数,不会多次触发内核异常,使上下文多次从用户态转入内核态,增加效率 。
signal 有点类似于单片机的中断 ,在linux驱动设计中中断中要做尽量少的工作(尽量快速),且不能调用引起等待或挂起的函数(引起死循环),中断分上下两部分 , 此处类似于这个设计,在signal回调中干少量工作 ,只是通知有信号产生(上中断) ,真正的工作在主循环中执行(下中断),而且在signal回调函数中有许多调用是不安全的,且信号会中断程序执行,且信号不会计数产生次数。
统一时钟源,便于统一管理,提高实效。
值得学习的地方:尾队列使用 ,singal和io对event 的映射,eventqueue和activequeues两个队列的设计。