libevent学习笔记【使用篇】——4. 让events正常工作

  • 一构建event对象
      • event标志
  • 二事件持久性 EV_PERSIST
  • 三 创建一个可以将自身作为回调函数参数的的event
  • 四纯超时events
  • 五 构造信号事件
  • 六不在堆中分配event
  • 七将events置为挂起或者非挂起
  • 八事件的优先级
  • 九检查event状态
  • 十找到当前正在运行的event
  • 十一配置一次性的events
  • 十二手动激活event
  • 十三优化一般性超时
  • 十四从已清除的内存识别事件
  • 十五过时的event处理函数

翻译自:http://www.wangafu.net/~nickm/libevent-book/Ref4_event.html
文章出处 http://blog.csdn.net/windeal3203/article/details/52772233
Libevents的基本操作单元是event,每一个event代表了一些条件的集合,这些条件包括:

  • 文件描述符已经准备好读或写
  • 文件描述符正在变为就绪,准备好读或写(仅限于边沿触发)
  • 超时事件
  • 信号发生
  • 用户触发事件

      events都有类似的生命周期。一旦调用Libevent函数创建好event,并将其关联到一个event_base之后,他就是“已初始化”状态(initialized)。这种状态下,可以进行add操作,将其状态变为base中的“挂起”状态(pending),处于“挂起”状态的event,如果触发事件的条件发生了(比如,文件描述符的状态发生变化,或者超时了),那么event的状态变为“激活”状态(active),然后它的回调函数(用户提供)开始运行。如果该event配置了“持久”属性(persistent),那么它的状态依然保持为“挂起”,否则,在回调函数运行时,它的状态就不再是“挂起”(“非挂起”状态)。可以通过delete操作,将一个“挂起”状态的event变为“非挂起”状态(non-pending),或者通过add操作,将“非挂起”的event变为“挂起”状态。

一、构建event对象

  创建新的event,可以使用event_new接口:

#define EV_TIMEOUT      0x01
#define EV_READ         0x02
#define EV_WRITE        0x04
#define EV_SIGNAL       0x08
#define EV_PERSIST      0x10
#define EV_ET           0x20

typedef void  (*event_callback_fn)(evutil_socket_t,  short,  void*);

struct event * event_new(struct  event_base *base,  evutil_socket_t  fd,
                                                    short what,  event_callback_fn  cb,
                                                    void* arg);

void  event_free(struct  event * event);

   event_new函数分配并且创建一个新的event对象,并与base进行关联。what参数是上面列出标志的集合,它们的具体意义见下方。如果fd是非负的整数,则它代表了我们需要观察可读或可写事件的文件。当event变为激活时,Libevent就会调用回调函数cb,将文件描述符参数fd,所有触发事件的标志位域,以及event_new的最后一个参数:arg传递给cb
  如果发生了内部错误,或者参数非法,则event_new返回NULL
  所有新的events都是“已初始化”和“非挂起”状态,可以调用event_add函数将这样的event变为“挂起”状态。
  调用event_free可以销毁event。对“挂起”或“激活”状态的event调用event_free也是安全的:在销毁它之前,会将其变为“非挂起”以及“非激活”状态。

  上述函数在文件中定义。
  

event标志:

  • EV_TIMEOUT:
    该标志表明,超时时间过后,该event变为“激活”状态。(注意:在构建event时,EV_TIMEOUT标志是被忽略的:当add event时可以设置超时时间,也可以不设置。当超时发生时,回调函数的what参数将会设置该标志。)
  • EV_READ:
    该标志表明,当文件描述符准备好读时,event将会变为“激活”
  • EV_WRITE:
    该标志表明,当文件描述符准备好写时,event将会变为“激活”
  • EV_SIGNAL:
    用来实现信号探测,参见下面的“构造信号事件”
  • EV_PERSIST:
    标志该event具有“持久”属性,参见下面的“事件持久性”
  • EV_ET:
    指明如果event_base的底层方法支持边沿触发的话,那么该event应该是边沿触发的。这将会影响到EV_READEV_WRITE

      自Libevent2.0.1-alpha版本以来,同一时刻,针对同一个文件描述符,可以有任意数量的event在同样的条件上“挂起”。比如,当给定的fd变为可读时,可以使两个events都变为激活状态。但是他们的回调函数的调用顺序是未定义的。

      所有这些标志都在中定义。

Examples

#include 

void  cb_func(evutil_socket_t  fd,  short what,  void * arg)
{
        const char *data = arg;
        printf("Got an event on socket %d:%s%s%s%s [%s]",
            (int) fd,
            (what&EV_TIMEOUT) ? " timeout" : "",
            (what&EV_READ)    ? " read" : "",
            (what&EV_WRITE)   ? " write" : "",
            (what&EV_SIGNAL)  ? " signal" : "",
            data);
}

void  main_loop(evutil_socket_t  fd1,  evutil_socket_t fd2)
{
        struct  event *ev1, *ev2;
        struct  timeval  five_seconds = {5,0};
        struct  event_base * base = event_base_new();

        /* The caller has already set up fd1,fd2 somehow, and make them
           nonblocking. */

        ev1 = event_new(base,  fd1,  EV_TIMEOUT|EV_READ|EV_PERSIST,  cb_func,
           (char*)"Reading event");
        ev2 = event_new(base,  fd2,  EV_WRITE|EV_PERSIST,  cb_func,
           (char*)"Writing event");

        event_add(ev1,  &five_seconds);
        event_add(ev2,  NULL);
        event_base_dispatch(base);
}

二、事件持久性 EV_PERSIST

  默认情况下,当一个“挂起”的event变为“激活”时(要么是因为fd准备好读或写,要么是超时时间到),那么在它的回调函数执行之前,它就会变为“非挂起”状态。因此,如果希望再次使event变为“挂起”状态,可以在回调函数内部再次调用event_add函数。
  如果event设置了EV_PERSIST标志,那么event就是“持久”的。这意味着event在回调函数激活的时候,依然保持“挂起”状态。如果希望在回调函数中将event变为“非挂起”状态,则可以调用event_del函数。

  当event的回调函数运行时,“持久”event的超时时间就会被重置。因此,如果某个event标志为EV_READ|EV_PERSIST,并且将超时时间设置为5秒,则该event在下面的条件发生时,会变为“激活”:当该socket准备好读时; 距离上次event变为激活状态后,又过了5秒钟.
  

三 、创建一个可以将自身作为回调函数参数的的event

  经常可能会希望创建这样一个event,它本身就是是回调函数的参数之一。不能仅仅传递一个指向event的指针作为event_new的参数,因为彼时它还没有创建。此时,可以通过调用event_self_cbarg函数解决这样的问题。
void*event_self_cbarg();
  该函数返回一个“魔术”指针,使得event_new创建一个本身就能作为回调函数参数的event

#include 

static int  n_calls = 0;

void  cb_func(evutil_socket_t  fd,  short what,  void * arg)
{
    struct  event *me = arg;

    printf("cb_func  called  %d times  so far.\n",  ++n_calls);

    if (n_calls > 100)
       event_del(me);
}

void  run(struct  event_base * base)
{
    struct  timeval  one_sec = { 1, 0 };
    struct  event *ev;
    /* We're going to set up a repeating timerto get called 100 times. */
   ev = event_new(base,  -1,  EV_PERSIST, cb_func,  event_self_cbarg());
    event_add(ev,  &one_sec);
    event_base_dispatch(base);
}

   该函数还可以与函数event_new,evtimer_new, evsignal_new, event_assign, evtimer_assignevsignal_assign一起使用。然而对于非event来说,他不会作为回调函数的参数。

四、纯超时events

  方便起见,Libevent提供了一系列以evtimer_开头的宏,这些宏可以代替event_*函数,来分配和操作纯超时events。使用这些宏仅能提高代码的清晰度而已。

#define evtimer_new(base,  callback,  arg)   event_new((base), -1, 0, (callback), (arg))
#define evtimer_add(ev,  tv)                              event_add((ev),(tv))
#define evtimer_del(ev)                                                            event_del(ev)
#define evtimer_pending(ev,  tv_out)   event_pending((ev), EV_TIMEOUT, (tv_out))

五 、构造信号事件

  Libevent也可以监控POSIX类的信号。构建一个信号处理函数,可以使用下面的接口:

#define evsignal_new(base,  signum,  cb,  arg)\
    event_new(base,  signum,  EV_SIGNAL|EV_PERSIST,  cb,  arg)

  除了提供一个代表信号值的整数,而不是一个文件描述符之外。它的参数与event_new是一样的。

struct event * hup_event;
struct event_base  *base = event_base_new();

/*call sighup_function on a HUP signal */
hup_event= evsignal_new(base,  SIGHUP,  sighup_function,  NULL);

  注意:信号回调函数是在信号发生之后,在eventloop中调用的。所以,它们可以调用那些,对于普通POSIX信号处理函数来说不是信号安全的函数。
  注意:不要在一个信号event上设置超时,不支持这样做。

  对于信号event,同样有一些方便的宏可以使用:

#define evsignal_add(ev,  tv)                             event_add((ev), (tv))
#define evsignal_del(ev)                                        event_del(ev)
#define evsignal_pending(ev,  what,  tv_out)  event_pending((ev), (what), (tv_out))

  警告:当前版本的Libevent,对于大多数的后端方法来说,同一时间,每个进程仅能有一个event_base可以用来监听信号。如果一次向两个event_base添加event,即使是不同的信号,也仅仅会只有一个event_base可以接收到信号。对于kqueue来说,不存在这样的限制。

六、不在堆中分配event

  出于性能或者其他原因的考虑,一些人喜欢将event作为一个大的结构体的一部分进行分配。对于这样的event,它节省了:

  • 内存分配器在堆上分配小对象的开销;
  • event指针的解引用的时间开销;
  • 如果event没有在缓存中,缓存不命中的时间开销。

      这种方法的风险在于,与其他版本的Libevent之间不满足二进制兼容性,他们可能具有不同的event大小。

      这些开销都非常小,对于大多数应用来说是无关紧要的。除非确定知道,应用程序因为使用堆分配的event而存在严重的性能损失,否则应该坚持实用event_new 如果后续版本的Libevent使用比当前Libevent更大的event结构,那么使用event_assign有可能会导致难以诊断的错误。

int  event_assign(struct  event * event, struct  event_base * base,
                                 evutil_socket_t fd,  short  what,
                                 void(*callback)(evutil_socket_t,  short,  void *),  void * arg);

  event_assign的参数与event_new相同,除了event参数,该参数指针必须指向一个未初始化的event。该函数成功时返回0,失败时返回-1.

#include 
/*Watch out! Including event_struct.h means that your code willnot
 * be binary-compatible with future versions ofLibevent. */
#include 
#include 

struct event_pair {
         evutil_socket_t  fd;
         struct event  read_event;
         struct event  write_event;
};
void  readcb(evutil_socket_t,  short,  void*);
void  writecb(evutil_socket_t,  short,  void*);
struct event_pair * event_pair_new(struct  event_base * base,  evutil_socket_t  fd)
{
        struct  event_pair  *p = malloc(sizeof(struct  event_pair));
        if (!p) return NULL;
        p->fd = fd;
        event_assign(&p->read_event,  base,  fd, EV_READ|EV_PERSIST,  readcb,  p);
        event_assign(&p->write_event,  base,  fd,  EV_WRITE|EV_PERSIST,writecb, p);
        return  p;
}

  同样可以使用event_assign来初始化栈或者静态存储区中的events。

警告:对于已经在event_base中处于“挂起”状态的event,永远不要调用event_assign。这样做会导致极为难以诊断的错误。如果event已经初始化,并且处于“挂起”状态,那么在调用event_assign之前应该先调用event_del。*

  对于使用event_assign分配的纯超时event或者信号event,同样有方便的宏可以使用:

#define evtimer_assign(event,  base,  callback, arg) \
              event_assign(event, base,  -1,  0,  callback,  arg)
#define evsignal_assign(event,  base,  signum, callback,  arg) \
              event_assign(event, base,  signum,  EV_SIGNAL|EV_PERSIST,  callback,  arg)

  如果需要在与未来版本的Libevent保持二进制兼容性的同时,使用event_assign,可以调用Libevent中的函数,得到运行时的event结构大小:

size_t event_get_struct_event_size(void);

  该函数返回需要为event结构预留的字节数。再次提醒,只有在确定堆分配导致很明显的性能问题时,才应该使用该函数,因为它使你的代码难读又难写。
  注意,将来版本的event_get_struct_event_size()的返回值可能比sizeof(structevent)小,这意味着event结构的末尾的额外字节仅仅是保留用于未来版本的Libevent的填充字节。
  下面是一个使用event_get_struct_size的例子:

#include 
#include 

/*When we allocate an event_pair in memory, we'll actually allocate
 * more space at the end of the structure.  We define some macros
 * to make accessing those events lesserror-prone. */
struct event_pair {
         evutil_socket_t  fd;
};

/*Macro: yield the struct event 'offset' bytes from the start of 'p' */
#define EVENT_AT_OFFSET(p,  offset) \
            ((struct  event*) ( ((char*)(p)) + (offset) ))
/*Macro: yield the read event of an event_pair */
#define READEV_PTR(pair) \
            EVENT_AT_OFFSET((pair),  sizeof(struct event_pair))
/*Macro: yield the write event of an event_pair */
#define WRITEEV_PTR(pair) \
            EVENT_AT_OFFSET((pair), \
                sizeof(struct  event_pair)+event_get_struct_event_size())

/*Macro: yield the actual size to allocate for an event_pair */
#defineEVENT_PAIR_SIZE() \
            (sizeof(struct  event_pair)+2*event_get_struct_event_size())

voidreadcb(evutil_socket_t, short, void *);
voidwritecb(evutil_socket_t, short, void *);
struct event_pair *event_pair_new(struct  event_base *base, evutil_socket_t  fd)
{
        struct  event_pair *p = malloc(EVENT_PAIR_SIZE());
        if (!p) return NULL;
        p->fd = fd;
       event_assign(READEV_PTR(p), base, fd,EV_READ|EV_PERSIST, readcb, p);
        event_assign(WRITEEV_PTR(p), base, fd,EV_WRITE|EV_PERSIST, writecb, p);
        return p;
}

  event_assign函数定义在文件中。event结构体定义在文件中。

七、将events置为“挂起”或者“非挂起”

  刚创建的一个event,实际上不能做任何事,直到通过调用event_add进行adding操作,将其置为“挂起”状态。
int event_add(struct event *ev, const struct timeval *tv);
  在“非挂起”状态的events上执行event_add操作,则会使得该event在配置的event_base上变为“挂起”状态。该函数返回0表示成功,返回-1表示失败。如果tvNULL,则该event没有超时时间。否则,tv以秒和毫妙表示超时时间。
  如果在已经是“挂起”状态的event进行event_add操作,则会保持其“挂起”状态,并且会重置其超时时间。如果event已经是“挂起”状态,而且以NULL为超时时间对其进行re-add操作,则event_add没有任何作用。
  注意:不要设置tv为希望超时事件执行的时间,比如如果置tv->tv_sec=time(NULL)+10,并且当前时间为2010/01/01,则超时时间为40年之后,而不是10秒之后。

int  event_del(struct event *ev);

  在已经初始化状态的event上调用event_del,则会将其状态变为“非挂起”以及“非激活”状态。如果event的当前状态不是“挂起”或“激活”状态,则该函数没有任何作用。该函数返回0表示成功,返回-1表示失败。
  注意,如果在event刚变为“激活”状态,但是它的回调函数还没有执行时,调用event_del函数,则该操作使得它的回调函数不会执行。

int event_remove_timer(struct event *ev);
  最后,可以在不删除event上的IO事件或信号事件的情况下,删除一个“挂起”状态的event上的超时事件。如果该event没有超时事件,则event_remove_timer没有作用。如果event没有IO事件或信号事件,只有超时事件的话,则event_remove_timer等同于event_del。该函数返回0表示成功,-1表示失败。
  这些函数都是在文件中定义的。

八、事件的优先级

  当多个事件在同一时间触发时,Libevent对于他们回调函数的调用顺序是没有定义的。可以通过优先级,定义某些“更重要”的events。
  每一个event_base都有一个或多个优先级的值。在event初始化之后,添加到event_base之前,可以设置该event的优先级。
int event_priority_set(struct event *event, int priority);
  event的优先级数必须是位于0event_base优先级-1这个区间内。该函数返回0表示成功,返回-1表示失败。
  当具有多种优先级的多个events同时激活的时候,低优先级的events不会运行。Libevent会只运行高优先级的events,然后重新检查events。只有当没有高优先级的events激活时,才会运行低优先级的events。

#include 

void  read_cb(evutil_socket_t,  short,  void*);
void  write_cb(evutil_socket_t,  short,  void*);

voidmain_loop(evutil_socket_t  fd)
{
  struct  event  *important,  *unimportant;
  struct  event_base  *base;

  base = event_base_new();
 event_base_priority_init(base, 2);
  /* Now base has priority 0, and priority 1 */
  important = event_new(base,  fd,  EV_WRITE|EV_PERSIST,  write_cb,  NULL);
  unimportant = event_new(base,  fd,  EV_READ|EV_PERSIST, read_cb,  NULL);
  event_priority_set(important, 0);
  event_priority_set(unimportant, 1);

  /*Now, whenever the fd is ready for writing, the write callback will
     happen before the read callback.  The read callback won't happen at
     all until the write callback is no longeractive.*/
}

  如果没有设置一个event的优先级,则它的默认优先级是“event_base队列长度”除以2。该函数在文件中声明。

九、检查event状态

  有时可能希望知道event是否已经添加了(处于“挂起”状态),或者检查他关联到哪个event_base等。

int  event_pending(const struct  event *ev,  short  what,  struct timeval  *tv_out);

#define event_get_signal(ev) /* ... */
evutil_socket_t event_get_fd(const  struct  event *ev);
struct event_base *event_get_base(const  struct event  *ev);
short event_get_events(const  struct  event  *ev);
event_callback_fn event_get_callback(const  struct event *ev);
void*event_get_callback_arg(const  struct  event  *ev);
int  event_get_priority(const struct  event *ev);

void  event_get_assignment(const struct event*event,
        struct  event_base  **base_out,
        evutil_socket_t  *fd_out,
        short  *events_out,
        event_callback_fn  *callback_out,
        void  **arg_out);

  event_pending函数检查给定的event是否处于“挂起”或“激活”状态。如果确实如此,并且在what参数中设置了任何EV_READ, EV_WRITE, EV_SIGNALEV_TIMEOUT标志的话,则该函数返回所有该event当前正在“挂起”或“激活”的标志。
  如果提供了tv_out参数,且在what参数中设置了EV_TIMEOUT参数,并且当前event确实在超时事件上“挂起”或者“激活”,则tv_out就会设置为event的超时时间。

  event_get_fdevent_get_signal函数返回event上配置的文件描述符或者信号值。event_get_base()返回其配置的event_baseevent_get_events()返回event上配置的事件标志(EV_READ,EV_WRITE等)。event_get_callback函数和event_get_callback_arg函数返回event的回调函数和参数指针。event_get_priority函数返回event的当前优先级。
  event_get_assignment函数在提供的参数指针中返回event的所有成分,如果参数指针为NULL,则该成分被忽略。

#include 
#include 

/*Change the callback and callback_arg of 'ev', which must not be pending. */
int  replace_callback(struct  event *ev,  event_callback_fn  new_callback,
     void* new_callback_arg)
{
    struct  event_base  *base;
    evutil_socket_t  fd;
    short  events;

    int  pending;
    pending = event_pending(ev, EV_READ|EV_WRITE|EV_SIGNAL|EV_TIMEOUT, NULL);
    if (pending) {
        /*We want to catch this here so that we do notre-assign a
         * pending event.  That would be very very bad.*/
       fprintf(stderr, "Error! replace_callbackcalled on a pending event!\n");
        return -1;
    }

    event_get_assignment(ev,  &base, &fd,  &events,
                         NULL /* ignore oldcallback */ ,
                         NULL /* ignore oldcallback argument */);

   event_assign(ev,  base,  fd,  events,new_callback,  new_callback_arg);
    return 0;
}

这些函数在文件中定义。

十、找到当前正在运行的event

  在调试程序时,可以得到当前正在运行的event的指针。
struct event * event_base_get_running_event(struct event_base * base);
  注意,只有在base的loop中调用该函数,该函数才有意义。在其他线程调用时不支持的,而且会导致未定义的行为。
  该函数在中声明。

十一、配置一次性的events

  如果不需要对一个event进行多次添加,或者对一个非持久的event,在add之后就会delete,则可以使用event_base_once函数。

int event_base_once(struct event_base *,  evutil_socket_t,  short,
                   void (*) (evutil_socket_t,  short,  void*),  void *,  const struct timeval *);

  该函数的参数与event_new一样,不同的是它不支持EV_SIGNALEV_PERSIST标志。得到的内部event会以默认的优先级添加到event_base中并运行。当它的回调函数执行完成之后,Libevent将会释放该内部event。该函数成功时返回0,失败是返回-1.

  通过event_base_once插入的event不能被删除或者手动激活。如果希望可以取消一个event,则需要通过常规的event_newevent_assign接口创建event。

  注意,直到Libevent2.0之前,如果event一直没有触发,则它的内存永远不会被释放。从Libevent2.1.2-alpha版本开始,当event_base释放时,即使events还没有被激活,它们的内存也会被释放。但是依然要注意:如果它们的回调函数的参数具有关联的内存,那么除非程序中进行释放,否则这些内存永远不会被释放。

十二、手动激活event

  某些极少的情况下,你可能希望在条件未被触发的情况下就激活event;
void event_active(struct event *ev, int what, short ncalls);
  该接口使得event变为“激活”状态,激活标志在what中传入(EV_READ, EV_WRITEEV_TIMEOUT的组合)。该event之前的状态不一定非得要是“挂起”状态,而且将其激活不会使其状态变为“挂起”状态。
  警告:在同一个event上递归调用event_active可能会导致资源耗尽。下面的例子就是不正确的示范:

structevent *ev;

static void cb(int  sock,  short which,  void *arg) {
        /* Whoops: Calling event_active on thesame event unconditionally
          from within its callback means that no other eventsmight not get
           run!*/
        event_active(ev,  EV_WRITE,  0);
}

int  main(int  argc,  char**argv) {
        struct  event_base *base = event_base_new();
        ev = event_new(base,  -1,  EV_PERSIST| EV_READ,  cb, NULL);
        event_add(ev, NULL);
        event_active(ev, EV_WRITE, 0);
        event_base_loop(base, 0);
        return 0;
}

上面的例子描述了这样的情形:event loop仅被执行一次,而cb会被无限的递归调用中。
Example:Alternative solution to the above problem using timers


struct event *ev;
struct timeval tv;

static void  cb(int  sock,short  which,  void *arg) {
   if (!evtimer_pending(ev, NULL)) {
       event_del(ev);
       evtimer_add(ev, &tv);
   }
}

int  main(int  argc,  char**argv) {
   struct  event_base  *base = event_base_new();
   tv.tv_sec = 0;
   tv.tv_usec = 0;

   ev = evtimer_new(base,  cb,  NULL);
   evtimer_add(ev, &tv);
   event_base_loop(base, 0);
   return 0;
}

Example:Alternative solution to the above problem usingevent_config_set_max_dispatch_interval()

structevent *ev;

static void  cb(int  sock, short  which,  void*arg) {
        event_active(ev,  EV_WRITE,  0);
}

intmain(int argc,  char **argv) {
        struct  event_config  *cfg = event_config_new();
        /* Run at most 16 callbacks beforechecking for other events. */
       event_config_set_max_dispatch_interval(cfg,  NULL,  16, 0);
        struct  event_base *base =event_base_new_with_config(cfg);
        ev = event_new(base,  -1,  EV_PERSIST| EV_READ,  cb,  NULL);

        event_add(ev, NULL);
        event_active(ev, EV_WRITE, 0);
        event_base_loop(base, 0);

        return 0;
}

该方法在 中定义。

十三、优化一般性超时

  当前版本的Libevent使用二叉堆算法来对”挂起”状态的event超时时间值进行跟踪。对于有序的添加和删除event超时时间的操作,二叉堆算法可以提供O(lg n)的性能。这对于添加随机分布的超时时间来说,性能是最优的,但是如果是大量相同时间的events来说就不是了。
  比如,假设有一万个事件,每一个event的超时时间都是在他们被添加之后的5秒钟。在这种情况下,使用双向队列实现的话,可以达到O(1)的性能。

  正常情况下,一般不希望使用队列管理所有的超时时间值,因为队列仅对于恒定的超时时间来说是快速的。如果一些超时时间或多或少的随机分布的话,那添加这些超时时间到队列将会花费O(n)的时间,这样的性能要比二叉堆差多了。
  Libevent解决这种问题的方法是将一些超时时间值放置在队列中,其他的则放入二叉堆中。可以向Libevent请求一个“公用超时时间”的时间值,然后使用该时间值进行事件的添加。如果存在大量的event,它们的超时时间都是这种单一公用超时时间的情况,那么使用这种优化的方法可以明显提高超时事件的性能。

const struct  timeval * event_base_init_common_timeout(
     struct event_base *base,  const  struct  timeval* duration);

  该方法的参数有event_base,以及一个用来初始化的公用超时时间值。该函数返回一个指向特殊timeval结构体的指针,可以使用该指针表明将event添加到O(1)的队列中,而不是O(lg n)的堆中。这个特殊的timeval结构可以在代码中自由的复制和分配。该timeval只能工作在特定的event_base上(参数)。不要依赖于该timeval的实际值:Libevent仅使用它们来指明使用哪个队列。

#include 
#include 

/*We're going to create a verylarge number of events on a given base,
 * nearly all of which have a ten-secondtimeout. If initialize_timeout
 * is called, we'll tell Libevent to add theten-second ones to an O(1)
 * queue. */
struct timeval  ten_seconds = { 10, 0 };

void  initialize_timeout(struct  event_base *base)
{
    struct  timeval  tv_in = { 10, 0 };
    const  struct  timeval*tv_out;
   tv_out =event_base_init_common_timeout(base, &tv_in);
   memcpy(&ten_seconds,tv_out, sizeof(struct timeval));
}

int  my_event_add(struct  event *ev, const  struct  timeval*tv)
{
    /* Note that ev must have the sameevent_base that we passed to
       initialize_timeout */
   if (tv && tv->tv_sec == 10 && tv->tv_usec == 0)
        return event_add(ev, &ten_seconds);
    else
        return event_add(ev, tv);
}

  类似于其他所有的优化函数,除非确定对你有用,否则应该避免使用这种公用超时时间功能。

十四、从已清除的内存识别事件

  Libevent提供了这样的函数,可以从已经清0的内存中(比如以calloc分配,或者通过memsetbzero清除)识别出已初始化的event

int  event_initialized(const  struct  event*ev);

#define evsignal_initialized(ev)  event_initialized(ev)
#define evtimer_initialized(ev)  event_initialized(ev)

  警告:这些函数不能在一块未初始化的内存中识别出已经初始化了的event。除非你能确定该内存要么被清0,要么被初始化为event,否则不要使用这些函数。
  一般情况下,除非你的应用程序有着极为特殊的需求,否则不要轻易使用这些函数。通过event_new返回的events永远已经初始化过的。

#include 
#include 

struct reader {
    evutil_socket_t  fd;
};

#defineREADER_ACTUAL_SIZE()  (sizeof(struct  reader) + event_get_struct_event_size())
#defineREADER_EVENT_PTR(r)  ((struct  event *) (((char*)(r))+sizeof(struct  reader)))

struct reader * allocate_reader(evutil_socket_t fd)
{
    struct  reader *r = calloc(1, READER_ACTUAL_SIZE());
    if (r)
        r->fd = fd;
    return r;
}

void  readcb(evutil_socket_t,  short,  void*);
int  add_reader(struct  reader *r, struct  event_base *b)
{
    struct  event  *ev= READER_EVENT_PTR(r);
    if (!event_initialized(ev))
        event_assign(ev,  b,  r->fd,  EV_READ,  readcb,  r);
    return  event_add(ev, NULL);
}

十五、过时的event处理函数

  在Libevent2.0之前的版本中,没有event_assign或者event_new函数,而只有event_set函数,该函数返回的event与“当前”base相关联。如果有多个event_base,则还需要调用event_base_set函数指明event与哪个base相关联。

void  event_set(struct  event  *event, evutil_socket_t  fd,  short what,
        void(*callback)(evutil_socket_t,  short,  void *),  void *arg);
int  event_base_set(struct  event_base * base,  struct  event *event);

  event_set函数类似于event_assign,除了它使用“当前”base的概念。event_base_set函数改变event所关联的base。
  如果是处理超时或者信号events,event_set也有一些便于使用的变种:evtimer_set类似于evtimer_assign,而evsignal_set类似于evsignal_assign

  Libevent2.0之前的版本中,使用以signal_为前缀的函数作为处理基于信号的event_set的变种。而不是evsignal_(也就是说是:signal_set, signal_add, signal_del, signal_pendingsignal_intialized)。Libevent古老版本(0.6之前),使用timeout_,而不是evtimer_前缀。因此,如果你需要处理很老的代码的话,可能会看见timeout_add(), timeout_del(), timeout_initialized(), timeout_set(), timeout_pending()等。

  较老版本的Libevent(2.0之前)使用两个宏,完成event_get_fdevent_get_signal的工作:EVENT_FDEVENT_SIGNAL。这些宏直接监测event结构的内部,因此在各种版本之间不具有二进制兼容性。在2.0以及之后的版本中,这些宏就是event_get_fdevent_get_signal函数的别名。
  在Libevent2.0之前的版本中不支持锁操作,因此,在运行base的线程之外的线程中,调用任何改变event状态的函数,都是不安全的。这些函数包括:event_add, event_del, event_active, 和 event_base_once

  event_base_once的古老版本是event_once,它使用“当前”base的概念。
  在Libevent2.0之前,超时事件设置EV_PERSISIT是不明智的。并非在event激活时重置超时时间,EV_PERSISIT标志对于超时而言无任何作用。
  Libevent2.0之前的版本不支持多个events,在同一时间添加同样的fd以及相同的READ/WRITE事件。换句话说,对于每一个fd,一次只能有一个event在其上等待读或写。

你可能感兴趣的:(开源项目)