Loading

libevent之event

使用事件

Libevent 的基本操作单元是事件。每个活动 表示一组条件,包括:

  • 准备读取或写入的文件描述符。
  • 准备读取或写入的文件描述符 (仅限 Edge 触发的 IO)。
  • 超时即将到期。
  • 出现信号。
  • 用户触发的事件。

事件具有相似的生命周期。将 Libevent 函数调用到 设置一个事件并将其与事件库关联,它将被初始化。此时,您可以添加,这使它在基础中处于挂起状态。当事件处于挂起状态时,如果条件 这将触发事件发生(例如,其文件描述符更改 状态或其超时过期),事件变为活动状态,其 (用户提供)回调函数运行。如果事件配置为持久性,则该事件仍处于挂起状态。如果它不是持久的,它就会停止 当其回调运行时处于挂起状态。您可以创建挂起事件 non-pending 通过删除它,您可以将非 pending 事件添加到 再次将其设置为挂起状态。

构造事件对象

若要创建新事件,请使用 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 是 非负数,它是我们将观察读取或写入的文件 事件。当事件处于活动状态时,Libevent 将调用提供的 cb 函数,并将其作为参数传递:文件描述符 fd所有触发事件的位字段,以及传递的值 在构造函数时为 arg

对于内部错误或无效参数,event_new() 将返回 NULL。

所有新事件都已初始化且非挂起。创建事件 挂起,调用 event_add()(如下所述)。

要解除分配事件,请调用 event_free()。调用 event_free() 是很安全的对挂起或活动的事件:这样做会使 事件在解除分配之前处于非挂起状态且处于非活动状态。

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

上述函数在 <event2/event.h> 和 第一个 出现在 Libevent 2.0.1-alpha 中。event_callback_fn类型 首次以 typedef 的形式出现在 Libevent 2.0.4-alpha 中。

事件标志

  • EV_TIMEOUT

    此标志指示超时后变为活动状态。
    在构造事件时忽略 EV _ TIMEOUT 标志: you可以在添加事件时设置超时,也可以不设置当超时时,将“ what”参数设置为回调函数已经发生了。

  • EV_READ

    此标志指示当提供的 文件描述符已准备好读取。

  • EV_WRITE

    此标志指示当提供的 文件描述符已准备好写入。

  • EV_SIGNAL

    用于实现信号检测。请参阅“构造信号事件” 下面。

  • EV_PERSIST

    指示事件是持久的。请参阅“关于事件 坚持“。

  • EV_ET

    指示事件应由边缘触发,如果 底层event_base后端支持边缘触发事件。 这会影响EV_READ和EV_WRITE的语义。

从 Libevent 2.0.1-alpha 开始,任何数量的事件都可能处于待处理状态 同时在相同的条件下。例如,您可能有两个 如果给定的 FD 准备好读取,则事件将变为活动状态。 其回调的运行顺序未定义。

这些标志在 <event2/event.h> 中定义。从那时起,一切都存在 在 Libevent 1.0 之前,除了 EV_ET,它是在 Libevent 2.0.1-alpha 版本。

关于事件持久性

默认情况下,每当挂起事件变为活动状态时(因为它的 fd 是 准备读取或写入,或者由于其超时过期),它变为 非待定权利之前 执行回调。因此,如果要使事件挂起 同样,您可以从内部再次调用 event_add() 回调函数。

但是,如果在事件上设置了EV_PERSIST标志,则该事件是持久的。这意味着该事件仍处于挂起状态,即使其 回调被激活。如果您想从其内部使其非挂起 callback,你可以调用 event_del()。

每当事件的回调时,持久事件的超时都会重置 运行。因此,如果您有一个带有 flags EV_READ|EV_PERSIST 和 超时 5 秒后,事件将变为活动状态:

  • 每当插座准备好读取时。
  • 每当事件自上次事件以来已经过去了五秒钟 积极。

创建事件作为其自己的回调参数

通常,您可能希望创建一个事件,该事件将自身接收为 回调参数。不能只将指向事件的指针作为参数传递 但是,event_new(),因为它还不存在。为了解决这个问题, 您可以使用 event_self_cbarg()。

接口

void *event_self_cbarg();

event_self_cbarg() 函数返回一个“魔术”指针,当传递该指针时 作为事件回调参数,告诉 event_new() 创建一个事件接收 本身作为其回调参数。

#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 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()。它 但是,不会用作非事件的回调参数。

event_self_cbarg() 函数是在 Libevent 2.1.1-alpha 中引入的。

仅超时事件

为方便起见,有一组以 evtimer_ 开头的宏 您可以使用代替 event_* 调用来分配和操作 纯超时事件。使用这些宏除了 提高代码的清晰度。

接口

#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 0.6 以来一直存在,除了 evtimer_new(), 它首次出现在 Libevent 2.0.1-alpha 中。

构造信号事件

Libevent 还可以监视 POSIX 样式的信号。要构造一个 处理程序,使用:

接口

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

这些论点与event_new一样,只是我们提供了一个信号 number 而不是文件描述符。

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

evsignal_* 宏自 Libevent 2.0.1-alpha 以来一直存在。 以前的版本称它们为 signal_add()、signal_del() 等。

处理信号时的注意事项

对于当前版本的 Libevent,对于大多数后端,只有一个event_base 每个进程一次可以监听信号。如果添加信号事件 一次到两个event_bases---即使信号不同---也只有一个 event_base将接收信号。

kqueue 后端没有此限制。

创建用户触发的事件

有时,创建可以激活的事件和 当所有高优先级事件都完成后再运行。任何种类 清理或垃圾收集就是这样的事件。请参阅“活动 with priority“,用于解释设置降低优先级。

用户触发的事件可以创建为:

struct event *user = event_new(evbase, -1, 0, user_cb, myhandle);

请注意,不需要 event_add()。然后用以下方式发射

event_active(user, 0, 0);

第 3 个参数对于非信号事件不显著。

设置不带堆分配的事件

出于性能等原因,有些人喜欢分配事件 作为更大结构的一部分。对于事件的每次使用,这 保存它们:

  • 用于分配小对象的内存分配器开销 堆。
  • 取消引用指向 struct 事件。
  • 如果 事件尚未在缓存中。

使用此方法可能会破坏与其他二进制文件的兼容性 Libevent 的版本,对于事件可能具有不同的大小 结构。

这些都是非常小的成本,对于大多数应用来说并不重要。 你应该坚持使用 event_new(),除非你知道 堆分配会造成显著的性能损失 您的活动。使用 event_assign() 可能会导致难以诊断的错误 如果 Libevent 的未来版本使用更大的事件结构 比你正在构建的那个。

接口

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 参数,该参数必须指向未初始化的事件。它返回 0 表示成功,-1 表示内部错误或错误参数。

#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_assign勿对已在 事件库。这样做会导致极难诊断 错误。如果事件已初始化且挂起,请调用 event_del() 在它上面再次调用 event_assign() 之前。

您可以使用一些方便的宏来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 库告诉 在运行时,结构事件应该有多大:

接口

size_t event_get_struct_event_size(void);

此函数返回需要留出的字节数 一个 struct 事件。和以前一样,只有在以下情况下才应该使用此函数 您知道堆分配实际上是您的一个重大问题 程序,因为它会使您的代码更难读写。

请注意,event_get_struct_event_size() 将来可能会给你一个sizeof(struct event) 的值。如果发生这种情况,则意味着 struct 事件末尾的任何额外字节都只是保留的填充字节 供 Libevent 的未来版本使用。

这是与上面相同的示例,但不是依赖于大小 对于 event_struct.h 的 struct 事件,我们使用 event_get_struct_size() 以在运行时使用正确的大小。

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

<event2/event.h> 中定义的 event_assign() 函数。它已经存在了 自 Libevent 2.0.1-alpha 以来。自 2.0.3-alpha 以来,它返回了一个 int; 以前,它返回 void。event_get_struct_event_size() 函数是在 Libevent 2.0.4-alpha 中引入的。事件结构 本身在 <event2/event_struct.h> 中定义。

使事件挂起和非挂起

一旦你构建了一个事件,它实际上不会做任何事情 直到您通过添加它使其处于挂起状态。您可以通过以下方式执行此操作 event_add:

接口

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

对非挂起事件调用 event_add 会使其在其 配置的底座。该函数在成功时返回 0,在成功时返回 -1 失败。如果 tv 为 NULL,则添加事件且不超时。 否则,tv 是超时的大小(以秒为单位),并且 微秒。

如果对挂起的事件调用 event_add(),它将 让它保持挂起状态,并使用提供的超时重新安排它。如果 事件已处于挂起状态,您使用超时 NULL 重新添加它, event_add() 将不起作用。

注意 不要将电视设置为您希望超时的时间 跑。如果您在 2010 年 1 月 1 日说“tv→tv_sec = time(NULL)+10;”,则您的 超时将等待 40 年,而不是 10 秒。

接口

int event_del(struct event *ev);

对初始化事件调用 event_del 会使其处于非挂起状态,并且 非活动状态。如果事件未挂起或处于活动状态,则没有 影响。成功时返回值为 0,失败时返回值为 -1。

注意 如果在事件变为活动之后但在事件之前删除该事件 它的回调有几率执行,回调不会 执行。

接口

int event_remove_timer(struct event *ev);

最后,您可以完全删除挂起事件的超时,而无需 删除其 IO 或信号组件。如果事件没有超时 待定,event_remove_timer() 无效。如果事件只有 超时但没有 IO 或信号组件,event_remove_timer() 具有 与 event_del() 的效果相同。成功时返回值为 0,返回值为 -1 失败。

这些在 <event2/event.h> 中定义;event_add() 和 event_del() 自 Libevent 0.1 以来就已存在;event_remove_timer() 被添加到 2.1.2-阿尔法。

具有优先次序的事件

当多个事件同时触发时,Libevent 不会 定义有关其回调时间的任何顺序 执行。您可以通过以下方式将某些事件定义为比其他事件更重要 使用优先级。

如上一节所述,每个event_base都有一个或多个 与之关联的优先级值。在将事件添加到 event_base,但是初始化后,您可以设置其优先级。

接口

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

事件的优先级是介于 0 和 event_base中的优先级,减去 1。该函数返回 0 on 成功,失败时为 -1。

当多个优先级的多个事件变为活动状态时, 不运行低优先级事件。相反,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. */
}

如果未为事件设置优先级,则默认值为 事件库中的队列数除以 2。

此函数在 <event2/event.h> 中声明。它从那时起就存在了 Libevent 1.0 版本。

检查事件状态

有时,您想判断是否已添加事件,并检查 它指的是什么。

接口

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 函数确定给定事件是否 待处理或活动。如果是,并且任何标志EV_READ,EV_WRITE, EV_SIGNAL 和 EV_TIMEOUT 设置在 what 参数、函数中 返回事件当前处于挂起或活动状态的所有标志 上。如果提供了tv_out,并且EV_TIMEOUT设置在什么中,并且 事件当前处于待处理状态或超时处于活动状态,则tv_out处于活动状态 设置为保留事件超时到期的时间。

event_get_fd() 和 event_get_signal() 函数返回 事件的配置文件描述符或信号编号。这 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, 它被忽略。

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

这些函数在 <event2/event.h> 中声明。event_pending() 函数从 Libevent 0.1 开始就存在了。引入 Libevent 2.0.1-alpha event_get_fd() 和 event_get_signal()。Libevent 2.0.2-alpha 发布 event_get_base().Libevent 2.1.2-alpha 添加了 event_get_priority()。这 其他是 Libevent 2.0.4-alpha 中的新功能。

查找当前正在运行的事件

出于调试或其他目的,可以获取指向当前 跑步事件。

接口

struct event *event_base_get_running_event(struct event_base *base);

请注意,此函数的行为仅在从以下位置调用时定义 在提供的event_base的循环中。从另一个线程调用它不是 支持,并可能导致未定义的行为。

此函数在 <event2/event.h> 中声明。它是在 Libevent 中引入的 2.1.1-阿尔法。

配置一次性事件

如果您不需要多次添加事件,或者删除一次事件 已添加,不一定要持久,可以使用 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_SIGNAL或EV_PERSIST。预定事件是 插入并以默认优先级运行。当回调为 最后,Libevent 释放了内部事件结构本身。 成功时返回值为 0,失败时返回值为 -1。

随 event_base_once 插入的事件无法删除或手动删除 已激活:如果您希望能够取消活动,请使用 常规 event_new() 或 event_assign() 接口。

另请注意,在 Libevent 2.0 之前,如果事件从未触发,则 用于保存它的内部存储器将永远不会被释放。从 Libevent 开始 2.1.2-alpha,当event_base被释放时,这些事件被释放,即使 他们尚未激活,但仍要注意:如果有一些存储空间 与其回调参数相关联,则不会释放该存储 除非你的程序已经做了一些事情来跟踪和发布它。

手动激活事件

在极少数情况下,您可能希望使事件处于活动状态,即使其 条件尚未触发。

接口

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

此函数使事件 ev 变得活动,并带有标志 what(EV_READ、EV_WRITE 和 EV_TIMEOUT 的组合)。该事件确实如此 不需要以前处于挂起状态,激活它也不需要 使其处于待定状态。

警告:在同一事件上递归调用 event_active() 可能会导致 资源枯竭。以下代码片段是操作示例 event_active可能使用不当。

坏例子:用 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”永远。

示例:使用计时器解决上述问题的替代方法

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

示例:使用 event_config_set_max_dispatch_interval() 解决上述问题的替代解决方案

struct event *ev;

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

int main(int argc, char **argv) {
        struct event_config *cfg = event_config_new();
        /* Run at most 16 callbacks before checking 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;
}

此函数在 <event2/event.h> 中定义。它已经存在了 从 Libevent 0.3 开始。

优化常见超时

当前版本的 Libevent 使用二进制堆算法来跟踪 挂起事件的超时。二进制堆提供顺序性能 O(lg n) 用于添加和删除每个事件超时。如果出现以下情况,这是最佳的 您正在添加具有一组随机分布的超时值的事件, 但如果有大量事件具有相同的超时,则不会。

例如,假设您有一万个事件,每个事件都应该 在添加 5 秒后触发其超时。在某种情况下 像这样,你可以通过使用 双链队列实现。

当然,您不希望在全部超时期间使用队列 值,因为队列仅对于常量超时值更快。如果 一些超时或多或少是随机分布的,然后添加 队列的其中一个超时将花费 O(n) 时间,即 比二进制堆差得多。

Libevent 允许您通过将一些超时放在队列中来解决这个问题, 以及二进制堆中的其他人。为此,您向 Libevent 请求 特殊的“通用超时”timeval,然后用于添加事件 有那个时间。如果您有非常多的事件 使用此优化的单个公共超时应该会有所改善 超时性能。

接口

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

此函数将event_base和持续时间作为其参数 要初始化的常见超时。它返回指向特殊 struct timeval,可用于指示事件应为 添加到 O(1) 队列而不是 O(lg n) 堆中。这个特别的 TimeVal 可以在代码中自由复制或分配。它只会 使用您用来构建它的特定基础。不要依赖 它的实际内容:Libevent 使用它们来告诉自己要哪个队列 用。

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

与所有优化函数一样,应避免使用 common_timeout功能,除非您非常确定它很重要 给你的。

此功能是在 Libevent 2.0.4-alpha 中引入的。

讲述一个好的事件,而不是清除的记忆

Libevent 提供了可用于区分 通过将其设置为 0 清除的内存中的初始化事件 (例如,通过使用 calloc() 分配它或使用 memset() 或 bzero())。

接口

int event_initialized(const struct event *ev);

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

警告

这些函数无法可靠地区分初始化事件 和一大堆未初始化的内存。你不应该使用它们 除非您知道有问题的内存已被清除或 初始化为事件。

通常,除非您有一个 考虑到非常具体的应用。event_new() 返回的事件是 始终初始化。

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

struct reader {
    evutil_socket_t fd;
};

#define READER_ACTUAL_SIZE() \
    (sizeof(struct reader) + \
     event_get_struct_event_size())

#define READER_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_initialized() 函数从 Libevent 0.3 开始就存在了。

过时的事件操作函数

Libevent 的 2.0 之前版本没有 event_assign() 或 event_new()。相反,您有关联事件的 event_set() 与“当前”基础。如果您有多个碱基,则需要 记得在之后调用 event_base_set() 以确保 事件与您实际想要使用的基础相关联。

接口

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(),除了它的使用 当前基础。event_base_set() 函数更改基数 与事件关联。

有 event_set() 的变体可以更方便地处理 定时器和信号:evtimer_set() 大致对应于 evtimer_assign(), evsignal_set() 大致对应于 evsignal_assign()。

Libevent 2.0 之前的版本使用“signal_”作为 event_set() 等的基于信号的变体,而不是“evsignal_”。 (也就是说,他们有 signal_set()、signal_add()、signal_del()、 signal_pending() 和 signal_initialized()。真正古老的版本 Libevent(0.6 之前)使用“timeout_”而不是“evtimer_”。因此,如果你是 在进行代码考古时,您可能会看到 timeout_add()、timeout_del()、 timeout_initialized()、timeout_set()、timeout_pending() 等。

代替 event_get_fd() 和 event_get_signal() 函数,较旧 Libevent 版本(2.0 之前)使用两个宏,分别称为 EVENT_FD() 和 EVENT_SIGNAL()。这些宏检查了事件结构的内容 直接,从而阻止了版本之间的二进制兼容性;在 2.0 和 后来它们只是 event_get_fd() 和 event_get_signal() 的别名。

由于 2.0 之前的 Libevent 版本没有 锁定支持,调用任何 从线程外部更改事件相对于基的状态 运行基地。这些包括 event_add()、event_del()、 event_active() 和 event_base_once()。

还有一个 event_once() 函数,它扮演着 event_base_once(),但使用了当前基数。

EV_PERSIST标志之前没有与超时进行合理的互操作 Libevent 2.0 版。而是在事件发生时重置超时 激活后,EV_PERSIST标志对超时没有任何作用。

2.0 之前的 Libevent 版本不支持多个事件 使用相同的 fd 和相同的 READ/WRITE 同时插入。 换言之,一次只能有一个事件等待继续阅读 每个 FD,一次只能有一个事件等待写入 每个 FD。

原文档地址:https://libevent.org/libevent-book/Ref4_event.html

posted @ 2024-06-26 14:12  Philosophy  阅读(64)  评论(0编辑  收藏  举报