Libevent事件
Toc
  1. 创建一个事件
    1. Example
  2. 事件标识
  3. 关于事件持续等待
  4. 创建一个默认回调函数的事件
    1. Interface
    2. Example
    3. 只有超时的事件
    4. 创建信号事件
    5. Example
    6. 没有使用堆来分配和创建事件
    7. Interface
    8. Example
    9. Inteface
    10. EXample
  • 让事件等待或取消等待
  • 事件的优先顺序
    1. Example
  • 检查事件状态
    1. Example
  • 获取当前正在执行的事件
  • 一次性事件
  • 手工激活事件
    1. Bad Example: making an infinite loop with event_active()
  • 优化超时时间
  • 废弃的事件处理函数
  • 原文:R4: Working with events

    事件是Libevent的基本操作单元。每个事件代表一组情况,包括:

    1. 文件描述符可以读写
    2. 边缘触发模式下,文件描述符变成可以读写
    3. 事件超时
    4. 信号到来
    5. 用户触发的事件

    事件有相似的生命周期。调用Libevent的函数来创建一个事件并把该事件关联到一个event_base上,这个事件就是初始化的状态。在这个时候点,你可以调用add,让它在base中变成等待状态。在事件等待过程中,如果符合触发事件的条件满足,事件就变成激活状态,比如文件描述符改变了状态,或者超时条件。事件激活后,它的(或者用户提供的)回调函数开始执行。如果事件被设置持续等待,那它继续保持等待状态,否则当它的回调函数执行完毕后,事件就停止等待。你也可以删除一个等待事件来取消等待。


    创建一个事件

    创建一个新的事件,使用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()函数尝试分配内存和创建一个新的事件以给base使用。参数what包含上面列出的一个或多个标识。如果fd为非负数,那它就是一个事件监听读写事件的文件。当事件激活后,Libevent将会调用回调函数cb,传给这个回调函数3个参数:a)文件描述符fd,b)触发的事件标识,c)原本传给event_new()的最后一个参数arg

    如果出现内部错误,或者遇到无效的参数,event_new()返回NULL指针。

    一个新事件初始化之后,还是非等待状态。调用event_add()来让事件变成等待状态(下面讲到)。

    调用event_free()来释放事件。释放一个等待或激活状态的事件是安全的:它会先让事件停止等待,或者让它变成非激活状态,再释放它。

    Example

    #include <event2/event.h>
    
    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);
    }
    

    事件标识

    1. EV_TIMEOUT
      表示在一个超时时间之后,事件会变成激活状态。
      EV_TIMEOUT标识在事件创建时是被忽略的:你在添加事件时也可以不设置这个标识。当超时的回调函数执行前,这个标识会被设置到参数what中,再传给回调函数。
    2. EV_READ
      当提供的文件描述符可读时激活事件。
    3. EV_WRITE
      当提供的文件描述符可写时激活事件。
    4. EV_SIGNAL
      用来检测信号。
    5. EV_PERSIST
      表示事件是持续等待的。
    6. EV_ET
      如果事件的后端函数支持边缘触发事件,那当前的事件应该是边缘触发。这个影响到EV_READ和EV_WRITE的语意。
      在Libevent 2.0.1-alpha之后,任意数量的事件可以同时等待同一个条件。比如,如果一个给出的fd变成可读时,可能有2个事件同时被激活,但哪个事件的回调函数先执行则是不确定的。

    关于事件持续等待

    当一个等待的事件激活,回调函数执行后,事件默认就不再等待了。所以若你想让事件重新等待,你可以在回调函数里调用event_add()函数。

    但是,若事件设置了EV_PERSIST标识,那它将会继续等待。这意味着当回调函数执行时,事件也处于等待状态。如果你想暂停等待,你可以在回调函数里调用event_del()函数。

    当回调函数开始执行后,该持续等待的事件会重置超时。如果你有一个设置EV_READ|EV_PERSIST标识的事件,以及事件有一个5秒的超时条件,那事件会被激活,当:

    1. socket可读时
    2. 上次事件激活之后的5秒之后,事件再次被激活

    创建一个默认回调函数的事件

    你可能经常需要创建一个回调它自身的事件,你不能传一个event的指针给event_new(),因为该事件还未存在。为了解决这个问题,你可以使用event_self_cbarg()

    Interface

    void *event_self_cbarg();
    

    该函数返回一个神奇的指针,当该指针作为arg参数传给event_new()时,event_new()将创建一个回调它自身的事件。

    Example

    #include <event2/event.h>
    
    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 timer to get called 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_assign()evsignal_assign()使用。

    只有超时的事件

    为了方便,有一组以evtimer_为前缀的宏来支持只做超时的事件。使用这些宏可让你的代码更清晰。

    #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-style的信号,使用下面函数来创建一个信号处理函数:

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

    参数跟event_new一样, 除了第2个参数,这里提供的是信号,而不是文件描述符。

    Example

    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);
    

    注意,当信号出现后,信号回调函数是在事件轮询时执行的,所以可以安全调用一些函数,那些在POSIX信号处理函数里可能不推荐调用的函数。

    也有对应的用于信号事件的一些宏:

    #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来监听信号。如果你同时往2个event_base中添加信号事件,即使是不同信号的事件,当信号到来时,也只有一个event_base会收到信号。
    kqueue这个后端则没有这个限制(苹果的队列啊)。

    没有使用堆来分配和创建事件

    为了性能和其他原因,一些人喜欢在一个大数据结构体中分配事件使用的内存,这样使用,他们可以节省:

    1. 内存分配器在堆上分配小对象内存的开销
    2. 对event数据结构指针进行解引用所需的时间开销
    3. 重新缓存event所需时间开销

    这样使用会带来跟其他Libevent版本的二进制兼容问题,因为可能存在不同长度的event数据结构。

    这些都是非常小的开销,一般不影响程序。 你应该坚持使用event_new(),除非你在堆上分配事件而遇到非常明显的性能问题。使用event_assign()会在未来的Libevent版本遇到难以定位的问题,如果未来Libevent使用一个比你使用的数据结构更大的大数据结构。

    Interface

    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这个参数,其他参数跟event_new()一样,参数event必须指向一个未初始化的事件,成功返回0,如果出现内部错误或者遇到错误参数,返回-1。

    Example

    #include <event2/event.h>
    /* Watch out!  Including event_struct.h means that your code will not
     * be binary-compatible with future versions of Libevent. */
    #include <event2/event_struct.h>
    #include <stdlib.h>
    
    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()来初始化栈上分配的或静态分配的事件。

    警告
    不要对一个已经在event_base里的且已经开始等待的事件调用event_assign()。否则会带来及其难以定位的错误。如果事件已经初始化或正处于等待,在你调用event_assing()之前先调用event_del()

    对于event_assign()也有对应的方便使用的宏:

    #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)
    

    如果你需要使用event_assign()并且想跟未来的Libevent版本保持二进制兼容,你可以在程序运行时让Libevent告诉你一个event数据结构需要多少空间:

    Inteface

    size_t event_get_struct_event_size(void);
    

    这个函数返回event数据结构需要多少字节。跟之前说的,除非你知道你的程序在堆上分配内存会带来非常严重的性能问题,否则不要使用这个函数,因为它会让你的代码难以阅读和编写。

    注意,未来版本里的event_get_struct_event_size()返回的字节数值可能比sizeof(struct event)要少,如果发生了,那意味着struct event末端的额外字节只是填充,在Libevent版本中使用。

    这里也有对应的例子,但不是依赖头文件event_struct.h里定义的event数据结构的长度,而是使用event_get_struct_size()在运行时决定event的长度。

    EXample

    #include <event2/event.h>
    #include <stdlib.h>
    
    /* 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 less error-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 */
    #define EVENT_PAIR_SIZE() \
                (sizeof(struct event_pair)+2*event_get_struct_event_size())
    
    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(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_add()让事件开始等待:

    int event_add(struct event *ev, const struct timeval *tv);
    

    成功返回0,失败返回-1。如果参数tv为NULL, 事件没有超时条件,否则,tv就是超时的秒数和微秒。

    int event_del(struct event *ev);
    

    对一个初始化的事件调用event_del让事件停止等待,也让它变成非激活状态。 如果事件不是处于等待或激活状态,调用这个函数没有效果。成功返回0,失败返回-1。
    注意:在事件被激活之后,且在回调函数开始执行之前,若删除了事件,那回调函数不会执行。

    int event_remove_timer(struct event *ev);
    

    最后,你可以删除一个等待事件的超时条件,而不需要删除它的IO或者信号条件。如果事件没有等待的超时条件,这个函数没有效果。如果事件只有超时条件,这个函数等同event_del。成功返回0,失败返回-1。


    事件的优先顺序

    当多个事件同时被触发,Libevent并没有定义他们回调函数的执行顺序。你可以使用他们的优先顺序来让一些事件变得更为重要。

    前一章里提到,每个event_base有一个或多个优先顺序的值。事件初始化之后,在添加到event_base之前,你可以设置事件的优先顺序。

    int event_priority_set(struct event *event, int priority);
    

    第2个参数priority的值最小为0,最大为event_base的优先值-1,成功返回0,失败返回-1。

    当多个事件同时激活,低优先级的事件没有执行,Libevent先执行高优先级的事件,然后再检查事件。 只有当没有高级别的事件激活,低级别的事件才被执行。

    Example

    #include <event2/event.h>
    
    void read_cb(evutil_socket_t, short, void *);
    void write_cb(evutil_socket_t, short, void *);
    
    void main_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 longer active. */
    }
    

    如果你没有设置事件的优先等级,那事件的默认优先等级为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检测给出的事件是等待状态还是激活状态。如果是这两个状态,而且what设置了EV_READ, EV_WRITE, EV_SIGNAL, EV_TIMEOUT中的一个或多个标识,那这个函数返回当前是否有标识对应的等待或激活的事件类型。如果提供了参数tv_out,参数what也设置了EV_TIMEOUT,则如果事件当前是等待超时状态或超时后被激活状态,那超时的时间点通过tv_out返回。

    函数event_get_fd()event_get_signal()返回事件的文件描述符或信号号码。
    函数The event_get_base()返回event_base。
    函数event_get_events()返回事件标识(EV_READ, EV_WRITE等)。
    函数event_get_callback()event_get_callback_arg()返回事件的回调函数和参数指针。
    函数event_get_priority()返回事件当前的优先等级。

    函数event_get_assignment() 复制事件分配的所有字段到指定的指针里。如果指针为NULL,则忽略。

    Example

    #include <event2/event.h>
    #include <stdio.h>
    
    /* 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 not re-assign a
             * pending event.  That would be very very bad. */
            fprintf(stderr,
                    "Error! replace_callback called on a pending event!\n");
            return -1;
        }
    
        event_get_assignment(ev, &base, &fd, &events,
                             NULL /* ignore old callback */ ,
                             NULL /* ignore old callback argument */);
    
        event_assign(ev, base, fd, events, new_callback, new_callback_arg);
        return 0;
    }
    


    获取当前正在执行的事件

    为了调试或其他原因,你可以获得一个指针,指向当前正在执行的事件。

    struct event *event_base_get_running_event(struct event_base *base);
    

    注意,只有在提供的event_base的循环里调用这个函数才能让这个函数正确执行。 不支持在另外线程中调用这个函数,将导致为定义的行为。


    一次性事件

    如果你不需要多次添加事件,或者在添加之后会马上删除它,也不需要让它持续等待,你可以使用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。这个事件以默认的优先等级开始执行,当它的回调函数执行完毕后,Libevent释放它内部的event数据结构。成功返回0,失败返回-1。

    这种一次性事件不能被删除或手工激活:如果你需要能够取消一个事件,你只能用常规的方式event_new()event_assign().


    手工激活事件

    比较少见,但你可以手工激活一个还未被触发的事件。

    void event_active(struct event *ev, int what, short ncalls);
    

    这个函数让事件ev激活,跟随标识what。在调用之前,事件不需要已经处于等待状态,激活之后也不会让它变成等待状态。

    警告:对同个事件不断重复调用event_active()可能回耗尽资源。下面是一个错误使用的例子。

    Bad Example: making an infinite loop with event_active()

    struct event *ev;
    
    static void cb(int sock, short which, void *arg) {
            /* Whoops: Calling event_active on the same event unconditionally
               from within its callback means that no other events might 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;
    }
    

    例子中只有一次事件轮询,但是不断循环调用cb


    优化超时时间

    略。大意是当你需要同时设置数以千万个超时事件时,可以 让Libevent给你一个优化后的超时时间值,以优化性能。


    废弃的事件处理函数

    略。



    – EOF –

    Categories: in_lib
    Tags: libevent