设计思路做到统一
统一事件类型:信号事件,定时事件,IO时间
统一系统调用:epoll/ select/ poll/ win 等作为底层 去实现不同的eventop(事件多路分发器)的接口
struct eventop
{
int (*add)(struct event_base *, evutil_socket_t fd, short old, short events, void *fdinfo);
int (*dispatch)(struct event_base *, struct timeval *);
}
组织形式是reactor模式
反转即cb。每个文件描述符上的可读/可写事件可以创建多个事件处理器(不同的回调函数),libevent的IO事件队列将相同文件描述符的事件处理器组织在一起(event_io_map),当事件就绪时,可以根据fd找到对应的IO事件队列(evmap_io),将队列中的每一个节点(event)按照优先级插入到不同的激活队列中
struct epollop {
struct epoll_event *events; // reactor(event_base)监听的所有的事件(事件数>文件描述符数)
int nevents;
int epfd;
};
struct evmap_io
{
struct event_list events; // 文件描述符上所有的事件
ev_uint16_t nread;
ev_uint16_t nwrite;
}
struct event_base
{
const struct eventop *evsel; // 多路复用的底层系统实现
struct event_io_map io; // evmap_io[fd] idx:fd --> evmap_io fd到events的映射
struct event_list eventqueue; // 注册事件队列
struct event_list *activequeues; // 激活队列
struct event_changelist changelist; //事件变化队列。 用途:如果一个文件描述符上注册的事件被多次修改,则可以使用缓冲来避免重复的系统调用(比如epoll_ctl).
}
2 注册队列和激活队列的作用
epoll相比于poll在于返回的就绪队列,不需要再次遍历,返回的就是可处理的。
同理,激活队列也是相同作用,激活队列中的都是直接可以执行cb的。
event_queue_insert(struct event_base *base, struct event *ev, int queue)
{
switch (queue) {
case EVLIST_INSERTED: // event_add()调用
TAILQ_INSERT_TAIL(&base->eventqueue, ev, ev_next);
break;
case EVLIST_ACTIVE: // dispatch()调用
base->event_count_active++;
TAILQ_INSERT_TAIL(&base->activequeues[ev->ev_pri],ev,ev_active_next);
break;
case EVLIST_TIMEOUT:
{
if (is_common_timeout(&ev->ev_timeout, base)) {
struct common_timeout_list *ctl =
get_common_timeout_list(base, &ev->ev_timeout);
insert_common_timeout_inorder(ctl, ev);
} else
min_heap_push(&base->timeheap, ev);
break;
}
}
}
3 event_add 为什么没有直接调用epoll_ctl
你先变,稳定了我在加
event_changelist_add() —> event_changelist_get_or_construct() 只修改event_changelist,因为文件描述符可能被多次设置监听读写时间,所以在这里只记录变化的,在dispatch时再epoll_apply_changes()。
//自上次调用 eventtop.dispatch 以来的“更改”列表。
struct event_changelist {
struct event_change *changes;
int n_changes; // 改变的个数
int changes_size;
};
4 libevent非内部函数是什么 | 是如何实现非阻塞的
在event_loop中
while(!done)
{
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;
}
static int
event_process_active(struct event_base *base)
{
struct event_list *activeq = NULL;
int i, c = 0;
for (i = 0; i < base->nactivequeues; ++i) {
if (TAILQ_FIRST(&base->activequeues[i]) != NULL) {
base->event_running_priority = i;
activeq = &base->activequeues[i];
c = event_process_active_single_queue(base, activeq);
if (c < 0) {
base->event_running_priority = -1;
return -1;
} else if (c > 0) //
break;
}
}
event_process_deferred_callbacks(&base->defer_queue,&base->event_break); //执行延迟回调函数,当所有的激活事件都处理完成后,需要做的事情
base->event_running_priority = -1;
return c;
}
libevent 中的尾队列TAILQ结构
c语言实现的双向链表 queue.h 用来存储事件 list的原型
尾队列头tqh 和 尾队列元素tqe
- 通过TAILQ_HEAD(name, type)宏可以快速的定义某一个类型结构体的队列.
队列处理方面不需要知道队列中数据是什么。类似queue这种传入泛型。 - 二级指针的用法
有一个类型叫做指针的指针tqh_last,于是可以修改指针的指针存储的指针的地址,而不是修改指针存储的数据的地址。这样不用存储最后一个节点,而是最后一个节点的tqe_next指针 - 插入/删除都是o(1)
#define TAILQ_HEAD(name, type)
struct name {
struct type *tqh_first;
struct type **tqh_last; 二级指针
}
#define TAILQ_HEAD_INITIALIZER(head)
{ NULL, &(head).tqh_first }
#define TAILQ_ENTRY(type)
struct {
struct type *tqe_next;
struct type **tqe_prev;
}
#define TAILQ_FIRST(head) ((head)->tqh_first)
#define TAILQ_END(head) NULL
#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
#define TAILQ_LAST(head, headname)
(*(((struct headname *)((head)->tqh_last))->tqh_last))
#define TAILQ_PREV(elm, headname, field)
(*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
#define TAILQ_EMPTY(head)
(TAILQ_FIRST(head) == TAILQ_END(head))
#define TAILQ_FOREACH(var, head, field)
for((var) = TAILQ_FIRST(head);
(var) != TAILQ_END(head);
(var) = TAILQ_NEXT(var, field))
#define TAILQ_FOREACH_REVERSe(var, head, headname, field)
for((var) = TAILQ_LAST(head, headname);
(var) != TAILQ_END(head);
(var) = TAILQ_PREV(var, headname, field))
#define TAILQ_INIT(head) do {
(head)->tqh_first = NULL;
(head)->tqh_last = &(head)->tqh_first;
} while (0)
#define TAILQ_INSERT_HEAD(head, elm, field) do {
if (((elm)->field.tqe_next = (head)->tqh_first) != NULL)
(head)->tqh_first->field.tqe_prev =
&(elm)->field.tqe_next;
else
(head)->tqh_last = &(elm)->field.tqe_next;
(head)->tqh_first = (elm);
(elm)->field.tqe_prev = &(head)->tqh_first;
} while (0)
#define TAILQ_INSERT_TAIL(head, elm, field) do {
(elm)->field.tqe_next = NULL;
(elm)->field.tqe_prev = (head)->tqh_last;
*(head)->tqh_last = (elm); elm是一个指针, tqh_last是一个指针的指针 这里相当于操作tqe_next了
(head)->tqh_last = &(elm)->field.tqe_next; 记录的是最后元素的下一个元素的地址,便于尾插
} while (0)
#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do {
if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)
(elm)->field.tqe_next->field.tqe_prev =
&(elm)->field.tqe_next;
else
(head)->tqh_last = &(elm)->field.tqe_next;
(listelm)->field.tqe_next = (elm);
(elm)->field.tqe_prev = &(listelm)->field.tqe_next;
} while (0)
#define TAILQ_INSERT_BEFORE(listelm, elm, field) do {
(elm)->field.tqe_prev = (listelm)->field.tqe_prev;
(elm)->field.tqe_next = (listelm);
*(listelm)->field.tqe_prev = (elm);
(listelm)->field.tqe_prev = &(elm)->field.tqe_next;
} while (0)
#define TAILQ_REMOVE(head, elm, field) do {
if (((elm)->field.tqe_next) != NULL)
(elm)->field.tqe_next->field.tqe_prev =
(elm)->field.tqe_prev;
else
(head)->tqh_last = (elm)->field.tqe_prev;
*(elm)->field.tqe_prev = (elm)->field.tqe_next;
} while (0)
#define TAILQ_REPLACE(head, elm, elm2, field) do {
if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL)
(elm2)->field.tqe_next->field.tqe_prev =
&(elm2)->field.tqe_next;
else
(head)->tqh_last = &(elm2)->field.tqe_next;
(elm2)->field.tqe_prev = (elm)->field.tqe_prev;
*(elm2)->field.tqe_prev = (elm2);
} while (0)



