一直对鼎鼎大名的libevent很感兴趣,所以需要深入研究下源码。
libevent支持的功能
- 异步dns
- 简单的http server和http client
- 简单的rpc框架
- bufferevent IO
- signal,定时器
前言
libevent的最新版本已经很复杂了,深入分析的难度很大,所以需要选择一个初期的版本(核心功能已经存在)去分析。
git clone https://github.com/libevent/libevent.git
git tag |head -5
release-1.1b
release-1.2
release-1.2a
release-1.3a
在此,我选择最老的一个 tag(release-1.1b) 进行分析,已经支持了
- bufferevent IO
- signal,定时器
初览
先确定需要分析的源码文件
# find ./ -name "*.c" |egrep -v test
./buffer.c
./devpoll.c
./epoll.c
./epoll_sub.c
./evbuffer.c
./event.c
./kqueue.c
./log.c
./poll.c
./rtsig.c
./select.c
./signal.c
./WIN32-Code/misc.c
./WIN32-Code/win32.c
除过测试代码有14个源码分析,如果只分析linux平台,可以在继续精简
./event.c
./signal.c
./evbuffer.c
./buffer.c
./epoll.c
只需要分析这5个文件就可以了,一共2161行代码,是不是压力就小了很多。
811 event.c
204 signal.c
378 evbuffer.c
423 buffer.c
345 epoll.c
2161 total
event.c : libevent的主要逻辑
evbuffer.c和buffer.c : proactor的实现,关于reactor和proactor,我觉得最简单的解释在这里。在此简单引用下:
reactor:能收了你跟俺说一声。
proactor: 你给我收十个字节,收好了跟俺说一声。
signal : 信号处理,本文暂不分析信号方面的代码
epoll.c : linux平台事件循环机制的后端实现
event.c文件分析
全局变量
struct event_list signalqueue;
struct event_base *current_base = NULL;
const struct eventop *eventops[] = {...}
event_base结构体
struct event_base {
const struct eventop *evsel;
void *evbase;
int event_count; /* counts number of total events */
int event_count_active; /* counts number of active events */
int event_gotterm; /* Set to terminate loop */
/* active event management */
struct event_list **activequeues;
int nactivequeues;
struct event_list eventqueue;
struct timeval event_tv;
RB_HEAD(event_tree, event) timetree;
};
evsel : 指向事件循环机制的后端实现
event_count : 当前的事件个数
event_count_active : 当前活跃的事件个数
activequeues : 二维数组,数组的每个元素是一个双向链表,双向链表中的每一个元素是struct event。使用二维数组是用来支持事件的优先级。数组的下标越小则优先级越高。形如:
nactivequeues : 二维数组的大小
event_tv : 最后一次更新的时间戳
eventqueue : 保存文件描述符(fd)的读写事件
timetree : 保存超时事件,早期版本使用了红黑树,后期版本已经修改为最小堆了
事件主循环分析
int
event_base_loop(struct event_base *base, int flags)
{
done = 0;
while (!done) {
gettimeofday(&tv, NULL);
if (timercmp(&tv, &base->event_tv, <)) {
// 当前时间小于上一次记录的时间,则说明用户向前修改时间了
struct timeval off;
// 计算出用户向前修改的毫秒数
timersub(&base->event_tv, &tv, &off);
// 对所有的超时事件减去相应的毫秒数
timeout_correct(base, &off);
}
base->event_tv = tv;
if (!base->event_count_active && !(flags & EVLOOP_NONBLOCK))
// 当前没有活跃的事件,则计算出与最近的超时事件的间隔时间
timeout_next(base, &tv);
else
// 当前存在活跃时间,则tv设置为0,dispatch(epoll_wait)会马上返回
timerclear(&tv);
res = evsel->dispatch(base, evbase, &tv);
if (res == -1)
return (-1);
// 处理超时的时间
timeout_process(base);
if (base->event_count_active) {
// 处理活跃事件
event_process_active(base);
}
return (0);
}
精简后的主逻辑还是比较清晰的,此处需要注意两个点:
- libevent对于用户修改时间有可能会导致定时器失效的处理
- 当事件触发时都是先将事件保存在activequeues中,然后每次仅仅处理最高优先级的一组事件,之后再次进入主循环,这样的做的好处是考虑了保证高优先级的事件优先被处理。
超时事件的处理
超时事件的处理在event.c文件中的timeout_process函数,每次dispatch返回时都会去检查已经超时的事件,然后放入到**activequeues(活跃队列)**中
void
timeout_process(struct event_base *base)
{
struct timeval now;
struct event *ev, *next;
gettimeofday(&now, NULL);
for (ev = RB_MIN(event_tree, &base->timetree); ev; ev = next) {
if (timercmp(&ev->ev_timeout, &now, >))
break;
// 当前now已经大于事件的超时事件
next = RB_NEXT(event_tree, &base->timetree, ev);
// 此处算是早期版本功能不足点,没有考虑到持续性(EV_PERSIST)的超时事件
event_queue_remove(base, ev, EVLIST_TIMEOUT);
event_del(ev);
// 放入到活跃事件链表中
// 1表示调用事件回调的次数
event_active(ev, EV_TIMEOUT, 1);
}
}
读写事件的处理
读写事件的处理是在事件循环的dispatch函数中处理,在linux平台具体的函数是epoll_dispatch,先进行epoll_wait,当fd有事件发生时,将相应的事件放入 activequeues(活跃队列) 中
int
epoll_dispatch(struct event_base *base, void *arg, struct timeval *tv)
{
struct epollop *epollop = arg;
struct epoll_event *events = epollop->events;
struct evepoll *evep;
int i, res, timeout;
// timeval向上取整转成毫秒
timeout = tv->tv_sec * 1000 + (tv->tv_usec + 999) / 1000;
res = epoll_wait(epollop->epfd, events, epollop->nevents, timeout);
if (res == -1) {
if (errno != EINTR) {
event_warn("epoll_wait");
return (-1);
}
return (0);
}
// 有res个fd有事件发生
for (i = 0; i < res; i++) {
int which = 0;
int what = events[i].events;
struct event *evread = NULL, *evwrite = NULL;
evep = (struct evepoll *)events[i].data.ptr;
if (what & EPOLLHUP) // fd的读写被关闭
what |= EPOLLIN | EPOLLOUT;
else if (what & EPOLLERR) // fd出错
what |= EPOLLIN | EPOLLOUT;
if (what & EPOLLIN) { // 可读事件发生
evread = evep->evread;
which |= EV_READ;
}
if (what & EPOLLOUT) { // 可写事件发生
evwrite = evep->evwrite;
which |= EV_WRITE;
}
if (!which)
continue;
if (evread != NULL && !(evread->ev_events & EV_PERSIST))
// 没有设置持续性事件(EV_PERSIST),则删除读事件
event_del(evread);
if (evwrite != NULL && evwrite != evread && !(evwrite->ev_events & EV_PERSIST))
// 没有设置持续性事件(EV_PERSIST),则删除写事件
event_del(evwrite);
if (evread != NULL)
event_active(evread, EV_READ, 1); // 将读事件放入活跃队列
if (evwrite != NULL)
event_active(evwrite, EV_WRITE, 1); // 将写事件放入活跃队列
}
return (0);
}
活跃事件的处理
static void
event_process_active(struct event_base *base)
{
struct event *ev;
struct event_list *activeq = NULL;
int i;
short ncalls;
for (i = 0; i < base->nactivequeues; ++i) {
if (TAILQ_FIRST(base->activequeues[i]) != NULL) {
// 找到优先级最高的一组事件
// 每次仅仅处理一组事件,而不是全部的活跃事件
activeq = base->activequeues[i];
break;
}
}
// 对优先级最高的一组事件进行处理
for (ev = TAILQ_FIRST(activeq); ev; ev = TAILQ_FIRST(activeq)) {
// 从活跃事件的队列中删除活跃事件
event_queue_remove(base, ev, EVLIST_ACTIVE);
ncalls = ev->ev_ncalls;
ev->ev_pncalls = &ncalls;
while (ncalls) { // ncalls表示调用ev_callback的次数
ncalls--;
ev->ev_ncalls = ncalls;
(*ev->ev_callback)((int)ev->ev_fd, ev->ev_res, ev->ev_arg);
}
}
}