c++库

Libevent:event

2019-03-17  本文已影响0人  食梦狸猫

Libevent的最基本操作单元就是event,每个event表示一系列状况:

events都有相同的生命周期。一旦我们调用Libevent的函数去创建一个event而且绑定在一个event_base上,这就算是初始化了。这时我们可以添加在这个event上关注的要发生的事件,然后当事件发生后,此event就被激活,然后回调函数就会被执行。如果这个event设置为一直保持的状态,就会一直不停地关注事件。不然,只会关注一次。

创建一个event对象

#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上,fd是我们关注读写事件的文件描述符,当event激活后,Libevent会调用提供的cb回调函数。出错的情况,会返回NULL;

销毁一个event,调用event_free()函数。

但要真正让Libevent开始关注事件,还需要event_add()函数

例子

#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();
  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);
}

设置event为自己的回调函数的参数

对于这种情况,我们不能直接传一个指向event的指针给event_new,因为它此时还不存在。为了解决这种情况,我们可以用函数event_self_cbarg()

//void* event_self_cbarg();

//例子

#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;
  ev = event_new(base,-1,EV_PERSIST,cb_func,event_self_cbarg());

event_add(ev,&one_sec);
event_base_dispatch(base);
}

关于时间事件的宏

Libevent提供了一系列方便的宏

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

创建信号event

Libevent可以监听POSIX信号

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

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

hup_event = evsignal_new(base,SIGHUP,sighup_function,NULL);

也有一些对于信号事件方便的宏:

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

创建一个不从堆分配的event

使用event_assign会造成难以诊断的错误,这时因为不同版本的Libevent里event结构的大小不同的原因。除非有了从堆上分配event有明显的性能不好的表现,我们应该尽量选择event_new()

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_new一样
//返回0成功,-1错误
//例子

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去初始化栈分配和static栈初始化的events

注意

永远不要在event已经绑定event_base后还在这个event上调用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)
size_t event_get_struct_event_size(void);

#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的另外几个函数

int event_add(struct event *ev, const struct timeval *tv);
//让event正式绑定在已经设置的event_base上

//tv是时间事件有关的,除了时间事件设置为NULL
//不然的话,设置tv->tv_sec = time(NULL)+10;会导致在40年后开启......

int event_del(struct event *ev);

int event_remove_timer(struct event *ev);

event的优先级

当有多个event在同时激活,Libevent不会定义任何执行的顺序。我们可以通过定义优先级来达到
在调用event_add之前,调用函数

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

设置优先级。优先级范围是0和这个event_base里有的优先级数量。
当多个事件同时激活,Libevent先跑优先级高的,然后再继续循环去检查,当没有高优先级的时候才会去执行低优先级的。

#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的状态

Libevent提供了一系列函数

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

例子

#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;
}

找到当前正在运行的的event

struct event *event_base_get_running_event(struct event_base *base);

设置一次性event

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

手动激活一个event

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

但注意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;
}

正确示范:

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

优化时间事件

目前的Libevent使用二叉堆来存储时间事件,一般情况下的表现是O(lg n),但是当有很多相同时间事件时就会不同了。

例如,假如我们有一万个相同时间的时间事件,如果用双向链表队列只会用O(1)。

Libevent允许我们放这样的时间事件在链表队列里,其他的在二叉堆里,这样的话可以向Libevent寻求一个“相同时间”时间事件

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

//例子

#include <event2/event.h>
#include <string.h>

/* We're going to create a very large number of events on a given base,
 * nearly all of which have a ten-second timeout.  If initialize_timeout
 * is called, we'll tell Libevent to add the ten-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 same event_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);
}

上一篇 下一篇

猜你喜欢

热点阅读