出处:http://blog.csdn.net/luotuo44/article/details/38501341
之前的博文讲了很多Libevent的基础构件,现在以一个实际例子来初步探究Libevent的基本工作流程。由于还有很多Libevent的细节并没有讲所以,这里的探究还是比较简洁,例子也相当简单。
- #include<unistd.h>
- #include<stdio.h>
- #include<event.h>
- #include<thread.h>
-
-
- void cmd_cb(int fd, short events, void *arg)
- {
- char buf[1024];
- printf("in the cmd_cb\n");
-
- read(fd, buf, sizeof(buf));
- }
-
-
- int main()
- {
- evthread_use_pthreads();
-
-
- struct event_base *base = event_base_new();
-
- struct event *cmd_ev = event_new(base, STDIN_FILENO,
- EV_READ | EV_PERSIST, cmd_cb, NULL);
-
- event_add(cmd_ev, NULL);
-
- event_base_dispatch(base);
-
- return 0;
- }
上面代码估计是不会比读者写的第一个Libevent程序复杂。但这已经包含了Libevent的基础工作流程。这里将进入这些函数的内部探究,并且只会讲解之前博文出现过的,没出现的,尽量不讲。在讲解之前,要先了解一下struct event这个结构体。
- struct event {
- TAILQ_ENTRY(event) ev_active_next;
- TAILQ_ENTRY(event) ev_next;
-
- union {
- TAILQ_ENTRY(event) ev_next_with_common_timeout;
- int min_heap_idx;
- } ev_timeout_pos;
-
-
- evutil_socket_t ev_fd;
-
- struct event_base *ev_base;
-
-
-
- union {
-
-
-
-
-
-
-
- struct {
- TAILQ_ENTRY(event) ev_io_next;
- struct timeval ev_timeout;
- } ev_io;
-
-
-
- struct {
- TAILQ_ENTRY(event) ev_signal_next;
- short ev_ncalls;
- short *ev_pncalls;
- } ev_signal;
- } _ev;
-
- short ev_events;
- short ev_res; //记录了当前激活事件的类型
-
-
- short ev_flags;
-
-
- ev_uint8_t ev_pri;
- ev_uint8_t ev_closure;
- struct timeval ev_timeout;
-
-
- void (*ev_callback)(evutil_socket_t, short, void *arg);
- void *ev_arg;
- };
event结构体里面有几个TAILQ_ENTRY队列节点类型。这里因为一个event是会同时处于多个队列之中。比如前几篇博文说到的同一个文件描述符或者信号值对应的多个event会被连在一起,所有的被加入到event_base的event也会连在一起,所有被激活的event也会被连在一起。所以会有多个QAILQ_ENTRY。
event结构体只有一两个之前没有说到的概念,这不妨碍理解event结构体。而event_base结构体则会太多之前没有说到的概念,所以这里就不贴出event_base的代码了。
在读这篇博文前,最好读一下前面几篇博文,因为会用到其他讲到的东西。如果之前有讲过的东西,这里也将一笔带过。
好了,开始探究。
最前面的evthread_use_pthreads();就不多说了,看《多线程、锁、条件变量(一)》和《多线程、锁、条件变量(二)》这两篇博文吧。
创建event_base:
下面看一下event_base_new函数。它是由event_base_new_with_config函数实现的。我们还是看后面那个函数吧。
-
- struct event_base *
- event_base_new_with_config(const struct event_config *cfg)
- {
- int i;
- struct event_base *base;
- int should_check_environment;
-
-
-
-
- if ((base = mm_calloc(1, sizeof(struct event_base))) == NULL) {
- event_warn("%s: calloc", __func__);
- return NULL;
- }
-
- ...
-
- TAILQ_INIT(&base->eventqueue);
-
- ...
-
- if (cfg)
- base->flags = cfg->flags;
-
- evmap_io_initmap(&base->io);
- evmap_signal_initmap(&base->sigmap);
-
- base->evbase = NULL;
-
- should_check_environment =
- !(cfg && (cfg->flags & EVENT_BASE_FLAG_IGNORE_ENV));
-
-
- for (i = 0; eventops[i] && !base->evbase; i++) {
- if (cfg != NULL) {
-
- if (event_config_is_avoided_method(cfg,
- eventops[i]->name))
- continue;
- if ((eventops[i]->features & cfg->require_features)
- != cfg->require_features)
- continue;
- }
-
- if (should_check_environment &&
- event_is_method_disabled(eventops[i]->name))
- continue;
-
-
- base->evsel = eventops[i];
-
-
- base->evbase = base->evsel->init(base);
- }
-
-
-
- #ifndef _EVENT_DISABLE_THREAD_SUPPORT
-
-
-
-
- if (EVTHREAD_LOCKING_ENABLED() &&
- (!cfg || !(cfg->flags & EVENT_BASE_FLAG_NOLOCK))) {
- EVTHREAD_ALLOC_LOCK(base->th_base_lock,
- EVTHREAD_LOCKTYPE_RECURSIVE);
- base->defer_queue.lock = base->th_base_lock;
- EVTHREAD_ALLOC_COND(base->current_event_cond);
- }
- #endif
-
- return (base);
- }
这里用到了event_config结构体,关于这个结构体可以参考《配置event_base》一文。这个结构体主要是对event_base进行一些配置。另外代码中还讲到了怎么使用选择一个多IO复用函数,这个可以参考《跨平台Reactor接口的实现》一文。
宏EVTHREAD_LOCKING_ENABLED主要是检测是否已经支持锁了。检测的方式也很简单,也就是检测_evthread_lock_fns全局变量中的lock成员变量是否不为NULL。有关这个_evthread_lock_fns全局变量可以查看《多线程、锁、条件变量(一)》。
创建event:
好了,现在event_base已经新建出来了。下面看一下event_new函数,它和前面的event_base_new一样,把主要是的初始化工作交给另一个函数。event_new函数的工作只是创建一个struct event结构体,然后把它的参数原封不动地传给event_assign,所以还是看event_assign函数。
-
- int
- event_assign(struct event *ev, struct event_base *base, evutil_socket_t fd,
- short events, void (*callback)(evutil_socket_t, short, void *), void *arg)
- {
-
- ev->ev_base = base;
- ev->ev_callback = callback;
- ev->ev_arg = arg;
- ev->ev_fd = fd;
- ev->ev_events = events;
- ev->ev_res = 0;
- ev->ev_flags = EVLIST_INIT;
- ev->ev_ncalls = 0;
- ev->ev_pncalls = NULL;
-
- if (events & EV_SIGNAL) {
- if ((events & (EV_READ|EV_WRITE)) != 0) {
- event_warnx("%s: EV_SIGNAL is not compatible with "
- "EV_READ or EV_WRITE", __func__);
- return -1;
- }
- }
-
- ...
-
- return 0;
- }
从event_assign函数的名字可以得知它是进行赋值操作的。所以它能可以在event被初始化后再次调用。不过,初始化后再次调用的话,有些事情要注意。这个在后面的博客中会说到。
从上面的代码可看到:如果这个event是用来监听一个信号的,那么就不能让这个event监听读或者写事件。原因是其与信号event的实现方法相抵触,具体可以参考《信号event的处理》。
注意,此时event结构体的变量ev_flags的值是EVLIST_INIT。对变量的追踪是很有帮助的。它指明了event结构体的状态。它通过以或运算的方式取下面的值:
-
- #define EVLIST_TIMEOUT 0x01 //event从属于定时器队列或者时间堆
- #define EVLIST_INSERTED 0x02 //event从属于注册队列
- #define EVLIST_SIGNAL 0x04 //没有使用
- #define EVLIST_ACTIVE 0x08 //event从属于活动队列
- #define EVLIST_INTERNAL 0x10 //该event是内部使用的。信号处理时有用到
- #define EVLIST_INIT 0x80 //event已经被初始化了
-
-
- #define EVLIST_ALL (0xf000 | 0x9f) //所有标志。这个不能取
将event加入到event_base中:
创建完一个event结构体后,现在看一下event_add。它同前面的函数一样,内部也是调用其他函数完成工作。因为它用到了锁,所以给出它的代码
-
- int
- event_add(struct event *ev, const struct timeval *tv)
- {
- int res;
-
-
- EVBASE_ACQUIRE_LOCK(ev->ev_base, th_base_lock);
- res = event_add_internal(ev, tv, 0);
-
- EVBASE_RELEASE_LOCK(ev->ev_base, th_base_lock);
- return (res);
- }
-
- static inline int
- event_add_internal(struct event *ev, const struct timeval *tv,
- int tv_is_absolute)
- {
- struct event_base *base = ev->ev_base;
- int res = 0;
- int notify = 0;
- ...
- if ((ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL)) &&
- !(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE))) {
- if (ev->ev_events & (EV_READ|EV_WRITE))
- res = evmap_io_add(base, ev->ev_fd, ev);
- else if (ev->ev_events & EV_SIGNAL)
- res = evmap_signal_add(base, (int)ev->ev_fd, ev);
- if (res != -1)
- event_queue_insert(base, ev, EVLIST_INSERTED);
- }
- ...
- return (res);
- }
event_add函数只是对event_base加了锁,然后调用event_add_internal函数完成工作。所以函数event_add是线程安全的。
event_add_internal函数会调用前几篇博文讲到的evmap_io_add和evmap_signal_add,把有相同文件描述符fd和信号值sig的event连在一个队列里面。成功之后,就会调用event_queue_insert,向event_base注册事件。
前面博文的evmap_io_add和evmap_signal_add函数内部还有一些地方并没有说到。那就是把要监听的fd或者sig添加到多路IO复用函数中,使得其是可以监听的。
-
- int
- evmap_io_add(struct event_base *base, evutil_socket_t fd, struct event *ev)
- {
- const struct eventop *evsel = base->evsel;
- struct event_io_map *io = &base->io;
- struct evmap_io *ctx = NULL;
- int nread, nwrite, retval = 0;
- short res = 0, old = 0;
- struct event *old_ev;
-
- ...
-
-
-
- GET_IO_SLOT_AND_CTOR(ctx, io, fd, evmap_io, evmap_io_init,
- evsel->fdinfo_len);
-
-
-
-
-
- nread = ctx->nread;
- nwrite = ctx->nwrite;
-
- if (nread)
- old |= EV_READ;
- if (nwrite)
- old |= EV_WRITE;
-
- if (ev->ev_events & EV_READ) {
-
-
-
- if (++nread == 1)
- res |= EV_READ;
- }
- if (ev->ev_events & EV_WRITE) {
- if (++nwrite == 1)
- res |= EV_WRITE;
- }
- if (EVUTIL_UNLIKELY(nread > 0xffff || nwrite > 0xffff)) {
- event_warnx("Too many events reading or writing on fd %d",
- (int)fd);
- return -1;
- }
-
-
-
- if (res) {
- void *extra = ((char*)ctx) + sizeof(struct evmap_io);
- if (evsel->add(base, ev->ev_fd,
- old, (ev->ev_events & EV_ET) | res, extra) == -1)
- return (-1);
- retval = 1;
- }
-
-
- ctx->nread = (ev_uint16_t) nread;
- ctx->nwrite = (ev_uint16_t) nwrite;
-
- TAILQ_INSERT_TAIL(&ctx->events, ev, ev_io_next);
-
- return (retval);
- }
代码中有两个计数nread和nwrite,当其值为1时,就说明是第一次监听对应的事件。此时,就要把这个fd添加到多路IO复用函数中。这就完成fd与select、poll、epoll之类的多路IO复用函数的相关联。这完成对fd监听的第一步。
下面再看event_queue_insert函数的实现。
-
- static void
- event_queue_insert(struct event_base *base, struct event *ev, int queue)
- {
- ...
-
- ev->ev_flags |= queue;
- switch (queue) {
- case EVLIST_INSERTED:
- TAILQ_INSERT_TAIL(&base->eventqueue, ev, ev_next);
- break;
- ...
- }
- }
这个函数的主要作为是把event加入到对应的队列中。在这里,是为了把event加入到eventqueue这个已注册队列中,即将event向event_base注册。注意,此时event结构体的ev_flags变量为EVLIST_INIT | EVLIST_INSERTED了。
进入主循环,开始监听event:
现在事件已经添加完毕,开始进入主循环event_base_dispatch函数。还是同样,该函数内部调用event_base_loop完成工作。
-
- int
- event_base_loop(struct event_base *base, int flags)
- {
- const struct eventop *evsel = base->evsel;
- int res, done, retval = 0;
-
-
- EVBASE_ACQUIRE_LOCK(base, th_base_lock);
-
- done = 0;
-
- while (!done) {
-
-
- res = evsel->dispatch(base, tv_p);
-
- if (N_ACTIVE_CALLBACKS(base)) {
- int n = event_process_active(base);
- }
- }
-
- done:
-
- EVBASE_RELEASE_LOCK(base, th_base_lock);
- return (retval);
- }
在event_base_loop函数内部会进行加锁,这是因为这里要对event_base里面的多个队列进行一些数据操作(增删操作),此时要用锁来保护队列不被另外一个线程所破坏。
上面代码中有两个函数evsel->dispatch和event_process_active。前一个将调用多路IO复用函数,对event进行监听,并且把满足条件的event放到event_base的激活队列中。后一个则遍历这个激活队列的所有event,逐个调用对应的回调函数。
可以看到整个流程如下图所示:
将已激活event插入到激活列表:
我们还是深入看看Libevent是怎么把event添加到激活队列的。dispatch是一个函数指针,它的实现都包含是一个多路IO复用函数。这里选择poll这个多路IO复用函数来作分析。
-
- static int
- poll_dispatch(struct event_base *base, struct timeval *tv)
- {
- int res, i, j, nfds;
- long msec = -1;
- struct pollop *pop = base->evbase;
- struct pollfd *event_set;
-
- nfds = pop->nfds;
-
- event_set = pop->event_set;
-
-
- EVBASE_RELEASE_LOCK(base, th_base_lock);
- res = poll(event_set, nfds, msec);
-
- EVBASE_ACQUIRE_LOCK(base, th_base_lock);
-
- ...
-
- i = random() % nfds;
- for (j = 0; j < nfds; j++) {
- int what;
- if (++i == nfds)
- i = 0;
- what = event_set[i].revents;
- if (!what)
- continue;
-
- res = 0;
-
-
-
-
- if (what & (POLLHUP|POLLERR))
- what |= POLLIN|POLLOUT;
-
- if (what & POLLIN)
- res |= EV_READ;
- if (what & POLLOUT)
- res |= EV_WRITE;
- if (res == 0)
- continue;
-
-
- evmap_io_active(base, event_set[i].fd, res);
- }
-
- return (0);
- }
pollfd数组的数据是在evmap_io_add函数中添加的,在evmap_io_add函数里面,有一个evsel->add调用,它会把数据(fd和对应的监听类型)放到pollfd数组中。
当主线程从poll返回时,没有错误的话,就说明有些监听的事件发生了。在函数的后面,它会遍历这个pollfd数组,查看哪个fd是有事件发生。如果事件发生,就调用evmap_io_active(base, event_set[i].fd, res);在这个函数里面会把这个fd对应的event放到event_base的激活event队列中。下面是evmap_io_active的代码。
- void
- evmap_io_active(struct event_base *base, evutil_socket_t fd, short events)
- {
- struct event_io_map *io = &base->io;
- struct evmap_io *ctx;
- struct event *ev;
-
-
- GET_IO_SLOT(ctx, io, fd, evmap_io);
-
-
- TAILQ_FOREACH(ev, &ctx->events, ev_io_next) {
- if (ev->ev_events & events)
- event_active_nolock(ev, ev->ev_events & events, 1);
- }
- }
-
- void
- event_active_nolock(struct event *ev, int res, short ncalls)
- {
- struct event_base *base;
- base = ev->ev_base;
-
- ...
-
- event_queue_insert(base, ev, EVLIST_ACTIVE);
-
- ...
- }
-
-
-
- static void
- event_queue_insert(struct event_base *base, struct event *ev, int queue)
- {
- ...
-
- ev->ev_flags |= queue;
- switch (queue) {
- case EVLIST_ACTIVE:
- base->event_count_active++;
-
- TAILQ_INSERT_TAIL(&base->activequeues[ev->ev_pri],
- ev,ev_active_next);
- break;
- }
- }
经过上面三个函数的调用,就可以把一个fd对应的所有符合条件的event插入到激活队列中。因为Libevent还对事件处理设有优先级,所以有一个激活数组队列,而不是只有一个激活队列。
注意,此时event结构体的ev_flags变量为EVLIST_INIT | EVLIST_INSERTED | EVLIST_ACTIVE了。
处理激活列表中的event:
现在已经完成了将event插入到激活队列中。接下来就是遍历激活数组队列,把所有激活的event都处理即可。
现在来追踪event_process_active函数。
-
- 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) {
- activeq = &base->activequeues[i];
- c = event_process_active_single_queue(base, activeq);
- ...
- }
- }
- return c;
- }
-
- static int
- event_process_active_single_queue(struct event_base *base,
- struct event_list *activeq)
- {
- struct event *ev;
- int count = 0;
-
- for (ev = TAILQ_FIRST(activeq); ev; ev = TAILQ_FIRST(activeq)) {
-
- if (ev->ev_events & EV_PERSIST)
- event_queue_remove(base, ev, EVLIST_ACTIVE);
- else
- event_del_internal(ev);
- if (!(ev->ev_flags & EVLIST_INTERNAL))
- ++count;
-
-
- switch (ev->ev_closure) {
- ...
- case EV_CLOSURE_NONE:
-
- (*ev->ev_callback)(ev->ev_fd, ev->ev_res, ev->ev_arg);
- break;
- }
-
- EVBASE_ACQUIRE_LOCK(base, th_base_lock);
-
- }
- return count;
- }
上面的代码,从高到低优先级遍历激活event优先级数组。对于激活的event,要调用event_queue_remove将之从激活队列中删除掉。然后再对这个event调用其回调函数。
event_queue_remove函数的调用会改变event结构体的ev_flags变量的值。调用后,
ev_flags
变量为
EVLIST_INIT | EVLIST_INSERTED
。
现在又可以等待下一次事件的到来了。