创建event_base

在使用任何有趣的 Libevent 函数之前,您需要分配 一种或多种event_base结构。每个event_base结构都包含一组 事件,并可以轮询以确定哪些事件处于活动状态。

如果event_base设置为使用锁,则可以安全地访问它 多个线程。但是,它的循环只能在单个线程中运行。如果 您希望有多个线程轮询 IO,您需要有一个 每个线程event_base。

提示 [Libevent 的未来版本可能支持运行event_bases 跨多个线程的事件。

每个event_base都有一个“方法”,或者一个后端,它用来确定哪个 活动已准备就绪。公认的方法有:

  • select
  • poll
  • epoll
  • kqueue
  • devpoll
  • evport
  • win32的

用户可以使用环境变量禁用特定的后端。如果您想关闭 kqueue 后端,可以设置 EVENT _ NOKQUEUE 环境变量,以此类推。如果希望从程序内部关闭后端,请参阅下面的 event _ config _ void _ method ()说明。

设置默认event_base

event_base_new() 函数分配并返回一个新的事件库 默认设置。它检查环境变量并返回 指向新event_base的指针。如果出现错误,则返回 NULL。

在方法之间进行选择时,它会选择操作系统最快的方法 支持。

接口

struct event_base *event_base_new(void);

对于大多数程序,这就是您所需要的。

event_base_new() 函数在 <event2/event.h> 中声明。它首先 出现在 Libevent 1.4.3 中。

设置复杂的event_base

如果你想更好地控制你得到什么样的event_base,你需要 使用event_config。event_config是一种不透明的结构,可以容纳 有关您对event_base偏好的信息。当你想要一个 event_base,将event_config传递给 event_base_new_with_config()。

接口

struct event_config *event_config_new(void);
struct event_base *event_base_new_with_config(const struct event_config *cfg);
void event_config_free(struct event_config *cfg);

若要分配具有这些函数的event_base,请调用 event_config_new() 以分配新event_config。然后,您在 event_config告诉它您的需求。最后,你可以调用 event_base_new_with_config() 获取新event_base。完成后, 您可以使用 event_config_free() 释放event_config。

接口

int event_config_avoid_method(struct event_config *cfg, const char *method);

enum event_method_feature {
    EV_FEATURE_ET = 0x01,
    EV_FEATURE_O1 = 0x02,
    EV_FEATURE_FDS = 0x04,
};
int event_config_require_features(struct event_config *cfg,
                                  enum event_method_feature feature);

enum event_base_config_flag {
    EVENT_BASE_FLAG_NOLOCK = 0x01,
    EVENT_BASE_FLAG_IGNORE_ENV = 0x02,
    EVENT_BASE_FLAG_STARTUP_IOCP = 0x04,
    EVENT_BASE_FLAG_NO_CACHE_TIME = 0x08,
    EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST = 0x10,
    EVENT_BASE_FLAG_PRECISE_TIMER = 0x20
};
int event_config_set_flag(struct event_config *cfg,
    enum event_base_config_flag flag);

调用 event_config_avoid_method 告诉 Libevent 避免特定的 按名称提供后端。调用 event_config_require_feature() 告诉 Libevent 不使用任何不能提供所有功能的后端。 调用 event_config_set_flag() 告诉 Libevent 设置一个或多个 构造事件库时下面的运行时标志。

event_config_require_features的公认特征值为:

  • EV_FEATURE_ET

    需要支持边缘触发 IO 的后端方法。

  • EV_FEATURE_O1

    需要一种后端方法,其中添加或删除单个 事件,或让单个事件变为活动状态,是一个 O(1) 操作。

  • EV_FEATURE_FDS

    需要可以支持任意文件的后端方法 描述符类型,而不仅仅是套接字。

event_config_set_flag() 的可识别选项值为:

  • EVENT_BASE_FLAG_NOLOCK

    不要为event_base分配锁。设置 此选项可以节省一点时间来锁定和释放 event_base,但会使访问它变得不安全且不能用多个线程访问。

  • EVENT_BASE_FLAG_IGNORE_ENV

    不检查 EVENT_* 环境 选择要使用的后端方法时的变量。之前要好好想想 使用此标志:它可以使用户更难调试交互 在您的程序和 Libevent 之间。

  • EVENT_BASE_FLAG_STARTUP_IOCP

    仅在 Windows 上,此标志使 Libevent 在启动时启用任何必要的 IOCP 调度逻辑,而不是 按需。

  • EVENT_BASE_FLAG_NO_CACHE_TIME

    而不是每隔检查一次当前时间 当事件循环准备好运行超时回调时,请在之后检查 每次超时回调。这可能会使用比您必须使用的更多的 CPU 有意的,所以要小心!

  • EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST

    告诉 Libevent,如果它决定 使用 epoll 后端,使用更快的基于“changelist”的 “changelist” 是安全的 后端。epoll-changelist 后端可以避免不必要的系统调用 同一 FD 在 调用后端的调度函数,但它也会触发内核错误 如果您向 Libevent 提供任何 fds 克隆,则会导致错误的结果 dup() 或其变体。如果使用后端,则此标志无效 除了 Epoll。您还可以通过以下方式打开 epoll-changelist 选项 设置EVENT_EPOLL_USE_CHANGELIST环境变量。

  • EVENT_BASE_FLAG_PRECISE_TIMER

    默认情况下,Libevent 尝试使用最快的可用计时机制 操作系统提供。如果时间较慢 提供更细粒度计时精度的机制,这 flag 告诉 Libevent 改用该计时机制。如果 操作系统没有提供这种更慢但更精确的机制, 此标志无效。

上述操作event_config的函数在成功时都返回 0, -1 失败时。

注意 设置一个需要后端的event_config很容易,您的 操作系统不提供。例如,从 Libevent 2.0.1-alpha 开始,没有 O(1) 用于 Windows 的后端,而 Linux 上没有同时提供这两种功能的后端 EV_FEATURE_FDS和EV_FEATURE_O1。如果您已进行配置 Libevent 无法满足,event_base_new_with_config() 将返回 NULL。

接口

int event_config_set_num_cpus_hint(struct event_config *cfg, int cpus)

不过,此函数目前仅在使用 IOCP 时对 Windows 有用 将来它可能会对其他平台有用。调用它告诉 event_config,它产生的event_base应该尽量好好利用 多线程时给定数量的 CPU。请注意,这只是一个提示: 事件库最终使用的 CPU 可能比您选择的更多或更少。

接口

int event_config_set_max_dispatch_interval(struct event_config *cfg,
    const struct timeval *max_interval, int max_callbacks,
    int min_priority);

此函数通过限制低优先级的数量来防止优先级倒置 在检查更多高优先级事件之前,可以调用事件回调。 如果 max_interval 为非 null,则事件循环会检查每个事件之后的时间 回调,如果已通过,则重新扫描高优先级事件max_interval。 如果max_callbacks为非负数,则事件循环还会检查更多事件 调用max_callbacks回调后。这些规则适用于任何 min_priority 或更高的事件。

示例:首选边缘触发的后端

struct event_config *cfg;
struct event_base *base;
int i;

/* My program wants to use edge-triggered events if at all possible.  So
   I'll try to get a base twice: Once insisting on edge-triggered IO, and
   once not. */
for (i=0; i<2; ++i) {
    cfg = event_config_new();

    /* I don't like select. */
    event_config_avoid_method(cfg, "select");

    if (i == 0)
        event_config_require_features(cfg, EV_FEATURE_ET);

    base = event_base_new_with_config(cfg);
    event_config_free(cfg);
    if (base)
        break;

    /* If we get here, event_base_new_with_config() returned NULL.  If
       this is the first time around the loop, we'll try again without
       setting EV_FEATURE_ET.  If this is the second time around the
       loop, we'll give up. */
}

示例:避免优先级倒置

struct event_config *cfg;
struct event_base *base;

cfg = event_config_new();
if (!cfg)
   /* Handle error */;

/* I'm going to have events running at two priorities.  I expect that
   some of my priority-1 events are going to have pretty slow callbacks,
   so I don't want more than 100 msec to elapse (or 5 callbacks) before
   checking for priority-0 events. */
struct timeval msec_100 = { 0, 100*1000 };
event_config_set_max_dispatch_interval(cfg, &msec_100, 5, 1);

base = event_base_new_with_config(cfg);
if (!base)
   /* Handle error */;

event_base_priority_init(base, 2);

这些函数和类型在 <event2/event.h> 中声明。

EVENT_BASE_FLAG_IGNORE_ENV标志首次出现在 Libevent 2.0.2-alpha 中。 EVENT_BASE_FLAG_PRECISE_TIMER标志首次出现在 Libevent 中 2.1.2-阿尔法。event_config_set_num_cpus_hint() 函数是 Libevent 中的新函数 2.0.7-rc 和 event_config_set_max_dispatch_interval() 是 2.1.1-alpha 中的新功能。 本节中的其他所有内容首先出现在 Libevent 2.0.1-alpha 中。

检查event_base的后端方法

有时您想查看哪些功能实际上可用 event_base,或者它使用哪种方法。

接口

const char **event_get_supported_methods(void);

event_get_supported_methods() 函数返回指向 此版本的 Libevent 中支持的方法的名称。这 数组中的最后一个元素为 NULL。

int i;
const char **methods = event_get_supported_methods();
printf("Starting Libevent %s.  Available methods are:\n",
    event_get_version());
for (i=0; methods[i] != NULL; ++i) {
    printf("    %s\n", methods[i]);
}
注意 此函数返回编译 Libevent 的方法列表 来支持。事实上,您的操作系统可能不会 当 Libevent 尝试运行时,支持它们。例如,您可能在 OSX 版本,其中 kqueue 太有问题而无法使用。

接口

const char *event_base_get_method(const struct event_base *base);
enum event_method_feature event_base_get_features(const struct event_base *base);

event_base_get_method() 调用返回正在使用的实际方法的名称 通过event_base。event_base_get_features() 调用返回的位掩码为 它支持的功能。

struct event_base *base;
enum event_method_feature f;

base = event_base_new();
if (!base) {
    puts("Couldn't get an event_base!");
} else {
    printf("Using Libevent with backend method %s.",
        event_base_get_method(base));
    f = event_base_get_features(base);
    if ((f & EV_FEATURE_ET))
        printf("  Edge-triggered events are supported.");
    if ((f & EV_FEATURE_O1))
        printf("  O(1) event notification is supported.");
    if ((f & EV_FEATURE_FDS))
        printf("  All FD types are supported.");
    puts("");
}

这些函数在 <event2/event.h> 中定义。event_base_get_method() call 最早在 Libevent 1.4.3 中可用。其他人首次出现在 Libevent 2.0.1-alpha 版本。

解除分配event_base

完成event_base后,可以将其取消分配 event_base_free()。

接口

void event_base_free(struct event_base *base);

请注意,此函数不会解除分配任何事件 当前与event_base关联,或关闭其任何套接字,或 释放他们的任何指针。

event_base_free() 函数在 <event2/event.h> 中定义。这是第一个 在 Libevent 1.2 中实现。

在event_base上设置优先级

Libevent 支持为事件设置多个优先级。默认情况下, 但是,event_base仅支持单个优先级。您可以将 通过调用 event_base_priority_init() 来event_base上的优先级数。

接口

int event_base_priority_init(struct event_base *base, int n_priorities);

此函数在成功时返回 0,在失败时返回 -1。基本参数是 要修改event_base,n_priorities是要修改的优先级数 支持。它必须至少为 1。新事件的可用优先级 将从 0(最重要)到 n_priorities-1(最不重要)编号。

有一个常数 EVENT_MAX_PRIORITIES 设置了 n_priorities值。使用更高的 n_priorities的价值。

注意 必须先调用此函数,然后才能激活任何事件。是的 最好在创建event_base后立即调用它。

要查找基地当前支持的优先级数,您可以 调用 event_base_getnpriorities()。

接口

int event_base_get_npriorities(struct event_base *base);

返回值等于 基础。因此,如果 event_base_get_npriorities() 返回 3,则允许的优先级 值为 0、1 和 2。

For an example, see the documentation for event_priority_set below.

默认情况下,将初始化与此库关联的所有新事件 优先级等于 n_priorities / 2。

event_base_priority_init函数在 <event2/event.h> 中定义。它有 自 Libevent 1.0 起可用。event_base_get_npriorities() 函数 是 Libevent 2.1.1-alpha 中的新功能。

在 fork() 之后重新初始化event_base

并非所有事件后端在调用 fork() 后都能干净地保留。因此,如果你的 程序使用 fork() 或相关的系统调用来启动一个新进程, 并且您想在分叉后继续使用event_base,您可以 需要重新初始化它。

接口

int event_reinit(struct event_base *base);

该函数在成功时返回 0,在失败时返回 -1。

struct event_base *base = event_base_new();

/* ... add some events to the event_base ... */

if (fork()) {
    /* In parent */
    continue_running_parent(base); /*...*/
} else {
    /* In child */
    event_reinit(base);
    continue_running_child(base); /*...*/
}

event_reinit() 函数在 <event2/event.h> 中定义。这是第一个 在 Libevent 1.4.3-alpha 中可用。

过时的event_base功能

旧版本的 Libevent 在很大程度上依赖于 “当前”event_base。“当前”event_base是一个全球性的环境 在所有线程之间共享。如果您忘记指定哪个event_base 你想要的,你得到了现在的。因为event_bases不是 线程安全,这可能会变得非常容易出错。

代替 event_base_new(),有:

接口

struct event_base *event_init(void);

此函数的工作方式类似于 event_base_new(),并设置当前基数 到分配的基地。没有其他方法可以更改 当前基础。

本节中的一些event_base函数具有以下变体 在当前基地上操作。这些函数的行为与当前 函数,只是它们没有基本参数。

当前函数 过时的当前基础版本
event_base_priority_init() event_priority_init()
event_base_get_method() event_get_method()

最后更新 世界协调时 2024-02-18 20:10:44

使用事件循环

这些文件版权所有 (c) 2009-2012 由 Nick Mathewson 制作 可在知识共享署名-非商业性使用-相同方式共享下使用 许可证,版本 3.0。未来的版本可能会在 限制性许可证。

此外,这些文档中的源代码示例也已获得许可 在所谓的“3-Clause”或“Modified”BSD许可证下。请参阅随这些文档一起分发的 license_bsd 文件 对于完整的条款。

若要获取本文档最新版本的源代码,请安装 git 并运行“git clone git://github.com/libevent/libevent-book.git”

运行循环

一旦您注册了某些事件的event_base(请参阅下一节 关于如何创建和注册事件),您将希望 Libevent 等待 事件并提醒您注意它们。

接口

#define EVLOOP_ONCE             0x01
#define EVLOOP_NONBLOCK         0x02
// EVLOOP_NO_EXIT_ON_EMPTY existed since Libevent 2.1
#define EVLOOP_NO_EXIT_ON_EMPTY 0x04

int event_base_loop(struct event_base *base, int flags);

默认情况下,event_base_loop() 函数运行一个event_base,直到那里 不再有事件注册。为了运行循环,它会反复检查 是否触发了任何已注册的事件(例如,如果读取 事件的文件描述符已准备好读取,或者如果超时事件的超时为 准备过期)。发生这种情况后,它会将所有触发的事件标记为 “active”,然后开始运行它们。

您可以通过设置一个或多个标志来更改 event_base_loop() 的行为 在其 flags 参数中。如果设置了EVLOOP_ONCE,则循环将等待 直到某些事件变为活动状态,然后运行活动事件,直到没有 更多运行,然后返回。如果设置了EVLOOP_NONBLOCK,则 循环不会等待事件触发:它只会检查是否 任何事件都准备好立即触发,如果是,则运行其回调。

通常,一旦没有挂起或活动事件,循环就会退出。 您可以 通过传递 EVLOOP_NO_EXIT_ON_EMPTY 标志---for 来覆盖此行为 例如,如果您要从其他线程添加事件。如果你 设置EVLOOP_NO_EXIT_ON_EMPTY,循环将继续运行,直到有人 调用 event_base_loopbreak() 或调用 event_base_loopexit() 或错误 发生。

完成后,如果 event_base_loop() 正常退出,则返回 0,如果 它退出是因为后端中的一些未处理的错误,如果它退出,则为 1 因为没有更多的待处理或活动事件。

为了帮助理解,以下是event_base_loop的大致摘要 算法:

伪代码

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

为方便起见,您还可以调用event_base_dispatch方法:

接口

int event_base_dispatch(struct event_base *base);

event_base_dispatch() 调用与 event_base_loop() 相同,没有 设置标志。因此,它会一直运行,直到没有更多的注册事件 或者直到调用 event_base_loopbreak() 或 event_base_loopexit()。

这些函数在 <event2/event.h> 中定义。它们从那时起就存在了 Libevent 1.0 版本。

停止循环

如果希望活动事件循环在所有事件都停止运行之前停止运行 从中删除,您可以调用两个略有不同的函数。

接口

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

event_base_loopexit() 函数告诉event_base在 给定的时间已经过去。如果 tv 参数为 NULL,则event_base停止 无延迟循环。如果event_base当前正在运行回调 对于任何活动 事件,它将继续运行它们,直到它们全部完成后才退出 跑。

event_base_loopbreak() 函数告诉event_base立刻退出其循环。它与 event_base_loopexit(base, NULL) 的不同之处在于,如果 event_base当前正在为任何活动事件运行回调,它将 退出 在完成它当前正在处理的那个之后。

另请注意,event_base_loopexit(base,NULL) 和 event_base_loopbreak(base) 当没有事件循环运行时,采取不同的行动:LoopExit 计划下一个 在下一轮回调后立即停止的事件循环的实例 运行(就好像它是用 EVLOOP_ONCE 调用的一样),而 loopbreak 仅 停止当前正在运行的循环,如果事件循环不停止,则不起作用 运行。

这两种方法在成功时返回 0,在失败时返回 -1。

示例:立即关闭

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

示例:运行事件循环 10 秒,然后退出。

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

有时你可能想判断你对 event_base_dispatch() 的调用是 event_base_loop() 正常退出,或因为调用 event_base_loopexit() 或 event_base_break()。您可以使用这些函数来执行以下操作 判断是否调用了 loopexit 或 break:

接口

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

如果循环停止,这两个函数将返回 true 分别为 event_base_loopexit() 或 event_base_break(),并且为 false 否则。下次启动事件时,将重置其值 圈。

这些函数在 <event2/event.h> 中声明。event_break_loopexit() 函数最早在 Libevent 1.0c 中实现;event_break_loopbreak() 是 首先在 Libevent 1.4.3 中实现。

重新检查事件

通常,Libevent 会检查事件,然后运行所有活动事件 具有最高优先级,然后再次检查事件,依此类推。但 有时您可能希望在当前 回调已运行,并告诉它再次扫描。以此类推 event_base_loopbreak(),您可以使用函数执行此操作 event_base_loopcontinue()。

接口

int event_base_loopcontinue(struct event_base *);

如果我们当前没有,则调用 event_base_loopcontinue() 不起作用 运行事件回调。

此函数是在 Libevent 2.1.2-alpha 中引入的。

检查内部时间缓存

有时您想大致了解内部当前时间 一个事件回调,并且您希望在不调用 getTimeOfDay() 的情况下获取它 你自己(大概是因为你的操作系统实现了 gettimeofday() 作为 syscall,并且您正在尝试避免 syscall 开销)。

在回调中,您可以向 Libevent 询问其对当前 它开始执行这一轮回调的时间:

接口

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

event_base_gettimeofday_cached() 函数将其 tv_out 参数的值设置为缓存时间(如果event_base当前为 执行回调。否则,它会为 evutil_gettimeofday() 调用 实际当前时间。成功时返回 0,失败时返回负数。

请注意,由于 timeval 是在 Libevent 开始运行时缓存的 回调,至少会有点不准确。如果您的回调 需要很长时间才能运行,它可能非常不准确。强制立即 缓存更新,可以调用以下函数:

接口

int event_base_update_cache_time(struct event_base *base);

它在成功时返回 0,在失败时返回 -1,如果基数为 不运行其事件循环。

event_base_gettimeofday_cached() 函数是 Libevent 中的新功能 2.0.4-阿尔法。Libevent 2.1.1-alpha 添加了 event_base_update_cache_time()。

转储event_base状态

接口

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

为了帮助调试你的程序(或调试 Libevent!),你有时可能会 想要event_base中添加的所有事件及其状态的完整列表。 调用 event_base_dump_events() 将此列表写入提供的 stdio 文件。

该列表旨在为人类可读;它的格式将来改变 Libevent 的版本。

此函数是在 Libevent 2.0.1-alpha 中引入的。

对event_base中的每个事件运行函数

接口

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

您可以使用 event_base_foreach_event() 遍历每个当前 与 event_base() 关联的活动或挂起事件。提供的 回调将在每个事件中调用一次,在一个未指定的事件中 次序。将传递 event_base_foreach_event() 的第三个参数 作为每次调用回调的第三个参数。

回调函数必须返回 0 才能继续迭代,或者其他函数 integer 停止迭代。无论回调函数最终的值如何 然后,返回将按 event_base_foreach_function() 返回。

回调函数不得修改其 接收、添加或删除任何事件到事件库,或以其他方式 修改与事件库关联的任何事件或未定义的行为 可能会发生,甚至包括崩溃和堆砸碎。

event_base锁将在呼叫期间保持 event_base_foreach_event() — 这将阻止其他线程执行此操作 任何对event_base有用的东西,所以请确保你的回调 不需要很长时间。

此函数是在 Libevent 2.1.2-alpha 中添加的。

过时的事件循环函数

如上所述,旧版本的 Libevent API 具有全局 “当前”event_base的概念。

本节中的一些事件循环函数具有以下变体 在当前基地上操作。这些函数的行为与当前 函数,只是它们没有基本参数。

当前函数 过时的当前基础版本
event_base_dispatch() event_dispatch()
event_base_loop() event_loop()
event_base_loopexit() event_loopexit()
event_base_loopbreak() event_loopbreak()
注意 因为 event_base 在 Libevent 2.0 之前不支持锁, 这些函数并不是完全线程安全的:这是不允许的 从线程调用 _loopbreak() 或 _loopexit() 函数 而不是执行事件循环的那个。

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