码迷,mamicode.com
首页 > 其他好文 > 详细

libevent高性能网络库源码分析——事件处理框架(四)

时间:2016-04-22 21:01:01      阅读:371      评论:0      收藏:0      [点我收藏+]

标签:

libevent中基于Reactor模式的事件处理框架对应event_base,在event在完成创建后,需要向event_base注册事件,监控事件的当前状态,当事件状态为激活状(EV_ACTIVE)时,调用回调函数执行。本文主要从以下几方面进行分析:event_base的结构,event_base的创建,事件的注册、事件分发、事件注销

event_base结构

struct event_base {
  //指定某个eventop结构体,它决定了该event_base使用哪种I/O多路复用技术(注解1)
  const struct eventop *evsel;
  void *evbase;

  //告知后端下次执行事件分发时需要注意的哪些事件
  struct event_changelist changelist;

  //一个eventop,专门用来处理信号事件的
  const struct eventop *evsigsel;
  //存储信号处理的信息
  struct evsig_info sig;

  //虚拟事件的个数
  int virtual_event_count;
  //总事件个数
  int event_count;
  //活跃事件个数
  int event_count_active;

  //处理完当前所有的事件之后退出
  int event_gotterm;
  //立即退出
  int event_break;
  //立即启动一个新的事件循环
  int event_continue;

  //当前运行事件的优先级
  int event_running_priority;

  //是否正在进行事件循环
  int running_loop;

  //活跃事件的链表
  struct event_list *activequeues;
  //活跃事件的个数
  int nactivequeues;

  //要延迟处理的事件队列
  struct deferred_cb_queue defer_queue;

  //IO事件队列
  struct event_io_map io;

  //信号事件队列
  struct event_signal_map sigmap;

  //所有注册事件的队列
  struct event_list eventqueue;

  //管理定时事件的最小堆
  struct min_heap timeheap;

  //IO就绪的时候和缓存时间
  struct timeval event_tv;
  struct timeval tv_cache;

  ......
};

event_base的初始化

创建event_base对象的过程也即创建了一个libevent实例,需要通过event_base_new()函数分配并创建一个具有默认配置的event_base,而event_base_new调用event_base_new_with_config(…)创建event_base。

struct event_base * event_base_new(void)
{
    struct event_base *base = NULL;
    struct event_config *cfg = event_config_new();
    if (cfg) {
        base = event_base_new_with_config(cfg);
        event_config_free(cfg);
    }
    return base;
}

从上面可以看出,首先创建一个具有默认配置的event_config,因此若需要对event_base进行配置,可以通过配置cfg达到。下面先看下event_config的结构体定义:

struct event_config {
  TAILQ_HEAD(event_configq, event_config_entry) entries;
  int n_cpus_hint;
  enum event_method_feature require_features;
  enum event_base_config_flag flags;
};
  • entries
    TAILQ_HEAD表示一个队列,队列的元素的类型为event_config_entry。libevent是基于跨平台的,其会对IO多路复用函数(select, evport, poll, epoll等)进行封装,根据操作系统选择最高效的IO复用函数。event_config_avoid_method可以配置屏蔽使用指定的IO多路复用函数。通过字符串的方式指定method。
  • n_cpus_hint
    指明CPU的数量,可通过event_config_set_num_cpus_hint函数来设置的。其作用是告诉event_config,系统中有多少个CPU,以便作一些对线程池作一些调整来获取更高的效率。目前,仅仅Window系统的IOCP(Windows的IOCP能够根据CPU的个数智能调整),该函数的设置才有用。event_base实际使用的CPU个数不一定等于提示的个数。
  • require_features
    指明了event_config要求的特征,用于指定IO多路复用函数所需要的特征,不同IO复用函数其需要的特征不一样,如果IO复用函数无法满足配置的特征,那么配置失败。各个IO复用函数支持的特征,可以从select.c, poll.c, epoll.c等源文件查看。
//event.h文件
enum event_method_feature {
    EV_FEATURE_ET = 0x01, //支持边沿触发

    //添加、删除、或者确定哪个事件激活这些动作的时间复杂度都为O(1)
    //select、poll是不能满足这个特征的,而epoll则满足
    EV_FEATURE_O1 = 0x02,

    EV_FEATURE_FDS = 0x04 //支持任意的文件描述符,而不能仅仅支持套接字
};


  • flags

可以通过event_config_set_flag函数进行设置。
  • EVENT_BASE_FLAG_NOLOCK:不要为event_base分配锁。设置这个选项可以为event_base节省一点加锁和解锁的时间,但是当多个线程访问event_base会变得不安全
  • EVENT_BASE_FLAG_IGNORE_ENV:选择多路IO复用函数时,不检测EVENT_*环境变量。使用这个标志要考虑清楚:因为这会使得用户更难调试程序与Libevent之间的交互
  • EVENT_BASE_FLAG_STARTUP_IOCP:仅用于Windows,这使得Libevent在启动时就启用任何必需的IOCP分发逻辑,而不是按需启用
  • EVENT_BASE_FLAG_NO_CACHE_TIME:不是在event loop每次准备执行timeout回调函数时检测当前时间,而是在每次执行timeout回调函数后都进行检测,这将消耗更多的CPU时间
  • EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST:告知Libevent,如果决定使用epoll这个多路IO复用函数,可以安全地使用更快的基于changelist 的多路IO复用函数:epoll-changelist多路IO复用可以在多路IO复用函数调用之间,同样的fd 多次修改其状态的情况下,避免不必要的系统调用。但是如果传递任何使用dup()或者其变体克隆的fd给libevent,epoll-changelist多路IO复用函数会触发一个内核bug,导致不正确的结果。在不使用epoll这个多路IO复用函数的情况下,这个标志是没有效果的。也可以通过设置EVENT_EPOLL_USE_CHANGELIST 环境变量来打开epoll-changelist选项。
  • struct event_base *
    event_base_new_with_config(const struct event_config *cfg)
    {
        int i;
        struct event_base *base;
        int should_check_environment;
    
        // event_base空间分配
        if ((base = mm_calloc(1, sizeof(struct event_base))) == NULL) {
            event_warn("%s: calloc", __func__);
            return NULL;
        }
        detect_monotonic();
        gettime(base, &base->event_tv);
    
        // 超时小根堆初始化,即base指向min-heap的指针,小根堆大小设置为0
        min_heap_ctor(&base->timeheap);
    
        // event_base的事件注册队列初始化
        TAILQ_INIT(&base->eventqueue);
        base->sig.ev_signal_pair[0] = -1;
        base->sig.ev_signal_pair[1] = -1;
        base->th_notify_fd[0] = -1;
        base->th_notify_fd[1] = -1;
    
        // event_base事件的回调函数队列初始化
        event_deferred_cb_queue_init(&base->defer_queue);
        base->defer_queue.notify_fn = notify_base_cbq_callback;
        base->defer_queue.notify_arg = base;
        if (cfg)
            base->flags = cfg->flags;
    
        // 初始化event_io_map, event_signal_map, changelist
        evmap_io_initmap(&base->io);
        evmap_signal_initmap(&base->sigmap);
        event_changelist_init(&base->changelist);
    
        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) {
                // 检查IO多路复用的方法是否需要屏蔽
                if (event_config_is_avoided_method(cfg,
                    eventops[i]->name))
                    continue;
    
                // 检查IO复用函数是否支持cfg中设置的特征
                if ((eventops[i]->features & cfg->require_features)
                    != cfg->require_features)
                    continue;
            }
    
            /* also obey the environment variables */
            if (should_check_environment &&
                event_is_method_disabled(eventops[i]->name))
                continue;
    
            base->evsel = eventops[i];
    
            base->evbase = base->evsel->init(base);
        }
    
        ....
    
        /* allocate a single active event queue */
        if (event_base_priority_init(base, 1) < 0) {
            event_base_free(base);
            return NULL;
        }
    
        ....
        return (base);
    }

    接口函数

    event_base管理事件主要通过以下五个接口函数:

    int event_add(struct event *ev, const struct timeval *timeout);
    int event_del(struct event *ev);
    int event_base_loop(struct event_base *base, int loops);
    void event_active(struct event *event, int res, short events);
    void event_process_active(struct event_base *base);

    2 . 添加事件 (event_add)

    int event_add(struct event *ev, const struct timeval *tv) 
    {
        int res;
        // check event是否有event_base,无
        if (EVUTIL_FAILURE_CHECK(!ev->ev_base)) {...;return -1;}
    
        // 获取event_base的th_base_lock锁
        EVBASE_ACQUIRE_LOCK(ev->ev_base, th_base_lock);
    
        /* 此处为event_add_internal函数的展开 */
        // tv不为NULL,就说明是一个超时event,在小根堆中为其留一个位置  
        if (tv != NULL && !(ev->ev_flags & EVLIST_TIMEOUT)) {
            if (min_heap_reserve(&base->timeheap,
                1 + min_heap_size(&base->timeheap)) == -1)
                return (-1);  /* ENOMEM == errno */
        }
    
        /* If the main thread is currently executing a signal event‘s
         * callback, and we are not the main thread, then we want to wait
         * until the callback is done before we mess with the event, or  
         * else we can race on ev_ncalls and ev_pncalls below.
         */
        if (base->current_event == ev && (ev->ev_events & EV_SIGNAL)
            && !EVBASE_IN_THREAD(base)) {
            ++base->current_event_waiters; // 等待事件数 +1,进入条件等待
            EVTHREAD_COND_WAIT(base->current_event_cond, base->th_base_lock); 
        }
    
        // IO事件,添加到event_io_map中
        res = evmap_io_add(base, ev->ev_fd, ev);
    
        // 信号事件,添加到event_signal_map中
        res = evmap_signal_add(base, (int)ev->ev_fd, ev);
    
        // 无论IO事件还是signal事件,添加到event queue中
        event_queue_insert(base, ev, EVLIST_INSERTED);
    
        if (res != -1 && tv != NULL) {  
            struct timeval now;
    
            //用户把这个event设置成EV_PERSIST,即为永久event,可以发生多次超时.  
            //需要记录用户设置的超时值,tv_is_absolute = 0表示使用相对时间
            if (ev->ev_closure == EV_CLOSURE_PERSIST && !tv_is_absolute)
                ev->ev_io_timeout = *tv;
    
            //该event之前被加入到超时队列。用户可以对同一个event调用多次event_add  
            //并且可以每次都用不同的超时值。
            if (ev->ev_flags & EVLIST_TIMEOUT) {  
                /* XXX I believe this is needless. */  
                //之前为该event设置的超时值是所有超时中最小的。
                //从下面的删除可知,会删除这个最小的超时值。此时多路IO复用函数  
                //的超时值参数就已经改变了。  
                if (min_heap_elt_is_top(ev))  
                    notify = 1; //要通知主线程。可能是次线程为这个event调用本函数  
    
                //从超时队列中删除这个event。
                //多次对同一个超时event调用event_add,那么只能保留最后的那个。  
                event_queue_remove(base, ev, EVLIST_TIMEOUT);  
            } 
    
            //若正在event_add的event由于超时而被激活,需要从active queue中将该event移除  
            if ((ev->ev_flags & EVLIST_ACTIVE) &&  
                (ev->ev_res & EV_TIMEOUT)) {//该event被激活的原因是超时  
    
                ...  
                event_queue_remove(base, ev, EVLIST_ACTIVE);  
            } 
    
            //获取当前时间
            gettime(base, &now);  
    
            //虽然用户在event_add时只需用一个相对时间,但实际上在Libevent内部  
            //还是要把这个时间转换成绝对时间。从存储的角度来说,存绝对时间只需  
            //一个变量。而相对时间则需两个,一个存相对值,另一个存参照物。  
            if (tv_is_absolute) { //该参数指明时间是否为一个绝对时间  
                ev->ev_timeout = *tv;  
            } else {  
                //参照时间 + 相对时间  ev_timeout存的是绝对时间  
                evutil_timeradd(&now, tv, &ev->ev_timeout);  
            }  
    
            //将该超时event插入到超时队列中
            event_queue_insert(base, ev, EVLIST_TIMEOUT);
    
            //若本次插入的超时值,是所有超时中最小的。那么此时就需要通知主线程。  
            if (min_heap_elt_is_top(ev))  
                notify = 1;
        }
    
        //如果需要通知,且本线程不是主线程,则需通知主线程 
        if (res != -1 && notify && EVBASE_NEED_NOTIFY(base))
            evthread_notify_base(base);
    
        /* 此处为event_add_internal函数的结束 */
    
        // 释放event_base的th_base_lock锁
        EVBASE_RELEASE_LOCK(ev->ev_base, th_base_lock);
    
        return (res);
    }

    注: 1、对于同一个event,若为IO event、信号event,那么将无法多次添加。若为超时event,则可进行多次添加,并且超时值为最后一次设置的超时大小。
    2、notify变量。主线程在执行event_base_dispatch,此时多次执行event_add,并且超时值发生了改变,那么需要更新event的超时值设置,并且以最后一次event_add为准,且通知主线程evthread_notify_base。

    3 . 删除事件 (event_del)

    int event_del(struct event *ev)
    {
        ....
        // 获取event_base的th_base_lock锁
        EVBASE_ACQUIRE_LOCK(ev->ev_base, th_base_lock);
    
        /* 此处展开event_del_internal函数*/
        struct event_base *base;
        int res = 0, notify = 0;
        ....
    
        // 主线程正在执行将要删除的event,且当前线程不是主线程,那么需要进入等待,
        // 直至主线程完成event的回调执行,
        base = ev->ev_base;
        if (base->current_event == ev && !EVBASE_IN_THREAD(base)) {
            ++base->current_event_waiters;
            EVTHREAD_COND_WAIT(base->current_event_cond, 
                                base->th_base_lock);
        }
    
        // 若当前event为信号事件,且正在循环执行,那么应该停止循环
        /* See if we are just active executing this event in a loop */
        if (ev->ev_events & EV_SIGNAL) {
            if (ev->ev_ncalls && ev->ev_pncalls) {
                /* Abort loop */
                *ev->ev_pncalls = 0;
            }
        }
    
        // 若event为定时事件,则需要从超时队列中将事件删除
        if (ev->ev_flags & EVLIST_TIMEOUT) {
            event_queue_remove(base, ev, EVLIST_TIMEOUT);
        }
    
        // 若event的状态为激活状态,则需要从激活队列中将事件删除
        if (ev->ev_flags & EVLIST_ACTIVE)
            event_queue_remove(base, ev, EVLIST_ACTIVE);
    
        // 若event的状态为插入状态,则需要从event queue将事件删除
        if (ev->ev_flags & EVLIST_INSERTED) {
            event_queue_remove(base, ev, EVLIST_INSERTED);
            if (ev->ev_events & (EV_READ|EV_WRITE))
                res = evmap_io_del(base, ev->ev_fd, ev); // 从event_io_map删除event
            else
                // 从event_signal_map删除event
                res = evmap_signal_del(base, (int)ev->ev_fd, ev); 
            if (res == 1) {
                // 删除成功需要通知主线程
                notify = 1;
                res = 0;
            }
        }
    
        //如果需要通知,且本线程不是主线程,则需通知主线程
        if (res != -1 && notify && EVBASE_NEED_NOTIFY(base))
            evthread_notify_base(base);
    
        /* 此处结束event_del_internal函数*/
    
        // 释放event_base的th_base_lock锁
        EVBASE_RELEASE_LOCK(ev->ev_base, th_base_lock);
    
        return (res);
    }

    本文参考:
    1. libevent源码深度剖析
    2. Libevent源码分析—–配置event_base
    3. libevent2.0源码学习三:事件循环的一生

    libevent高性能网络库源码分析——事件处理框架(四)

    标签:

    原文地址:http://blog.csdn.net/wzgang123/article/details/51191718

    (0)
    (0)
       
    举报
    评论 一句话评论(0
    登录后才能评论!
    © 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
    迷上了代码!