高性能网络库分析1——libevent

libevent事件循环分析

libevent库事件循环封装在event_base_loop()函数中,函数的主循环流程:

while(1){

  if(base中有事件或loop需要阻塞){

    timeout_next(base, &tv_p); // timer heap中取根节点的超时时间,作为IO复用的阻塞等待时间tv_ptv_p指向tv

  }

  else // 无需阻塞IO复用等待

    evutil_timerclear(&tv) //IO复用阻塞等待时长清0-

  

  clear_time_cache(base); // 清除base对象中系统时间缓存tv_catch

  1) evsel->dispatch(base, tv_p); // 调用select(), poll(), epoll()IO复用系统调用, IOsignal激活事件检测

  update_time_catch(base); // 跟新base对象的系统时间缓存

  2) timeout_process(base); //定时器激活事件检测

  

  3) event_process_active(base); // 处理激活的事件

}

看起来过程很复杂,实际上归纳一下,一轮循环也就分为两个阶段:

1)激活事件检测阶段

    其中,由于libevent时间管理的策略原因,激活事件检测阶段又分为两个子阶段:
    1.1)定时器激活事件检测,evsel->dispatch(base, tv_p) ,参数二为io复用阻塞超时值;
    1.2)IO事件及signal事件检测;timeout_process(base)。

2)激活事件处理阶段
    遍历各级激活事件优先级队列,逐队列逐事件调用事件回调函数,处理事件。

 

首先,谈下libevent库中时间管理的策略

libevent采用monotonic时间,作为定时器是否激活的依据。(CLOCK_MONOTONIC:以绝对时间为准,获取的时间为系统重启到现在的时间,更改系统时间对它没有影响。)

用户调用,

evtimer_set(&ev,timeer_cb, NULL)

构造一个timer事件,然后调用,

event_add(&ev,&tv);

为定时器事件设置超时值,在event_add()函数内部会调用event_add_nolock_()函数判断插入事件的类型,然后将事件插入对因事件列表或者堆中,其中在处理定时器事件时,操作如下

int event_add_nolock_(struct event *ev, const struct timeval *tv, int tv_is_absolute){

  

  if (res != -1 && tv != NULL){ // 表明事件类型为定时器

    

    // 将当前MONOTONIC时间+用户设定的定时器超时值作为定时器超时激活的绝对时间

    evutil_timeradd(&now, tv, &ev->ev_timeout);

    

    // 将定时器事件插入到小根堆中

    event_queue_insert_timeout(base, ev);

  }

  

}

可以看到,实际上事件真正的超时值是一个绝对时间。所以,若将libevent事件循环看做一个生命周期,定时器事件则是生命周期中的固定点(同步点),而IO&signal事件由于是异步发生的,所以是生命周期中的异步点,如下图:

libevent定时器与muduo定时器的区别:

libevent库中定时器机采用一种弱定时器机制,利用select()等IO复用系统调用的阻塞时长来实现定时器功能,因此使定时器具备平台无关性,但缺点使不太精确,且要将定时器事件的处理与其他事件分开处理。

而muduo库针对linux平台设计,定时器采用timerfd定时器文件描述符,虽然在精准度上也没比libevent提高多少,但由于使用文件描述符,使得定时器事件的处理与IO,文件等事件的处理统一,利于开发。

回到事件主循环,简单分析下2个阶段核心函数的操作

1)evsel->dispatch()

这个部分,实际上就是封装了IO复用那一套东西,根据平台对IO复用的支持,libevent可以使用select(),poll(),epoll(),kqueue()。网上有大量相关资料,不再赘述。

2)timerout_process(base)

首先,这个函数的命名方式非常容易引起歧义,一开始我以为这个函数是为了处理因超时被激活的定时器事件,结果阅读源码后发现这个原来是检测timer heap中哪些定时器事件超时并激活他们的函数。和evsel->dispatch()同属激活事件检测阶段…,下面是关键代码:

timeout_process(base){

  gettime(base, &now); // 获取当前系统绝对时间

  while(不断从timer heap根取定时器事件用于检测){

    if(evutil_timercmp(ev->ev_timeout, now, >)) {

      // 这是一个宏,实际就是比对一个定时器事件的超时时间与当前时间,若堆顶定时器超时时间晚于当前时间,说明堆中无能激活事件,退出循环。

      break;

    }

    // else // 存在能激活的定时器事件,处理描述如下

    {

      /*首先,将事件从timer heap中取下,

        然后,检测事件是否已在激活队列,若在就从激活队列中移除(重置事件)*/

      event_del_nolock_(ev, EVENT_DEL_NOBLOCK);

      /*最后,将定时器事件插入到激活队列中等待处理*/

      event_active_nolock_(ev,EV_TIMEOUT,1);

    }

  }

}

3event_process_active(base)

event_process_active()函数处理经过evsel->dispatch()函数检测过得的激活事件。libevent库将激活事件按优先级组织为多个优先级链表。event_process_active函数的职责就是按优先级从高到低处理激活事件(调用其事件句柄)。

event_process_active(){

  for(inti=0;i<base→nativequeues;i++){

    activeq= &base->activequeues[i];

    //参数二为回调函数为某一优先级梯队的激活事件回调函数链表,参数三为数组最大回调函数个数,函数处理一个优先级梯队下所有激活事件

    event_process_active_single_queue(base,activeq,maxcb,…)

  }

}

其中event_process_active_single_queue处理一个优先级梯度下所有激活事件,过程如下:

structevent_callback *evcb;

for(/*遍历回调函数列表-> evcb*/){

  /*先从激活队列中删除事件*/

  event_queue_remove_active(base,evcb);

  /*然后根据evcb对象的类型调用事件句柄*/

  switch(evcb→evcb_closure){ //此处用到closure一词,可能作者思路是事件关闭前调用的意思

    caseEV_CLOSURE_EVENT_SIGNAL:

  event_signal_closure(base,ev); //处理signal

    break;

  caseEV_CLOSURE_EVENT_PERSIST:

    event_persist_closure(base,ev); //处理周期性定时器事件

    break;

  caseEV_CLOSURE_EVENT:

    evcb_callback(ev->ev_fd,res, ev→ev_arg); // IO事件,timer事件

    break;

  ...

  }

}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值