Libevent源码分析-----Libevent工作流程探究

出处:http://blog.csdn.net/luotuo44/article/details/38501341


        之前的博文讲了很多Libevent的基础构件,现在以一个实际例子来初步探究Libevent的基本工作流程。由于还有很多Libevent的细节并没有讲所以,这里的探究还是比较简洁,例子也相当简单。

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include<unistd.h>  
  2. #include<stdio.h>  
  3. #include<event.h>  
  4. #include<thread.h>  
  5.   
  6.   
  7. void cmd_cb(int fd, short events, void *arg)  
  8. {  
  9.     char buf[1024];  
  10.     printf("in the cmd_cb\n");  
  11.   
  12.     read(fd, buf, sizeof(buf));  
  13. }  
  14.   
  15.   
  16. int main()  
  17. {  
  18.     evthread_use_pthreads();  
  19.   
  20.     //使用默认的event_base配置  
  21.     struct event_base *base = event_base_new();  
  22.   
  23.     struct event *cmd_ev = event_new(base, STDIN_FILENO,  
  24.                                      EV_READ | EV_PERSIST, cmd_cb, NULL);  
  25.   
  26.     event_add(cmd_ev, NULL); //没有超时  
  27.   
  28.     event_base_dispatch(base);  
  29.   
  30.     return 0;  
  31. }  
        上面代码估计是不会比读者写的第一个Libevent程序复杂。但这已经包含了Libevent的基础工作流程。这里将进入这些函数的内部探究,并且只会讲解之前博文出现过的,没出现的,尽量不讲。在讲解之前,要先了解一下struct event这个结构体。

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. struct event {  
  2.     TAILQ_ENTRY(event) ev_active_next; //激活队列  
  3.     TAILQ_ENTRY(event) ev_next; //注册事件队列  
  4.     /* for managing timeouts */  
  5.     union {  
  6.         TAILQ_ENTRY(event) ev_next_with_common_timeout;  
  7.         int min_heap_idx; //指明该event结构体在堆的位置  
  8.     } ev_timeout_pos; //仅用于定时事件处理器(event).EV_TIMEOUT类型  
  9.   
  10.     //对于I/O事件,是文件描述符;对于signal事件,是信号值  
  11.     evutil_socket_t ev_fd;  
  12.   
  13.     struct event_base *ev_base; //所属的event_base  
  14.   
  15.     //因为信号和I/O是不能同时设置的。所以可以使用共用体以省内存  
  16.     //在低版本的Libevent,两者是分开的,不在共用体内。  
  17.     union {  
  18.     //无论是信号还是IO,都有一个TAILQ_ENTRY的队列。它用于这样的情景:  
  19.     //用户对同一个fd调用event_new多次,并且都使用了不同的回调函数。  
  20.     //每次调用event_new都会产生一个event*。这个xxx_next成员就是把这些  
  21.     //event连接起来的。  
  22.       
  23.         /* used for io events */  
  24.         //用于IO事件  
  25.         struct {  
  26.             TAILQ_ENTRY(event) ev_io_next;  
  27.             struct timeval ev_timeout;  
  28.         } ev_io;  
  29.   
  30.         /* used by signal events */  
  31.         //用于信号事件  
  32.         struct {  
  33.             TAILQ_ENTRY(event) ev_signal_next;  
  34.             short ev_ncalls; //事件就绪执行时,调用ev_callback的次数         /* Allows deletes in callback */  
  35.             short *ev_pncalls; //指针,指向次数  
  36.         } ev_signal;  
  37.     } _ev;  
  38.   
  39.     short ev_events;//记录监听的事件类型 EV_READ EVTIMEOUT之类  
  40.     short ev_res;       /* result passed to event callback *///记录了当前激活事件的类型  
  41.     //libevent用于标记event信息的字段,表明其当前的状态.  
  42.     //可能值为前面的EVLIST_XXX  
  43.     short ev_flags;   
  44.   
  45.     //本event的优先级。调用event_priority_set设置  
  46.     ev_uint8_t ev_pri;  
  47.     ev_uint8_t ev_closure;  
  48.     struct timeval ev_timeout;//用于定时器,指定定时器的超时值  
  49.   
  50.     /* allows us to adopt for different types of events */  
  51.     void (*ev_callback)(evutil_socket_t, shortvoid *arg); //回调函数  
  52.     void *ev_arg; //回调函数的参数  
  53. };  
        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函数实现的。我们还是看后面那个函数吧。

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. //event.c文件  
  2. struct event_base *  
  3. event_base_new_with_config(const struct event_config *cfg)  
  4. {  
  5.     int i;  
  6.     struct event_base *base;  
  7.     int should_check_environment;  
  8.   
  9.   
  10.     //之所以不用mm_malloc是因为mm_malloc并不会清零该内存区域。  
  11.     //而这个函数是会清零申请到的内存区域,这相当于被base初始化  
  12.     if ((base = mm_calloc(1, sizeof(struct event_base))) == NULL) {  
  13.         event_warn("%s: calloc", __func__);  
  14.         return NULL;  
  15.     }  
  16.       
  17.     ...  
  18.   
  19.     TAILQ_INIT(&base->eventqueue);  
  20.   
  21.     ...  
  22.   
  23.     if (cfg)  
  24.         base->flags = cfg->flags;  
  25.   
  26.     evmap_io_initmap(&base->io);  
  27.     evmap_signal_initmap(&base->sigmap);  
  28.   
  29.     base->evbase = NULL;  
  30.   
  31.     should_check_environment =  
  32.         !(cfg && (cfg->flags & EVENT_BASE_FLAG_IGNORE_ENV));  
  33.   
  34.     //选择IO复用结构体  
  35.     for (i = 0; eventops[i] && !base->evbase; i++) {  
  36.         if (cfg != NULL) {  
  37.             /* determine if this backend should be avoided */  
  38.             if (event_config_is_avoided_method(cfg,  
  39.                 eventops[i]->name))  
  40.                 continue;  
  41.             if ((eventops[i]->features & cfg->require_features)  
  42.                 != cfg->require_features)  
  43.                 continue;  
  44.         }  
  45.   
  46.         if (should_check_environment &&  
  47.             event_is_method_disabled(eventops[i]->name))  
  48.             continue;  
  49.   
  50.         //找到一个满足条件的多路IO复用函数  
  51.         base->evsel = eventops[i];  
  52.   
  53.         //初始化ev_base。并且会对信号监听的处理也进行初始化  
  54.         base->evbase = base->evsel->init(base);  
  55.     }  
  56.   
  57.   
  58.   
  59. #ifndef _EVENT_DISABLE_THREAD_SUPPORT  
  60.     //测试evthread_lock_callbacks结构中的lock指针函数是否为NULL  
  61.     //即测试Libevent是否已经初始化为支持多线程模式。  
  62.     //由于一开始是用mm_calloc申请内存的,所以该内存区域的值为0  
  63.     //对于th_base_lock变量,目前的值为NULL.  
  64.     if (EVTHREAD_LOCKING_ENABLED() &&  
  65.         (!cfg || !(cfg->flags & EVENT_BASE_FLAG_NOLOCK))) { //配置是支持锁的  
  66.         EVTHREAD_ALLOC_LOCK(base->th_base_lock,  
  67.             EVTHREAD_LOCKTYPE_RECURSIVE); //申请一个锁  
  68.         base->defer_queue.lock = base->th_base_lock;  
  69.         EVTHREAD_ALLOC_COND(base->current_event_cond);//申请一个条件变量  
  70.     }  
  71. #endif  
  72.   
  73.     return (base);  
  74. }  

        这里用到了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函数。

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. //event.c文件   
  2. int  
  3. event_assign(struct event *ev, struct event_base *base, evutil_socket_t fd,   
  4.                   short events, void (*callback)(evutil_socket_t, shortvoid *), void *arg)  
  5. {  
  6.     //进行一些赋值和初始化。  
  7.     ev->ev_base = base;  
  8.     ev->ev_callback = callback;  
  9.     ev->ev_arg = arg;  
  10.     ev->ev_fd = fd;  
  11.     ev->ev_events = events;  
  12.     ev->ev_res = 0;  
  13.     ev->ev_flags = EVLIST_INIT; //初始化状态  
  14.     ev->ev_ncalls = 0;  
  15.     ev->ev_pncalls = NULL;  
  16.   
  17.     if (events & EV_SIGNAL) {  
  18.         if ((events & (EV_READ|EV_WRITE)) != 0) {  
  19.             event_warnx("%s: EV_SIGNAL is not compatible with "  
  20.                 "EV_READ or EV_WRITE", __func__);  
  21.             return -1;  
  22.         }  
  23.     }   
  24.   
  25.     ...  
  26.   
  27.     return 0;  
  28. }  

        从event_assign函数的名字可以得知它是进行赋值操作的。所以它能可以在event被初始化后再次调用。不过,初始化后再次调用的话,有些事情要注意。这个在后面的博客中会说到。

        从上面的代码可看到:如果这个event是用来监听一个信号的,那么就不能让这个event监听读或者写事件。原因是其与信号event的实现方法相抵触,具体可以参考《信号event的处理》。

        注意,此时event结构体的变量ev_flags的值是EVLIST_INIT。对变量的追踪是很有帮助的。它指明了event结构体的状态。它通过以或运算的方式取下面的值

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. //event_struct.h文件  
  2. #define EVLIST_TIMEOUT  0x01 //event从属于定时器队列或者时间堆  
  3. #define EVLIST_INSERTED 0x02 //event从属于注册队列  
  4. #define EVLIST_SIGNAL   0x04 //没有使用  
  5. #define EVLIST_ACTIVE   0x08 //event从属于活动队列  
  6. #define EVLIST_INTERNAL 0x10 //该event是内部使用的。信号处理时有用到  
  7. #define EVLIST_INIT 0x80 //event已经被初始化了  
  8.   
  9. /* EVLIST_X_ Private space: 0x1000-0xf000 */  
  10. #define EVLIST_ALL  (0xf000 | 0x9f) //所有标志。这个不能取  



将event加入到event_base中:

        创建完一个event结构体后,现在看一下event_add。它同前面的函数一样,内部也是调用其他函数完成工作。因为它用到了锁,所以给出它的代码
[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. //event.c文件  
  2. int  
  3. event_add(struct event *ev, const struct timeval *tv)  
  4. {  
  5.     int res;  
  6.   
  7.     //加锁  
  8.     EVBASE_ACQUIRE_LOCK(ev->ev_base, th_base_lock);  
  9.     res = event_add_internal(ev, tv, 0);  
  10.     //解锁  
  11.     EVBASE_RELEASE_LOCK(ev->ev_base, th_base_lock);  
  12.     return (res);  
  13. }  
  14.   
  15. static inline int  
  16. event_add_internal(struct event *ev, const struct timeval *tv,  
  17.     int tv_is_absolute)  
  18. {  
  19.     struct event_base *base = ev->ev_base;  
  20.     int res = 0;  
  21.     int notify = 0;  
  22.     ...  
  23.     if ((ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL)) &&  
  24.         !(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE))) {  
  25.         if (ev->ev_events & (EV_READ|EV_WRITE))  
  26.             res = evmap_io_add(base, ev->ev_fd, ev); //加入io队列  
  27.         else if (ev->ev_events & EV_SIGNAL)  
  28.             res = evmap_signal_add(base, (int)ev->ev_fd, ev);//加入信号队列  
  29.         if (res != -1)  
  30.             event_queue_insert(base, ev, EVLIST_INSERTED);//向event_base注册事件  
  31.     }  
  32.     ...  
  33.     return (res);  
  34. }  

        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复用函数中,使得其是可以监听的。

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. //evmap.c文件  
  2. int  
  3. evmap_io_add(struct event_base *base, evutil_socket_t fd, struct event *ev)  
  4. {  
  5.     const struct eventop *evsel = base->evsel;  
  6.     struct event_io_map *io = &base->io;  
  7.     struct evmap_io *ctx = NULL;  
  8.     int nread, nwrite, retval = 0;  
  9.     short res = 0, old = 0;  
  10.     struct event *old_ev;  
  11.   
  12.     ...  
  13.   
  14.     //GET_IO_SLOT_AND_CTOR宏的作用就是让ctx指向struct event_map_entry结构体中的TAILQ_HEAD  
  15.     //宏的展开,可以到http://blog.csdn.net/luotuo44/article/details/38403241查看  
  16.     GET_IO_SLOT_AND_CTOR(ctx, io, fd, evmap_io, evmap_io_init,  
  17.                          evsel->fdinfo_len);  
  18.   
  19.     //同一个fd可以调用event_new,event_add  
  20.     //多次。nread、nwrite就是记录有多少次。如果每次event_new的回调函数  
  21.     //都不一样,那么当fd有可读或者可写时,这些回调函数都是会触发的。  
  22.     //对一个fd不能event_new、event_add太多次的。后面会进行判断  
  23.     nread = ctx->nread;  
  24.     nwrite = ctx->nwrite;  
  25.   
  26.     if (nread)  
  27.         old |= EV_READ;  
  28.     if (nwrite)  
  29.         old |= EV_WRITE;  
  30.   
  31.     if (ev->ev_events & EV_READ) {  
  32.         //记录是不是第一次。如果是第一次,那么就说明该fd还没被  
  33.         //加入到多路IO复用中。即还没被加入到像select、epoll这些  
  34.         //函数中。那么就要加入。这个在后面可以看到。  
  35.         if (++nread == 1)  
  36.             res |= EV_READ;  
  37.     }  
  38.     if (ev->ev_events & EV_WRITE) {  
  39.         if (++nwrite == 1)  
  40.             res |= EV_WRITE;  
  41.     }  
  42.     if (EVUTIL_UNLIKELY(nread > 0xffff || nwrite > 0xffff)) {  
  43.         event_warnx("Too many events reading or writing on fd %d",  
  44.                     (int)fd);  
  45.         return -1;  
  46.     }  
  47.   
  48.   
  49.     //把fd加入到多路IO复用中。  
  50.     if (res) {  
  51.         void *extra = ((char*)ctx) + sizeof(struct evmap_io);  
  52.         if (evsel->add(base, ev->ev_fd,  
  53.                        old, (ev->ev_events & EV_ET) | res, extra) == -1)  
  54.             return (-1);  
  55.         retval = 1;  
  56.     }  
  57.   
  58.     //nread进行了++。把次数记录下来。下次对于同一个fd,这个次数就有用了  
  59.     ctx->nread = (ev_uint16_t) nread;  
  60.     ctx->nwrite = (ev_uint16_t) nwrite;  
  61.   
  62.     TAILQ_INSERT_TAIL(&ctx->events, ev, ev_io_next);  
  63.   
  64.     return (retval);  
  65. }  

        代码中有两个计数nread和nwrite,当其值为1时,就说明是第一次监听对应的事件。此时,就要把这个fd添加到多路IO复用函数中。这就完成fdselectpollepoll之类的多路IO复用函数的相关联。这完成对fd监听的第一步。

 

        下面再看event_queue_insert函数的实现。

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. //event.c文件  
  2. static void  
  3. event_queue_insert(struct event_base *base, struct event *ev, int queue)  
  4. {  
  5.     ...  
  6.   
  7.     ev->ev_flags |= queue;  
  8.     switch (queue) {  
  9.     case EVLIST_INSERTED:  
  10.         TAILQ_INSERT_TAIL(&base->eventqueue, ev, ev_next);  
  11.         break;  
  12.         ...  
  13.     }  
  14. }  

        这个函数的主要作为是把event加入到对应的队列中。在这里,是为了把event加入到eventqueue这个已注册队列中,即将event向event_base注册。注意,此时event结构体的ev_flags变量为EVLIST_INIT | EVLIST_INSERTED了。

 

进入主循环,开始监听event:        

        现在事件已经添加完毕,开始进入主循环event_base_dispatch函数。还是同样,该函数内部调用event_base_loop完成工作。

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. //event.c文件  
  2. int  
  3. event_base_loop(struct event_base *base, int flags)  
  4. {  
  5.     const struct eventop *evsel = base->evsel;  
  6.     int res, done, retval = 0;  
  7.   
  8.     //加锁  
  9.     EVBASE_ACQUIRE_LOCK(base, th_base_lock);  
  10.   
  11.     done = 0;  
  12.   
  13.     while (!done) {  
  14.         //该函数的内部会解锁,然后调用OS提供的的多路IO复用函数。  
  15.         //这个函数退出后,又会立即加锁。这有点像条件变量。        
  16.         res = evsel->dispatch(base, tv_p);  
  17.   
  18.         if (N_ACTIVE_CALLBACKS(base)) {  
  19.             int n = event_process_active(base);  
  20.         }   
  21.     }  
  22.   
  23. done:  
  24.     //解锁  
  25.     EVBASE_RELEASE_LOCK(base, th_base_lock);  
  26.     return (retval);  
  27. }  

        在event_base_loop函数内部会进行加锁,这是因为这里要对event_base里面的多个队列进行一些数据操作(增删操作),此时要用锁来保护队列不被另外一个线程所破坏。

        上面代码中有两个函数evsel->dispatch和event_process_active。前一个将调用多路IO复用函数,对event进行监听,并且把满足条件的event放到event_base的激活队列中。后一个则遍历这个激活队列的所有event,逐个调用对应的回调函数。

        可以看到整个流程如下图所示:

        



将已激活event插入到激活列表:

        我们还是深入看看Libevent是怎么把event添加到激活队列的。dispatch是一个函数指针,它的实现都包含是一个多路IO复用函数。这里选择poll这个多路IO复用函数来作分析。

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. //poll.c文件  
  2. static int  
  3. poll_dispatch(struct event_base *base, struct timeval *tv)  
  4. {  
  5.     int res, i, j, nfds;  
  6.     long msec = -1;  
  7.     struct pollop *pop = base->evbase;  
  8.     struct pollfd *event_set;  
  9.   
  10.     nfds = pop->nfds;  
  11.   
  12.     event_set = pop->event_set;  
  13.   
  14.     //解锁  
  15.     EVBASE_RELEASE_LOCK(base, th_base_lock);  
  16.     res = poll(event_set, nfds, msec);  
  17.     //再次加锁  
  18.     EVBASE_ACQUIRE_LOCK(base, th_base_lock);  
  19.   
  20.     ...  
  21.   
  22.     i = random() % nfds;   
  23.     for (j = 0; j < nfds; j++) {  
  24.         int what;  
  25.         if (++i == nfds)  
  26.             i = 0;  
  27.         what = event_set[i].revents;  
  28.         if (!what)  
  29.             continue;  
  30.   
  31.         res = 0;  
  32.   
  33.         //如果fd发生错误,就把之当作读和写事件。之后调用read  
  34.         //或者write时,就能得知具体是什么错误了。这里的作用是  
  35.         //通知到上层。  
  36.         if (what & (POLLHUP|POLLERR))  
  37.             what |= POLLIN|POLLOUT;  
  38.           
  39.         if (what & POLLIN)  
  40.             res |= EV_READ;  
  41.         if (what & POLLOUT)  
  42.             res |= EV_WRITE;  
  43.         if (res == 0)  
  44.             continue;  
  45.   
  46.         //把这个ev放到激活队列中。  
  47.         evmap_io_active(base, event_set[i].fd, res);  
  48.     }  
  49.   
  50.     return (0);  
  51. }  

        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的代码。

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. void //evmap.c文件  
  2. evmap_io_active(struct event_base *base, evutil_socket_t fd, short events)  
  3. {  
  4.     struct event_io_map *io = &base->io;  
  5.     struct evmap_io *ctx;  
  6.     struct event *ev;  
  7.   
  8.     //由这个fd找到对应event_map_entry的TAILQ_HEAD.  
  9.     GET_IO_SLOT(ctx, io, fd, evmap_io);  
  10.   
  11.     //遍历这个队列。将所有与fd相关联的event结构体都处理一遍  
  12.     TAILQ_FOREACH(ev, &ctx->events, ev_io_next) {  
  13.         if (ev->ev_events & events)  
  14.             event_active_nolock(ev, ev->ev_events & events, 1);  
  15.     }  
  16. }  
  17.   
  18. void //event.c文件  
  19. event_active_nolock(struct event *ev, int res, short ncalls)  
  20. {  
  21.     struct event_base *base;  
  22.     base = ev->ev_base;  
  23.   
  24.     ...   
  25.     //将ev插入到激活队列  
  26.     event_queue_insert(base, ev, EVLIST_ACTIVE);  
  27.   
  28.     ...  
  29. }  
  30.   
  31.   
  32. //将event 插入到event_base的对应(由queue指定)的队列里面  
  33. static void //event.c文件  
  34. event_queue_insert(struct event_base *base, struct event *ev, int queue)  
  35. {  
  36.     ...  
  37.   
  38.     ev->ev_flags |= queue;  
  39.     switch (queue) {  
  40.     case EVLIST_ACTIVE:  
  41.         base->event_count_active++;  
  42.         //将event插入到对应对应优先级的激活队列中  
  43.         TAILQ_INSERT_TAIL(&base->activequeues[ev->ev_pri],  
  44.             ev,ev_active_next);  
  45.         break;  
  46.     }  
  47. }  

        经过上面三个函数的调用,就可以把一个fd对应的所有符合条件的event插入到激活队列中。因为Libevent还对事件处理设有优先级,所以有一个激活数组队列,而不是只有一个激活队列。

        注意,此时event结构体的ev_flags变量为EVLIST_INIT | EVLIST_INSERTED | EVLIST_ACTIVE了。



处理激活列表中的event:

        现在已经完成了将event插入到激活队列中。接下来就是遍历激活数组队列,把所有激活的event都处理即可。

        现在来追踪event_process_active函数。

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. //event.c文件  
  2. static int  
  3. event_process_active(struct event_base *base)  
  4. {  
  5.     struct event_list *activeq = NULL;  
  6.     int i, c = 0;  
  7.   
  8.     //从高优先级到低优先级遍历优先级数组  
  9.     for (i = 0; i < base->nactivequeues; ++i) {  
  10.         //对于特定的优先级,遍历该优先级的所有激活event  
  11.         if (TAILQ_FIRST(&base->activequeues[i]) != NULL) {  
  12.             activeq = &base->activequeues[i];  
  13.             c = event_process_active_single_queue(base, activeq);  
  14.             ...  
  15.         }  
  16.     }  
  17.     return c;  
  18. }  
  19.   
  20. static int  
  21. event_process_active_single_queue(struct event_base *base,  
  22.     struct event_list *activeq)  
  23. {  
  24.     struct event *ev;  
  25.     int count = 0;  
  26.   
  27.     for (ev = TAILQ_FIRST(activeq); ev; ev = TAILQ_FIRST(activeq)) {  
  28.         //如果是永久事件,那么只需从active队列中删除。  
  29.         if (ev->ev_events & EV_PERSIST)  
  30.             event_queue_remove(base, ev, EVLIST_ACTIVE);  
  31.         else //不是的话,那么就要把这个event删除掉。  
  32.             event_del_internal(ev);  
  33.         if (!(ev->ev_flags & EVLIST_INTERNAL))  
  34.             ++count;  
  35.   
  36.         //下面开始处理这个event  
  37.         switch (ev->ev_closure) {  
  38.         ...   
  39.         case EV_CLOSURE_NONE:             
  40.             //调用用户设置的回调函数。  
  41.             (*ev->ev_callback)(ev->ev_fd, ev->ev_res, ev->ev_arg);  
  42.             break;  
  43.         }  
  44.   
  45.         EVBASE_ACQUIRE_LOCK(base, th_base_lock);  
  46.   
  47.     }  
  48.     return count;  
  49. }  

        上面的代码,从高到低优先级遍历激活event优先级数组。对于激活的event,要调用event_queue_remove将之从激活队列中删除掉。然后再对这个event调用其回调函数。

        event_queue_remove函数的调用会改变event结构体的ev_flags变量的值。调用后,  ev_flags 变量为 EVLIST_INIT | EVLIST_INSERTED 现在又可以等待下一次事件的到来了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值