学习笔记:高性能IO框架库Libevent(二):eventloop事件循环

1,505 阅读6分钟

Libevent参考手册解读:

2.进入event loop循环

开始eventloop循环: 不设置标志:重复检查是否有注册的事件被触发,直到没有注册事件位置等价于函数int event_base_dispatch(struct event_base *base); 设置EVLOOP_ONCE:等待事件被触发一次后退出。 设置EVLOOP_NONBLOCK:存在就绪事件,处理完后返回,否则立即返回。 设置EVLOOP_NO_EXIT_ON_EMPTY:需要手动用event_base_loopbreak(),event_base_loopexit()来退出

#define EVLOOP_ONCE		0x01
#define EVLOOP_NONBLOCK		0x02
#define EVLOOP_NO_EXIT_ON_EMPTY	0x04

int event_base_loop(struct event_base *base, int flags);\\正常退出返回0,出现错误则返回-1

该函数的实现伪代码如下:

while (any events are registered with the loop,
        or EVLOOP_NO_EXIT_ON_EMPTY was set) {

    if (EVLOOP_NONBLOCK was set, or any events are already active)
        If any registered events have triggered, mark them active.
    else
        Wait until at least one event has triggered, and mark it active.

    for (p = 0; p < n_priorities; ++p) {
       if (any event with priority of p is active) {
          Run all active events with priority of p.
          break; /* Do not run any events of a less important priority */
       }
    }

    if (EVLOOP_ONCE was set or EVLOOP_NONBLOCK was set)
       break;
}

退出eventloop循环:

int event_base_loopexit(struct event_base *base,const struct timeval *tv);
int event_base_loopbreak(struct event_base *base);

示例代码:

.Example: Shut down immediately
[code,C]
--------
#include <event2/event.h>

/* Here's a callback function that calls loopbreak */
void cb(int sock, short what, void *arg)
{
    struct event_base *base = arg;
    event_base_loopbreak(base);
}

void main_loop(struct event_base *base, evutil_socket_t watchdog_fd)
{
    struct event *watchdog_event;

    /* Construct a new event to trigger whenever there are any bytes to
       read from a watchdog socket.  When that happens, we'll call the
       cb function, which will make the loop exit immediately without
       running any other active events at all.
     */
    watchdog_event = event_new(base, watchdog_fd, EV_READ, cb, base);

    event_add(watchdog_event, NULL);

    event_base_dispatch(base);
}
#include <event2/event.h>

void run_base_with_ticks(struct event_base *base)
{
  struct timeval ten_sec;

  ten_sec.tv_sec = 10;
  ten_sec.tv_usec = 0;

  /* Now we run the event_base for a series of 10-second intervals, printing
     "Tick" after each.  For a much better way to implement a 10-second
     timer, see the section below about persistent timer events. */
  while (1) {
     /* This schedules an exit ten seconds from now. */
     event_base_loopexit(base, &ten_sec);

     event_base_dispatch(base);
     puts("Tick");
  }
}

检查循环状态: 如果循环正常结束 return true;

int event_base_got_exit(struct event_base *base);
int event_base_got_break(struct event_base *base);

重新检查就绪事件: 一般来说,libevent先检查事件状态,然后执行高优先级的激活事件,处理完后再检查事件。但有时想在当前回调函数执行完后,立即重新检查就绪事件的更新状态,可以执行以下函数。

int event_base_loopcontinue(struct event_base *);

检查内部缓存时间: 为了在执行回调函数时减小系统开销,查询缓存时间(回调函数开始执行的时间)

int event_base_gettimeofday_cached(struct event_base *base,struct timeval *tv_out);
int event_base_update_cache_time(struct event_base *base);

有时回调函数执行时间很长,缓存时间非常不精确,那就用event_base_update_cache_time函数进行缓存时间更新。 输出事件状态到文件: 输出注册的event状态到文件,帮助debug

void event_base_dump_events(struct event_base *base, FILE *f);

遍历每一个事件: 该函数的回调函数会对每一个事件触发,每次调用回调时都会重新传递第三个参数。该回调函数(event_base_foreach_event_cb类型)必须每次return 0才能继续遍历,否则会终止。

typedef int (*event_base_foreach_event_cb)(const struct event_base *,const struct event *, void *);

int event_base_foreach_event(struct event_base *base,
                             event_base_foreach_event_cb fn,
                             void *arg);

3.创建event事件:

event事件分类:

  • 一个文件描述符准备好读或写
  • 一个文件描述符变为准备好读或写的状态(ET)
  • 超时事件
  • 发生某信号
  • 用户触发事件

所有事件具有相似的生命周期。调用 libevent 函数设置事件并且关联到 event_base 之后,事件进入“已初始化(initialized)”状态。此时可以将事件添加到 event_base 中,这使之进入“未决(pending)”状态。在未决状态下,如果触发事件的条件发生(比如说,文件描述符的状态改变,或者超时时间到达),则事件进入“激活(active)”状态,(用户提供的)事件回调函数将被执行。如果配置为“持久的(persistent)”,事件将保持为未决状态。否则,执行完回调后,事件不再是未决的。删除操作可以让未决事件成为非未决(已初始化)的;添加操作可以让非未决事件再次成为未决的。

image.png

新建事件和事件相关函数: 该函数首先对base进行关联,what参数传入flag选项,cb传入事件处理回调函数,arg传入自定义指针

#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);
int event_add(struct event *ev, const struct timeval *tv);

int event_del(struct event *ev);

int event_remove_timer(struct event *ev);\\移除指定事件TIMEOUT触发的属性

void event_free(struct event *event);

int event_priority_set(struct event *event, int priority);\\设置当前事件优先级 0为最高优先级

tips:可以利用函数 void *event_self_cbarg();传入自身对象

此外在官方使用手册中还有event_assign()这种在栈空间初始化事件的函数,但官方并不推荐使用。

示例代码:

.Example
[code,C]
----------
#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. */
}

新建定时器事件以及定时事件相关函数:

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

新建信号事件以及信号事件相关函数:

#define evsignal_new(base, signum, cb, arg) \
    event_new(base, signum, EV_SIGNAL|EV_PERSIST, cb, arg)
#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))

示例代码:

.Example
[code,C]
--------
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);

检查事件状态: 下列函数可以检查与事件相关的状态

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

为debug准备的函数: 检测当前eventloop正在跑的event。

struct event *event_base_get_running_event(struct event_base *base);

配置一次触发事件:

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

除了不支持EV_SIGNAL和EV_PERSIST之外,与event_new功能相同。

手动激活: void event_active(struct event *ev, int what, short ncalls);

tips:what 只能是EV_READ, EV_WRITE, and EV_TIMEOUT

优化公用超时:

在存在大量相同超时值的定时事件时使用,使用双向链表代替二叉堆进行存取,让存取操作变为O(1)。libevent 通过放置一些超时值到队列中,另一些到二进制堆中来解决这个问题。要使用这个 机制,需要向 libevent 请求一个“公用超时(common timeout)”值,然后使用它来添加事件。 如果有大量具有单个公用超时值的事件,使用这个优化应该可以改进超时处理性能。

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

这个函数需要 event_base 和要初始化的公用超时值作为参数。函数返回一个到特别的 timeval 结构体的指针,可以使用这个指针指示事件应该被添加到 O(1)队列,而不是 O (logN)堆。可以在代码中自由地复制这个特别的 timeval 或者进行赋值,但它仅对用于构 造它的特定 event_base 有效。不能依赖于其实际内容:libevent 使用这个内容来告知自身 使用哪个队列。

示例代码:

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