Libevent源码剖析-event

1 简介

本文来重点介绍下libevent中的event事件,在类unix系统中编写网络程序时,我们经常需要处理3类事件-IO事件&signal事件&timer事件,libevent通过reactor来注册&调度&处理IO事件,并且也将signal和timer事件借助socket() / accept() / socketpair() / eventfd() / pipe() / pipe2() / timer_createfd()等系统调用,巧妙的将各平台的signal事件timer事件,融入到了libevent的reactor框架中。程序员只需要利用几个简易的api便可实现相应的业务逻辑。

2 event

    首先,让我们从例子入手,来对libevent的event事件,有一个感性认识。

2.1 I/O 事件 (IO events)

  • 这是 libevent 最核心的功能之一,主要用于网络 I/O文件 I/O 的事件处理。libevent 通过监听文件描述符(如 sockets),根据不同的事件(如可读、可写)来执行回调函数。

• 典型的事件类型有:

  • EV_READ:当文件描述符可读时触发(例如,有新的数据可以读取)。
  • EV_WRITE:当文件描述符可写时触发(例如,准备好可以发送数据)。
  • 作用:I/O 事件使得应用程序可以在不阻塞的情况下管理大量连接

使用libevent的api简易实现:

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

void read_cb(evutil_socket_t fd, short event, void *arg) {
    char buf[1024];
    int len = read(fd, buf, sizeof(buf) - 1);
    if (len > 0) {
        buf[len] = '\0';
        printf("Received: %s\n", buf);
    }
}

int main() {
    struct event_base *base;
    struct event *read_event;
    base = event_base_new();

    // 使用 STDIN_FILENO 监听标准输入(文件描述符 0)
    read_event = event_new(base, STDIN_FILENO, EV_READ | EV_PERSIST, read_cb, NULL);
    event_add(read_event, NULL);

    // 启动事件循环
    event_base_dispatch(base);

    // 清理
    event_free(read_event);
    event_base_free(base);
    return 0;
}

2.2 信号事件 (signal events)

  • 信号事件用于处理操作系统发送的信号(如 SIGINT 或 SIGHUP)。libevent 允许程序通过注册信号处理函数来捕获信号并做出响应。
  • 例如,通过监听 SIGINT(Ctrl + C 信号),evsignal_new / event_add,可以让程序优雅地退出或执行特定的清理操作。
  • 事件类型为 EV_SIGNAL,与特定信号相关联。

使用libevent的api简易实现:

#include <event2/event.h>
#include <signal.h>
#include <stdio.h>

void signal_cb(evutil_socket_t sig, short event, void *arg) {
    printf("Caught signal %d, exiting...\n", sig);
    struct event_base *base = (struct event_base *)arg;
    event_base_loopexit(base, NULL);  // 退出事件循环
}

int main() {
    struct event_base *base;
    struct event *signal_event;

    base = event_base_new();

    // 捕获 SIGINT 信号 (Ctrl+C)
    signal_event = evsignal_new(base, SIGINT, signal_cb, base);
    event_add(signal_event, NULL);

    // 启动事件循环
    event_base_dispatch(base);

    // 清理
    event_free(signal_event);
    event_base_free(base);
    return 0;
}

2.3 定时器事件 (timer events)

  • 定时器事件用于管理超时周期性任务。libevent 提供了精确的定时器机制,支持一次性触发(单次定时器)或周期性触发(循环定时器)。
  • 典型使用场景包括定时任务调度、超时处理等。
  • 可以通过设置超时时间来创建定时器事件。libevent 提供的 evtimerevtimer_add 函数可以用来创建和管理定时器事件。

使用libevent的api简易实现:

#include <event2/event.h>
#include <stdio.h>
#include <time.h>

void timer_cb(evutil_socket_t fd, short event, void *arg) {
    time_t current_time = time(NULL);
    printf("Timer triggered at %s", ctime(&current_time));
}

int main() {
    struct event_base *base;
    struct event *timer_event;
    struct timeval five_seconds = {5, 0};  // 5秒

    base = event_base_new();

    // 创建定时器事件,5秒后触发
    timer_event = evtimer_new(base, timer_cb, NULL);
    evtimer_add(timer_event, &five_seconds);

    // 启动事件循环
    event_base_dispatch(base);

    // 清理
    event_free(timer_event);
    event_base_free(base);
    return 0;
}

3 原理

libevent 的 I/O、信号和定时器事件的实现是基于 reactor 模式 的,它通过事件循环不断地监视调度不同类型的事件,当事件触发时,调用对应的回调函数处理。这三种事件类型(I/O、信号和定时器)在 reactor 模式中各自对应不同的任务,但它们都通过同一个事件循环统一调度,形成高效的异步事件处理模型。我们来逐步解释这些事件是如何融入到 reactor 模式中的。

3.1 reactor 模式简介

    reactor 模式是一种用于处理并发 I/O 任务的设计模式。请参见 Libevent源码剖析-reactor 一文,它的核心思想是:

  • 通过一个 事件分发器 (如 libevent 中的 event_base),将所有的 I/O信号定时器事件集中管理。
  • 事件触发时,不是直接执行阻塞的 I/O 操作,而是注册事件和回调,当事件发生时,执行相应的回调函数。
  • 事件循环:不断监视各类事件的发生。
  • 事件多路复用机制:高效监听多个 I/O 事件(如 epoll, kqueue 等)。
  • 事件处理器:为每种类型的事件注册回调,事件触发后执行对应的处理逻辑。

3.2 libevent 中的 reactor 模型

    在 libevent 中,event_basereactor 模式的核心,它通过统一的事件循环来处理 I/O、信号和定时器事件。其工作原理如下:

  • 事件循环 (event_base_dispatch):这是 reactor 模式的调度器,它通过调用底层的 select / epoll / kqueue 等多路复用系统调用来监视各类事件。
  • 回调机制:libevent 允许用户为每种事件(I/O、信号、定时器)注册回调,当事件触发时,libevent 会自动调用对应的回调函数。

3.3 I/O 事件融入 reactor

  • I/O 事件(如 socket 读写)在 reactor 模式中的作用是异步处理网络或文件 I/O,避免阻塞主程序。libevent 将每个 I/O 事件与一个文件描述符(如 socket)绑定,当文件描述符变为可读可写时,事件循环会通过 select / epoll 或 kqueue 等机制监听到并触发回调。
  • 信号事件在 reactor 模式中提供了捕获和处理系统信号的机制。libevent 将信号(如 SIGINT、SIGHUP)视为事件并注册相应的回调。当某个信号被操作系统触发时,libevent 的事件循环会捕获信号并调用相应的处理函数。
  • 定时器事件用于处理超时周期性任务,在 reactor 模式中,它允许事件循环每隔一定时间触发某个回调函数,执行定时任务。libevent 的事件循环会同时监视 I/O 事件信号事件定时器事件,所有这些事件的触发条件(如时间到期)都由同一个事件循环处理。

3.4 统一事件循环中的处理

    在 reactor 模式中,所有的 I/O信号定时器事件都融入到了同一个事件循环 event_base_dispatch 中,libevent 的事件分发器会根据事件类型(I/O、信号、定时器)和触发条件调用相应的回调。

libevent 内部工作流程

  1. 程序通过 event_base_dispatch 进入事件循环,libevent 开始监控所有注册的事件。
  2. 当有事件发生时,libevent 检测事件类型(如 I/O 可读、信号触发、定时器到期)。
  3. 根据事件类型和触发条件,libevent 调用对应的回调函数,处理该事件。
  4. 事件处理完成后,继续监听其他事件,循环往复,直到主动退出。

    libevent 通过这种统一的事件管理,使得程序可以同时处理不同类型的异步任务,避免阻塞,提升程序的并发性能

4 源码剖析

    接下来,我们结合libevent的源码来深入的剖析其实现原理,看看是如何将IO事件signal事件timer事件统一到event_base_dispatch事件循环中处理的。

4.1 几个重要的结构体及API

    在剖析IO事件signal事件timer事件源码前,根据第2节的demo例子,我们先来分析下几个重要的结构体及其API:

4.1.1 event结构体

  •  event 结构体是libevent的核心数据结构之一,用于描述和管理事件。它包含了事件的类型、关联的文件描述符或信号、回调函数以及事件状态等信息。event 结构体通过与事件分发event_base)结合使用,管理 I/O、信号和定时器事件。
  • event结构体是libevent对IO事件&signal事件&timer事件3类事件操作的抽象;
/** A regular event. Uses the evcb_callback callback */
#define EV_CLOSURE_EVENT 0
/** A signal event. Uses the evcb_callback callback */
#define EV_CLOSURE_EVENT_SIGNAL 1
/** A persistent non-signal event. Uses the evcb_callback callback */
#define EV_CLOSURE_EVENT_PERSIST 2
/** A simple callback. Uses the evcb_selfcb callback. */
#define EV_CLOSURE_CB_SELF 3
/** A finalizing callback. Uses the evcb_cbfinalize callback. */
#define EV_CLOSURE_CB_FINALIZE 4
/** A finalizing event. Uses the evcb_evfinalize callback. */
#define EV_CLOSURE_EVENT_FINALIZE 5
/** A finalizing event that should get freed after. Uses the evcb_evfinalize
 * callback. */
#define EV_CLOSURE_EVENT_FINALIZE_FREE 6
/** @} */

// 事件callback结构体定义
struct event_callback {
    // 需要在event_base_dispatch的next cycle中执行的event集
	TAILQ_ENTRY(event_callback) evcb_active_next;
	short evcb_flags;
    // 所属event的优先级,0~n,0表示优先级最高
	ev_uint8_t evcb_pri;	/* smaller numbers are higher priority */
    // 此为以上宏定义flag,表示使用以下union联合体中哪一个callback
	ev_uint8_t evcb_closure;
	/* allows us to adopt for different types of events */
    // 主要是为了应对不同类型的event处理,具体根据evcb_closure来判断类型
    union {
		void (*evcb_callback)(evutil_socket_t, short, void *);
		void (*evcb_selfcb)(struct event_callback *, void *);
		void (*evcb_evfinalize)(struct event *, void *);
		void (*evcb_cbfinalize)(struct event_callback *, void *);
	} evcb_cb_union;
    // 以上callback函数参数
	void *evcb_arg;
};

// 此为对所有IO事件&signal事件及timer事件的抽象
struct event {
    // event的回调
	struct event_callback ev_evcallback;

	/* for managing timeouts */
	union {
        // 下1个需要处理的common timeout event
		TAILQ_ENTRY(event) ev_next_with_common_timeout;
        // 下一个即将超时的小根堆里的timer事件
		int min_heap_idx;
	} ev_timeout_pos;
    // 此为IO事件中对应的socket fd
	evutil_socket_t ev_fd;
    // 此event关联的event_base,属于哪一个event_base实例
	struct event_base *ev_base;

	union {
		/* used for io events */
		struct {
            // 此为将fd的多个IO事件串起来
			LIST_ENTRY (event) ev_io_next;
            // 诸如epoll最后1个参数是timeout,此为其timeout值
			struct timeval ev_timeout;
		} ev_io;

		/* used by signal events */
		struct {
            // 此为将某个signal多次注册的事件及回调串起来,
			LIST_ENTRY (event) ev_signal_next;
            // 此为某signal要注册了多少次
			short ev_ncalls;
			/* Allows deletes in callback */
            // 此signal直执行1次,便在其callback里减1
			short *ev_pncalls;
		} ev_signal;
	} ev_;

    // 读写属性EV_READ / EV_WRITE / EV_SIGNAL / EV_TIMEOUT
	short ev_events;
    // 此event的回调执行完毕的返回值
	short ev_res;		/* result passed to event callback */
    // 此为event的超时值,比如common timeout
	struct timeval ev_timeout;
};

    值得一说的是,libevent实现了1次性event事件,其结构体如下: 

/* Sets up an event for processing once */
struct event_once {
    // 此类event有很多,用链表表达
	LIST_ENTRY(event_once) next_once;
    // 一次性event是用event来实现的
    // 原理:用户侧先通过event_once注册业务回调,然后libevent通过event来实现,给此event注册事件回调,在此事件回调里执行以下cb来唤醒用户侧回调
	struct event ev;

    // 这是业务侧所注册的callback
	void (*cb)(evutil_socket_t, short, void *);
    // callback参数
	void *arg;
};

     这里列举1个event_once使用的例子:

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

void timeout_cb(evutil_socket_t fd, short event, void *arg) {
    printf("Timeout occurred!\n");
}

int main() {
    struct event_base *base;
    struct timeval timeout = {3, 0};  // 3 秒超时

    // 创建 event_base
    base = event_base_new();

    // 注册一个一次性定时器事件,3 秒后触发
    event_base_once(base, -1, EV_TIMEOUT, timeout_cb, NULL, &timeout);

    // 启动事件循环
    event_base_dispatch(base);

    // 释放 event_base
    event_base_free(base);

    return 0;
}

4.1.2 event_base结构体

  • event_base 是 libevent 的核心组件之一,负责管理调度所有的事件。它是一个 事件循环,用来监听和处理各类事件(如 I/O、信号、定时器等)。每个 event_base 代表一个事件分发器,包含事件循环的所有必要数据结构。

  • event_base 提供了一个事件分发机制,所有事件(如 I/O、信号、定时器等)都要注册到一个 event_base 中。

  • 它通过调用底层的事件多路复用机制(如 select、epoll、kqueue 等)来监听多个文件描述符或信号,并在事件触发时调用相应的回调函数。

  • 事件循环会一直运行,直到手动停止或没有事件需要处理。

    在libevent中对event_base结构体的定义,详细阐述如下: 

// 此为reactor的核心,用于libevent中IO事件&signal事件&timer事件的分派
struct event_base {
	/** Function pointers and other data to describe this event_base's
	 * backend. */
    // 对诸如select / poll / epoll / kqueue / devpoll等系统调用操作的抽象
    // 通过它可向系统调用注册&收集&处理&取消注册等各平台多路复用的共有操作
    // 此为预编译时根据configure配置选项所决定使用backend哪一个多路复用
	const struct eventop *evsel;
	/** Pointer to backend-specific data. */
    // 多路复用init操作时,会malloc1个对应的epollop实例,即多路复用的上下文,后续诸如epoll_wait时所需
    // 此eventop有别于上面的eventop,系多路复用上下文实例
	void *evbase;

	/** List of changes to tell backend about at next dispatch.  Only used
	 * by the O(1) backends. */
    // 用于epoll等O(1)事件复杂度的多路复用,用于收集事件变化
    // 此为epoll和kqueue所需
	struct event_changelist changelist;

	/** Function pointers used to describe the backend that this event_base
	 * uses for signals */
    // libevent统一了IO/signal/timer事件的处理,此为统一signal事件处理
	const struct eventop *evsigsel;
	/** Data to implement the common signal handelr code. */
    // 与evbase类似,系signal相关的上下文
	struct evsig_info sig;

	/** Number of virtual events */
	int virtual_event_count;
	/** Maximum number of virtual events active */
	int virtual_event_count_max;
	/** Number of total events added to this event_base */
    // 实际的已关注事件数
	int event_count;
	/** Maximum number of total events added to this event_base */
    // 支持关注的最大事件数
	int event_count_max;
	/** Number of total events active in this event_base */
    // 有多少个event就绪了
	int event_count_active;
	/** Maximum number of total events active in this event_base */
    // 当前event_base最大的active事件数
	int event_count_active_max;

	/** Set if we should terminate the loop once we're done processing
	 * events. */
    // 让event_base_dispatch事件循环优雅的退出
	int event_gotterm;
	/** Set if we should terminate the loop immediately */
    // 让event_base_dispatch事件循环立马退出
	int event_break;
	/** Set if we should start a new instance of the loop immediately. */
    // 启动1个新的事件循环
	int event_continue;

	/** The currently running priority of events */
    // libevent在对event处理的时候,是按优先级进行的,此表示当前运行event的优先级
	int event_running_priority;

	/** Set if we're running the event_base_loop function, to prevent
	 * reentrant invocation. */
    // 表示event_base_dispatch事件循环是否已在运行
	int running_loop;

	/** Set to the number of deferred_cbs we've made 'active' in the
	 * loop.  This is a hack to prevent starvation; it would be smarter
	 * to just use event_config_set_max_dispatch_interval's max_callbacks
	 * feature */
    // 延迟回调队列的大小
	int n_deferreds_queued;

	/* Active event management. */
	/** An array of nactivequeues queues for active event_callbacks (ones
	 * that have triggered, and whose callbacks need to be called).  Low
	 * priority numbers are more important, and stall higher ones.
	 */
    // 此为event_base_dispatch事件循环当前cycle需要执行的callback队列
	struct evcallback_list *activequeues;
	/** The length of the activequeues array */
    // 此为event_base_dispatch事件循环当前cycle需要执行的callback队列
	int nactivequeues;
	/** A list of event_callbacks that should become active the next time
	 * we process events, but not this time. */
    // 此为event_base_dispatch事件循环下次cycle需要执行的callback队列
	struct evcallback_list active_later_queue;

	/* common timeout logic */

	/** An array of common_timeout_list* for all of the common timeout
	 * values we know. */
    // 以下3个字段,是处理common timeout相关逻辑的
    // 此timeout有别于常见的timer,常见的timer是用小根堆来管理的
    // 此为common timeout的队列
	struct common_timeout_list **common_timeout_queues;
	/** The number of entries used in common_timeout_queues */
    // 表示以上common timeout队列大小
	int n_common_timeouts;
	/** The total size of common_timeout_queues. */
    // 已分配的common timeout大小
	int n_common_timeouts_allocated;

	/** Mapping from file descriptors to enabled (added) events */
    // 此为管理所有IO事件及其callback的,即IO事件的所有event实例(已注册的所有event)
	struct event_io_map io;

	/** Mapping from signal numbers to enabled (added) events. */
    // 此为管理所有signal事件及其callback的,即signal时间的所有event实例(已注册的所有signal event)
	struct event_signal_map sigmap;

	/** Priority queue of events with timeouts. */
    // 此为管理所有常见timer的事件及callback的,小根堆
	struct min_heap timeheap;

	/** Stored timeval: used to avoid calling gettimeofday/clock_gettime
	 * too often. */
    // 此为频繁调用gettimeofday/clock_gettime之缓存优化
	struct timeval tv_cache;

	struct evutil_monotonic_timer monotonic_timer;

	/** Difference between internal time (maybe from clock_gettime) and
	 * gettimeofday. */
	struct timeval tv_clock_diff;
	/** Second in which we last updated tv_clock_diff, in monotonic time. */
	time_t last_updated_clock_diff;

#ifndef EVENT__DISABLE_THREAD_SUPPORT
	/* threading support */
	/** The thread currently running the event_loop for this base */
    // 当前运行event_base_dispatch事件循环的线程ID
	unsigned long th_owner_id;
	/** A lock to prevent conflicting accesses to this event_base */
    // 多线程下对event_base的线程安全访问
	void *th_base_lock;
	/** A condition that gets signalled when we're done processing an
	 * event with waiters on it. */
	void *current_event_cond;
	/** Number of threads blocking on current_event_cond. */
	int current_event_waiters;
#endif
	/** The event whose callback is executing right now */
	struct event_callback *current_event;

#ifdef _WIN32
	/** IOCP support structure, if IOCP is enabled. */
    // Windows平台IOCP多路复用相关
	struct event_iocp_port *iocp;
#endif

	/** Flags that this base was configured with */
    // event_base配置flag
	enum event_base_config_flag flags;

	struct timeval max_dispatch_time;
	int max_dispatch_callbacks;
	int limit_callbacks_after_prio;

    // 以下是从其他thread唤醒event_base_dispatch事件循环之用
	/* Notify main thread to wake up break, etc. */
	/** True if the base already has a pending notify, and we don't need
	 * to add any more. */
    // 是否有等待循环event_base_dispatch事件循环的
	int is_notify_pending;
	/** A socketpair used by some th_notify functions to wake up the main
	 * thread. */
    // 唤醒原理是利用类似socket读写来实现的
	evutil_socket_t th_notify_fd[2];
	/** An event used by some th_notify functions to wake up the main
	 * thread. */
    // 唤醒是利用event_base中的event来实现关注就绪读事件的
	struct event th_notify;
	/** A function used to wake up the main thread from another thread. */
    // 唤醒event_base_dispatch后执行的callback
	int (*th_notify_fn)(struct event_base *base);

	/** Saved seed for weak random number generator. Some backends use
	 * this to produce fairness among sockets. Protected by th_base_lock. */
    // 诸如select / poll等多路复用在select返回成功,需要轮询数组检测是否确已就绪
    // 用此技术来增加扫描的公平性
	struct evutil_weakrand_state weakrand_seed;

	/** List of event_onces that have not yet fired. */
    // 只需关注一次的事件,此类event有很多,用链表描述
	LIST_HEAD(once_event_list, event_once) once_events;

};

4.1.3 eventop结构体

  • eventop 结构体定义了底层的事件处理机制接口。它封装了不同的操作系统(或平台)上具体的事件多路复用机制(如 select、poll、epoll、kqueue 等)。通过 eventop 结构体,libevent 可以以统一的方式调用这些底层事件管理函数。

    再来看看各平台多路复用下的eventop操作抽象: 

/**
   A flag used to describe which features an event_base (must) provide.

   Because of OS limitations, not every Libevent backend supports every
   possible feature.  You can use this type with
   event_config_require_features() to tell Libevent to only proceed if your
   event_base implements a given feature, and you can receive this type from
   event_base_get_features() to see which features are available.
*/
enum event_method_feature {
    /** Require an event method that allows edge-triggered events with EV_ET. */
    EV_FEATURE_ET = 0x01,
    /** Require an event method where having one event triggered among
     * many is [approximately] an O(1) operation. This excludes (for
     * example) select and poll, which are approximately O(N) for N
     * equal to the total number of possible events. */
    EV_FEATURE_O1 = 0x02,
    /** Require an event method that allows file descriptors as well as
     * sockets. */
    EV_FEATURE_FDS = 0x04,
    /** Require an event method that allows you to use EV_CLOSED to detect
     * connection close without the necessity of reading all the pending data.
     *
     * Methods that do support EV_CLOSED may not be able to provide support on
     * all kernel versions.
     **/
    EV_FEATURE_EARLY_CLOSE = 0x08
};

/** Structure to define the backend of a given event_base. */
struct eventop {
	/** The name of this backend. */
	const char *name;
	/** Function to set up an event_base to use this backend.  It should
	 * create a new structure holding whatever information is needed to
	 * run the backend, and return it.  The returned pointer will get
	 * stored by event_init into the event_base.evbase field.  On failure,
	 * this function should return NULL. */
    // 比如,对epoll多路复用上下文的初始化操作,并在此对signal进行初始化
	void *(*init)(struct event_base *);
	/** Enable reading/writing on a given fd or signal.  'events' will be
	 * the events that we're trying to enable: one or more of EV_READ,
	 * EV_WRITE, EV_SIGNAL, and EV_ET.  'old' will be those events that
	 * were enabled on this fd previously.  'fdinfo' will be a structure
	 * associated with the fd by the evmap; its size is defined by the
	 * fdinfo field below.  It will be set to 0 the first time the fd is
	 * added.  The function should return 0 on success and -1 on error.
	 */
    // 注册事件,也即关注事件
	int (*add)(struct event_base *, evutil_socket_t fd, short old, short events, void *fdinfo);
	/** As "add", except 'events' contains the events we mean to disable. */
    // 取消注册事件,即不关注读写事件了
	int (*del)(struct event_base *, evutil_socket_t fd, short old, short events, void *fdinfo);
	/** Function to implement the core of an event loop.  It must see which
	    added events are ready, and cause event_active to be called for each
	    active event (usually via event_io_active or such).  It should
	    return 0 on success and -1 on error.
	 */
    // 调度事件
	int (*dispatch)(struct event_base *, struct timeval *);
	/** Function to clean up and free our data from the event_base. */
    // init的反向操作
	void (*dealloc)(struct event_base *);
	/** Flag: set if we need to reinitialize the event base after we fork.
	 */
    // 在fork()调用后,某些多路复用需要重新初始化,比如epoll / devpoll / devport / kqueue 
    // 其他多路复用不涉及,包括win32select,signal也不涉及
	int need_reinit;
	/** Bit-array of supported event_method_features that this backend can
	 * provide. */
    // 各平台多路复用所能支持的独有特性
	enum event_method_feature features;
	/** Length of the extra information we should record for each fd that
	    has one or more active events.  This information is recorded
	    as part of the evmap entry for each fd, and passed as an argument
	    to the add and del functions above.
	 */
	size_t fdinfo_len;
};

    4.1.4 event_base_new

    event_base_new函数很简单,主要是对event_base_new_with_config(cfg)的封装:

struct event_base* event_base_new(void)
{
	struct event_base *base = NULL;
	struct event_config *cfg = event_config_new();
	if (cfg) {
		base = event_base_new_with_config(cfg);
		event_config_free(cfg);
	}
	return base;
}

    所以,展开event_base_new_with_config看其实现: 

struct event_base *
event_base_new_with_config(const struct event_config *cfg)
{
	int i;
	struct event_base *base;
	int should_check_environment;

#ifndef EVENT__DISABLE_DEBUG_MODE
	event_debug_mode_too_late = 1;
#endif

    // 首先是malloc一个event_base实例
	if ((base = mm_calloc(1, sizeof(struct event_base))) == NULL) {
		event_warn("%s: calloc", __func__);
		return NULL;
	}

    // 接收传入的event_config
	if (cfg)
		base->flags = cfg->flags;

	should_check_environment =
	    !(cfg && (cfg->flags & EVENT_BASE_FLAG_IGNORE_ENV));

	{
		struct timeval tmp;
		int precise_time =
		    cfg && (cfg->flags & EVENT_BASE_FLAG_PRECISE_TIMER);
		int flags;
		if (should_check_environment && !precise_time) {
			precise_time = evutil_getenv_("EVENT_PRECISE_TIMER") != NULL;
			base->flags |= EVENT_BASE_FLAG_PRECISE_TIMER;
		}
		flags = precise_time ? EV_MONOT_PRECISE : 0;
		evutil_configure_monotonic_time_(&base->monotonic_timer, flags);

		gettime(base, &tmp);
	}

    // 小根堆的初始化,此小根堆用于timer事件的管理
	min_heap_ctor_(&base->timeheap);

    // ev_signal_pair是用以将signal事件融入到reactor框架之用
    // 就好比1个pipe,一端读一端写,先关注读事件,当通过signal()/sigaction()注册的signal回调触发后,往此管道的写端写入,这样就能唤醒event_base_dispatch事件循环,然后执行ev_signal_pair的读回调
    // 在此读回调里执行执行就绪signal的回调事件
	base->sig.ev_signal_pair[0] = -1;
	base->sig.ev_signal_pair[1] = -1;
    // 与ev_signal_pair类似,用以唤醒event_base_dispatch事件循环线程的
	base->th_notify_fd[0] = -1;
	base->th_notify_fd[1] = -1;

    // 此active_later_queue乃为vent_base_dispatch线程next cycle执行的队列
	TAILQ_INIT(&base->active_later_queue);

    // IO/signal/timer事件队列或小根堆的初始化
	evmap_io_initmap_(&base->io);
	evmap_signal_initmap_(&base->sigmap);
	event_changelist_init_(&base->changelist);

    // 此为多路复用的上下文实例,在对应多路复用的init操作里malloc,在dealloc里释放
	base->evbase = NULL;

    // 配置copy
	if (cfg) {
		memcpy(&base->max_dispatch_time,
		    &cfg->max_dispatch_interval, sizeof(struct timeval));
		base->limit_callbacks_after_prio =
		    cfg->limit_callbacks_after_prio;
	} else {
		base->max_dispatch_time.tv_sec = -1;
		base->limit_callbacks_after_prio = 1;
	}
	if (cfg && cfg->max_dispatch_callbacks >= 0) {
		base->max_dispatch_callbacks = cfg->max_dispatch_callbacks;
	} else {
		base->max_dispatch_callbacks = INT_MAX;
	}
	if (base->max_dispatch_callbacks == INT_MAX &&
	    base->max_dispatch_time.tv_sec == -1)
		base->limit_callbacks_after_prio = INT_MAX;

	for (i = 0; eventops[i] && !base->evbase; i++) {
		if (cfg != NULL) {
			/* determine if this backend should be avoided */
			if (event_config_is_avoided_method(cfg,
				eventops[i]->name))
				continue;
			if ((eventops[i]->features & cfg->require_features)
			    != cfg->require_features)
				continue;
		}

		/* also obey the environment variables */
		if (should_check_environment &&
		    event_is_method_disabled(eventops[i]->name))
			continue;
        // 多路复用的eventop实例,在configure预编译时即存在
		base->evsel = eventops[i];

        // 此为多路复用操作相关的上下文实例
		base->evbase = base->evsel->init(base);
	}

	if (base->evbase == NULL) {
		event_warnx("%s: no event mechanism available",
		    __func__);
		base->evsel = NULL;
		event_base_free(base);
		return NULL;
	}

	if (evutil_getenv_("EVENT_SHOW_METHOD"))
		event_msgx("libevent using: %s", base->evsel->name);

	/* allocate a single active event queue */
	if (event_base_priority_init(base, 1) < 0) {
		event_base_free(base);
		return NULL;
	}

	/* prepare for threading */

#if !defined(EVENT__DISABLE_THREAD_SUPPORT) && !defined(EVENT__DISABLE_DEBUG_MODE)
	event_debug_created_threadable_ctx_ = 1;
#endif

#ifndef EVENT__DISABLE_THREAD_SUPPORT
	if (EVTHREAD_LOCKING_ENABLED() &&
	    (!cfg || !(cfg->flags & EVENT_BASE_FLAG_NOLOCK))) {
		int r;
		EVTHREAD_ALLOC_LOCK(base->th_base_lock, 0);
		EVTHREAD_ALLOC_COND(base->current_event_cond);
		r = evthread_make_base_notifiable(base);
		if (r<0) {
			event_warnx("%s: Unable to make base notifiable.", __func__);
			event_base_free(base);
			return NULL;
		}
	}
#endif

    // windows平台创建IOCP完成端口,并开启worker线程池,开启IOCP服务
#ifdef _WIN32
	if (cfg && (cfg->flags & EVENT_BASE_FLAG_STARTUP_IOCP))
		event_base_start_iocp_(base, cfg->n_cpus_hint);
#endif

	return (base);
}

4.1.5 event_base_dispatch

    这是reactor模式中的eventloop事件循环,用以调度event事件并处理,event_base_dispatch接口很简单,实际上只是对event_base_loop的封装:

int
event_base_dispatch(struct event_base *event_base)
{
	return (event_base_loop(event_base, 0));
}

    展开event_base_loop,看其实现: 

int
event_base_loop(struct event_base *base, int flags)
{
	const struct eventop *evsel = base->evsel;
	struct timeval tv;
	struct timeval *tv_p;
	int res, done, retval = 0;

	/* Grab the lock.  We will release it inside evsel.dispatch, and again
	 * as we invoke user callbacks. */
	EVBASE_ACQUIRE_LOCK(base, th_base_lock);

	if (base->running_loop) {
		event_warnx("%s: reentrant invocation.  Only one event_base_loop"
		    " can run on each event_base at once.", __func__);
		EVBASE_RELEASE_LOCK(base, th_base_lock);
		return -1;
	}
    // 表示此事件循环线程是否已在运行
	base->running_loop = 1;

	clear_time_cache(base);

	if (base->sig.ev_signal_added && base->sig.ev_n_signals_added)
		evsig_set_base_(base);

	done = 0;

#ifndef EVENT__DISABLE_THREAD_SUPPORT
    // reactor事件循环线程ID
	base->th_owner_id = EVTHREAD_GET_ID();
#endif

	base->event_gotterm = base->event_break = 0;

    // 此为eventloop线程主体实现
	while (!done) {
		base->event_continue = 0;
		base->n_deferreds_queued = 0;

		/* Terminate the loop if we have been asked to */
		if (base->event_gotterm) {
			break;
		}

		if (base->event_break) {
			break;
		}

		tv_p = &tv;
		if (!N_ACTIVE_CALLBACKS(base) && !(flags & EVLOOP_NONBLOCK)) {
			timeout_next(base, &tv_p);
		} else {
			/*
			 * if we have active events, we just poll new events
			 * without waiting.
			 */
			evutil_timerclear(&tv);
		}

		/* If we have no events, we just exit */
		if (0==(flags&EVLOOP_NO_EXIT_ON_EMPTY) &&
		    !event_haveevents(base) && !N_ACTIVE_CALLBACKS(base)) {
			event_debug(("%s: no events registered.", __func__));
			retval = 1;
			goto done;
		}

        // 将active_later_queue队列中的event移动到activequeues队列中,并在此cycle中执行事件回调
		event_queue_make_later_events_active(base);

		clear_time_cache(base);

        // 此处使用多路复用来调度IO/signal事件
		res = evsel->dispatch(base, tv_p);

		if (res == -1) {
			event_debug(("%s: dispatch returned unsuccessfully.",
				__func__));
			retval = -1;
			goto done;
		}

		update_time_cache(base);

        // timer事件是在此处调度的
		timeout_process(base);

        // 若activequeues队列仍有活跃的event,在此一并处理
		if (N_ACTIVE_CALLBACKS(base)) {
			int n = event_process_active(base);
			if ((flags & EVLOOP_ONCE)
			    && N_ACTIVE_CALLBACKS(base) == 0
			    && n != 0)
				done = 1;
		} else if (flags & EVLOOP_NONBLOCK)
			done = 1;
	}
	event_debug(("%s: asked to terminate loop.", __func__));

done:
	clear_time_cache(base);
	base->running_loop = 0;

	EVBASE_RELEASE_LOCK(base, th_base_lock);

	return (retval);
}

4.2 IO事件

4.2.1 相关API

    站在使用者的角度来看,让libevent关注IO事件的读写,只需要event_add(ev, tv)即可:

// 对IO事件&signal事件&timer事件的关注,最终都会走到这个接口api
int
event_add(struct event *ev, const struct timeval *tv)
{
	int res;

	if (EVUTIL_FAILURE_CHECK(!ev->ev_base)) {
		event_warnx("%s: event has no event_base set.", __func__);
		return -1;
	}

	EVBASE_ACQUIRE_LOCK(ev->ev_base, th_base_lock);

    // 事件添加的具体实现
	res = event_add_nolock_(ev, tv, 0);

	EVBASE_RELEASE_LOCK(ev->ev_base, th_base_lock);

	return (res);
}

    当不需要关注此event读写事件时,让libevent取消关注IO事件接口: 

// 取消关注IO事件接口
int
event_del(struct event *ev)
{
	return event_del_(ev, EVENT_DEL_AUTOBLOCK);
}

 4.2.2 实现原理

  • libevent对IO事件的管理,都是组织在event_base之struct event_io_map io中的,这是一个数组,数组的下标是fd(fd 是>= 0的int类型,是不是比较巧妙),数组的元素是struct evmap_io,定义在下文;
  • 由以上可知,struct event_io_map io数组,在高并发场景下,是比较庞大的,用数组来组织,其优点是根据fd索引的时间复杂度为O(1)
  • struct event_io_map io只用来对user所注册的event进行组织,而当event就绪后,则会被libevent移动到struct evcallback_list activequeues中,这也是一个数组结构,event就绪后便遍历之并执行event回调;
  • struct evmap_io是对event结构体的封装,是一个双向链表,因为fd上的事件可能会有几个类型的:读事件、写事件、关闭事件等;

关于struct event_io_map io数组结构体,请参见:

    关于struct event_io_map的定义,请参见: 

/* #define HT_CACHE_HASH_VALS */

#ifdef EVMAP_USE_HT
#define HT_NO_CACHE_HASH_VALUES
#include "ht-internal.h"
struct event_map_entry;
HT_HEAD(event_io_map, event_map_entry);
#else
#define event_io_map event_signal_map
#endif

/* Used to map signal numbers to a list of events.  If EVMAP_USE_HT is not
   defined, this structure is also used as event_io_map, which maps fds to a
   list of events.
*/
struct event_signal_map {
	/* An array of evmap_io * or of evmap_signal *; empty entries are
	 * set to NULL. */
	void **entries;
	/* The number of entries available in entries */
	int nentries;
};

     evmap_io定义如下:

// event_dlist是event类型的双向链表
LIST_HEAD (event_dlist, event); 

/** An entry for an evmap_io list: notes all the events that want to read or
	write on a given fd, and the number of each.
  */
struct evmap_io {
    // 这是一个event结构的双向链表
	struct event_dlist events;
    // 是否已关注了read事件
	ev_uint16_t nread;
    // 是否已关注了write事件
	ev_uint16_t nwrite;
    // 是否已关注了close事件
	ev_uint16_t nclose;
};

    event_add(ev, tv)是libevent提供给user的api接口,只是对event_add_nolock(ev, tv)的封装:

/* Implementation function to add an event.  Works just like event_add,
 * except: 1) it requires that we have the lock.  2) if tv_is_absolute is set,
 * we treat tv as an absolute time, not as an interval to add to the current
 * time */
int
event_add_nolock_(struct event *ev, const struct timeval *tv,
    int tv_is_absolute)
{
	struct event_base *base = ev->ev_base;
	int res = 0;
	int notify = 0;

	EVENT_BASE_ASSERT_LOCKED(base);
	event_debug_assert_is_setup_(ev);

	event_debug((
		 "event_add: event: %p (fd "EV_SOCK_FMT"), %s%s%s%scall %p",
		 ev,
		 EV_SOCK_ARG(ev->ev_fd),
		 ev->ev_events & EV_READ ? "EV_READ " : " ",
		 ev->ev_events & EV_WRITE ? "EV_WRITE " : " ",
		 ev->ev_events & EV_CLOSED ? "EV_CLOSED " : " ",
		 tv ? "EV_TIMEOUT " : " ",
		 ev->ev_callback));

	EVUTIL_ASSERT(!(ev->ev_flags & ~EVLIST_ALL));

	if (ev->ev_flags & EVLIST_FINALIZING) {
		/* XXXX debug */
		return (-1);
	}

	/*
	 * prepare for timeout insertion further below, if we get a
	 * failure on any step, we should not change any state.
	 */
	if (tv != NULL && !(ev->ev_flags & EVLIST_TIMEOUT)) {
		if (min_heap_reserve_(&base->timeheap,
			1 + min_heap_size_(&base->timeheap)) == -1)
			return (-1);  /* ENOMEM == errno */
	}

	/* If the main thread is currently executing a signal event's
	 * callback, and we are not the main thread, then we want to wait
	 * until the callback is done before we mess with the event, or else
	 * we can race on ev_ncalls and ev_pncalls below. */
#ifndef EVENT__DISABLE_THREAD_SUPPORT
	if (base->current_event == event_to_event_callback(ev) &&
	    (ev->ev_events & EV_SIGNAL)
	    && !EVBASE_IN_THREAD(base)) {
		++base->current_event_waiters;
		EVTHREAD_COND_WAIT(base->current_event_cond, base->th_base_lock);
	}
#endif

	if ((ev->ev_events & (EV_READ|EV_WRITE|EV_CLOSED|EV_SIGNAL)) &&
	    !(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE|EVLIST_ACTIVE_LATER))) {
        // 这里执行对IO事件的添加
		if (ev->ev_events & (EV_READ|EV_WRITE|EV_CLOSED))
			res = evmap_io_add_(base, ev->ev_fd, ev);
        // 这里执行对signal事件的添加
		else if (ev->ev_events & EV_SIGNAL)
			res = evmap_signal_add_(base, (int)ev->ev_fd, ev);
        // 对于添加成功的event事件,置EVLIST_INSERTED标志位
		if (res != -1)
			event_queue_insert_inserted(base, ev);
		if (res == 1) {
			/* evmap says we need to notify the main thread. */
			notify = 1;
			res = 0;
		}
	}

	/*
	 * we should change the timeout state only if the previous event
	 * addition succeeded.
	 */
    // 这里是对timeout事件的处理逻辑,略去相关code
    ......

    // 若调用event_add(ev, tv)时不在对应event_base的事件循环线程中,则将其唤醒
	/* if we are not in the right thread, we need to wake up the loop */
	if (res != -1 && notify && EVBASE_NEED_NOTIFY(base))
		evthread_notify_base(base);

	event_debug_note_add_(ev);

	return (res);
}

    这里重点关注对IO事件的添加逻辑,对signal事件的添加逻辑其实也一样,只是换了身马甲而已:

/** Expand 'map' with new entries of width 'msize' until it is big enough
	to store a value in 'slot'.
 */
// struct event_io_map数组按2倍扩容
static int
evmap_make_space(struct event_signal_map *map, int slot, int msize)
{
	if (map->nentries <= slot) {
		int nentries = map->nentries ? map->nentries : 32;
		void **tmp;

		while (nentries <= slot)
			nentries <<= 1;

		tmp = (void **)mm_realloc(map->entries, nentries * msize);
		if (tmp == NULL)
			return (-1);

		memset(&tmp[map->nentries], 0,
		    (nentries - map->nentries) * msize);

		map->nentries = nentries;
		map->entries = tmp;
	}

	return (0);
}

/* code specific to file descriptors */

/** Constructor for struct evmap_io */
static void
evmap_io_init(struct evmap_io *entry)
{
	LIST_INIT(&entry->events);
	entry->nread = 0;
	entry->nwrite = 0;
	entry->nclose = 0;
}


/* return -1 on error, 0 on success if nothing changed in the event backend,
 * and 1 on success if something did. */
int
evmap_io_add_(struct event_base *base, evutil_socket_t fd, struct event *ev)
{
	const struct eventop *evsel = base->evsel;
	struct event_io_map *io = &base->io;
	struct evmap_io *ctx = NULL;
	int nread, nwrite, nclose, retval = 0;
	short res = 0, old = 0;
	struct event *old_ev;

	EVUTIL_ASSERT(fd == ev->ev_fd);

	if (fd < 0)
		return 0;

#ifndef EVMAP_USE_HT
    // struct event_io_map数组按原来大小的2倍扩容
	if (fd >= io->nentries) {
		if (evmap_make_space(io, fd, sizeof(struct evmap_io *)) == -1)
			return (-1);
	}
#endif
    // struct event_io_map是1个数组,其下标为fd的值(fd >= 0的int类型)
    // 其数组元素是struct evmap_io,它是一个双向链表,fd上的读写和close事件由此链表来组织
    // 所以,很容易根据fd找到对应的slot,并完成初始化操作
	GET_IO_SLOT_AND_CTOR(ctx, io, fd, evmap_io, evmap_io_init,
						 evsel->fdinfo_len);

	nread = ctx->nread;
	nwrite = ctx->nwrite;
	nclose = ctx->nclose;

	if (nread)
		old |= EV_READ;
	if (nwrite)
		old |= EV_WRITE;
	if (nclose)
		old |= EV_CLOSED;

	if (ev->ev_events & EV_READ) {
		if (++nread == 1)
			res |= EV_READ;
	}
	if (ev->ev_events & EV_WRITE) {
		if (++nwrite == 1)
			res |= EV_WRITE;
	}
	if (ev->ev_events & EV_CLOSED) {
		if (++nclose == 1)
			res |= EV_CLOSED;
	}
	if (EVUTIL_UNLIKELY(nread > 0xffff || nwrite > 0xffff || nclose > 0xffff)) {
		event_warnx("Too many events reading or writing on fd %d",
		    (int)fd);
		return -1;
	}
	if (EVENT_DEBUG_MODE_IS_ON() &&
	    (old_ev = LIST_FIRST(&ctx->events)) &&
	    (old_ev->ev_events&EV_ET) != (ev->ev_events&EV_ET)) {
		event_warnx("Tried to mix edge-triggered and non-edge-triggered"
		    " events on fd %d", (int)fd);
		return -1;
	}

	if (res) {
		void *extra = ((char*)ctx) + sizeof(struct evmap_io);
		/* XXX(niels): we cannot mix edge-triggered and
		 * level-triggered, we should probably assert on
		 * this. */
        // 让多路复用开始关注fd的读写关闭事件
		if (evsel->add(base, ev->ev_fd,
			old, (ev->ev_events & EV_ET) | res, extra) == -1)
			return (-1);
		retval = 1;
	}

	ctx->nread = (ev_uint16_t) nread;
	ctx->nwrite = (ev_uint16_t) nwrite;
	ctx->nclose = (ev_uint16_t) nclose;
    // 最后,将此ev插入到该fd事件链表的头部
	LIST_INSERT_HEAD(&ctx->events, ev, ev_io_next);

	return (retval);
}

    值得一提的是,当取消关注IO事件时,并没有缩容逻辑处理。 

 4.3 signal事件

4.3.1 相关API

    关于signal事件相关操作接口,其实只是对IO事件操作的封装而已,一个纯粹的马甲,证据在此:

/**
   @name evsignal_* macros

   Aliases for working with signal events
 */
/**@{*/
// signal事件相关操作接口也只是IO相关接口的马甲
#define evsignal_add(ev, tv)		event_add((ev), (tv))
#define evsignal_assign(ev, b, x, cb, arg)			\
	event_assign((ev), (b), (x), EV_SIGNAL|EV_PERSIST, cb, (arg))
#define evsignal_new(b, x, cb, arg)				\
	event_new((b), (x), EV_SIGNAL|EV_PERSIST, (cb), (arg))
#define evsignal_del(ev)		event_del(ev)
#define evsignal_pending(ev, tv)	event_pending((ev), EV_SIGNAL, (tv))
#define evsignal_initialized(ev)	event_initialized(ev)
/**@}*/

4.3.2 实现原理

    与IO事件的处理一致,包括api接口和数据结构,因此,不再赘述。

    此处列出struct event_signal_map signal数据结构图:

4.3.3 融入到reactor

    对于signal事件,值得关注的是,libevent如何将signal事件融入到reactor框架中。大致思路如下:

  • 利用IO事件event,和socket() / accept() / socketpair() / pipe() / pipe2() / timer_createfd()等系统调用,将signal事件转化为对IO事件的处理;
  • 具体是,在eventopinit操作中完成了对多路复用的初始化后,调用evsig_init_(base)利用对应平台以上系统调用创建1个管道,管道的一端用来读、另外一端用来写,然后关注读端的读事件;
  • user侧再调用evsignal_add完成对signal事件的添加,并调用signal()/sigaction()来注册signal回调,此signal事件由event_base之struct event_signal_map signal来组织;
  • 当signal触发时,内核会回调此callback,然后libevent在此callback里,往管道的一端写入1个字节,如此便可唤醒event_base_dispatch事件循环线程了,是不是比较奇妙;
  • libevent对signal事件的管理,和IO事件如出一辙,即struct event_signal_map signal,一个数组,其下标为signal值(0-64),数组元素则是evmap_signal类型指针,是不是很巧妙;

    struct event_signal_map数组元素是一个evmap_signal类型指针,指向的是一个event类型的双向链表: 

/* An entry for an evmap_signal list: notes all the events that want to know
   when a signal triggers. */
struct evmap_signal {
	struct event_dlist events;
};

4.4 timer事件

4.4.1 相关API

    与signal事件操作接口类似,timer事件操作的接口也只是对IO事件操作接口的封装,只是一个简单的adpter,换了一身马甲:

/**
   @name evtimer_* macros

    Aliases for working with one-shot timer events */
/**@{*/
// timer事件相关操作接口只是IO相关接口的马甲
#define evtimer_assign(ev, b, cb, arg) \
	event_assign((ev), (b), -1, 0, (cb), (arg))
#define evtimer_new(b, cb, arg)	       event_new((b), -1, 0, (cb), (arg))
#define evtimer_add(ev, tv)		event_add((ev), (tv))
#define evtimer_del(ev)			event_del(ev)
#define evtimer_pending(ev, tv)		event_pending((ev), EV_TIMEOUT, (tv))
#define evtimer_initialized(ev)		event_initialized(ev)
/**@}*/

4.4.2 实现原理 

    定时器事件(timer event) 是通过精确管理事件的超时机制来实现的。它的原理基于 小根堆(min-heap) 数据结构,以高效管理多个定时器事件的触发时间,并与 I/O 事件、信号事件等统一处理。

    定时器事件本质上是通过对每个事件设定超时时间,在事件循环中定期检查哪些事件已经超时,并执行其回调函数。libevent 使用 struct event 中的 定时器字段 来存储与定时器事件相关的信息。

定时器事件的工作原理

    定时器事件本质上是通过对每个事件设定超时时间,在事件循环中定期检查哪些事件已经超时,并执行其回调函数。libevent 使用 struct event 中的 定时器字段 来存储与定时器事件相关的信息。

定时器事件的基本原理

  • 超时设置:当用户注册一个定时器事件时,可以指定一个超时时间(timeval 结构),libevent 将这个时间与当前时间相加,得到事件的到期时间。

  • 事件管理:libevent 使用一个 小根堆 来管理所有的定时器事件。小根堆是一种数据结构,用于高效地维护多个事件的到期时间。堆的根节点是当前最早到期的事件,libevent 可以通过不断从堆中取出根节点来确定哪些事件需要处理

  • 事件循环中的处理:在事件循环的每次cycle中,libevent 会检查堆顶的定时器事件,看它是否已经超时。如果超时,则将该事件从堆中移除,并执行与该事件相关联的回调函数。

  • 如果没有定时器超时,则继续等待或处理其他类型的事件(如 I/O 事件或信号事件)。

与 I/O 事件的集成

  • 定时器事件与 I/O 事件在 libevent 中被统一处理。当事件循环等待 I/O 事件时,会设置一个超时时间,并在超时后优先处理定时器事件。这使得定时器和 I/O 事件可以同时存在于同一个事件循环中。

定时器事件的实现步骤

1.注册定时器事件

定时器事件的注册过程类似于 I/O 事件,只是需要额外的超时参数:

struct event *ev = evtimer_new(base, callback, arg);

或者通过 event_add 函数直接注册一个定时器事件:

struct timeval tv;
tv.tv_sec = 5;  // 设置定时器为5秒
tv.tv_usec = 0;
event_add(ev, &tv);

在 event_add 中,当检测到有超时参数时,libevent 将这个事件添加到定时器的小根堆中。

2. 小根堆管理

    libevent 的定时器事件基于一个 小根堆 来管理。小根堆是一个优先级队列,能够高效地找到最早到期的事件。

  • 当一个定时器事件被添加时,它的到期时间会被插入到小根堆中,libevent 会根据到期时间对堆进行排序。
  • 每次从堆中取出到期时间最早的事件时,只需要访问堆的根节点,这个操作的复杂度为 O(1),删除堆顶元素的复杂度为 O(log N),其中 N 是定时器事件的数量。

    libevent 使用 min_heap 相关函数来管理这个堆。min_heap_insertmin_heap_erase 等函数用于在堆中插入删除定时器事件。

3. 事件循环中的定时器处理

    libevent 在事件循环中,会在每次处理 I/O 事件时顺带检查定时器事件。它的工作流程如下:

  • 在调用 event_base_loop 时,libevent 会调用底层的 I/O 多路复用函数(如 select、epoll_wait)来等待 I/O 事件。
  • 此外,libevent 会为这些 I/O 多路复用函数设置一个超时时间,确保不会无限期阻塞,超时时间由小根堆中的最早到期事件决定。
  • 当 I/O 函数返回时,libevent 会首先检查定时器堆顶的事件是否已经超时,如果超时则触发该事件的回调。
  • 如果定时器事件没有超时或尚未到期,libevent 会继续等待 I/O 事件。

4. 定时器事件触发

    当定时器事件到期时:

  • 如果是一次性timer事件,libevent 会从定时器堆中移除该事件,否则会再次关注此timer事件。
  • 调用 event_active 将该事件标记为活动状态
  • 事件循环会在适当的时机调用与该事件相关的回调函数。

    关于 小根堆 的实现原理,将另起一篇文章介绍。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/899982.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ESP32开发__ESP-IDF, ESP-ADF官网下载,安装及环境配置

前言 不说废话&#xff0c;直面“干货”。最近公司项目涉及基于 ESP32 系列芯片开发&#xff0c;那我们新手小白如何准备相关工作及快速入门&#xff0c;本篇文章旨在&#xff1a;介绍ESP32&#xff0c;指导用户搭建 ESP32 硬件开发的软件环境&#xff08; ESP-IDF V5.2.1 和 …

解码专业术语——应用系统开发项目中的专业词汇解读

文章目录 引言站点设置管理具体要求包括&#xff1a; Footer管理基于URL的权限控制利用数据连接池优化数据库操作什么是数据连接池&#xff1f;优化的优势 利用反射改造后端代码&#xff0c;AJAX反射的作用及其在后端代码中的应用AJAX 实现前后端无刷新交互 引言 创新实践项目二…

Bash 中的 ${} 和 $() 有什么区别 ?

Bash (Bourne-Again SHell) 是一种流行的 Unix SHell&#xff0c;用于编写脚本。如果您使用 Bash 脚本&#xff0c;那么了解不同的语法元素对于提高脚本的效率和避免错误是很重要的。 在本文中&#xff0c;我们将解释 Bash 中 ${} 和 $() 语法之间的区别&#xff0c;并向您展示…

「iOS」——AFNetworking的简单使用

iOS学习 前言原生网络请求使用AFNetworking库进行网络请求具体使用 单例创建的原因单例使用 总结 前言 我们之前学习了通过OC原生内容进行网络申请&#xff0c;AFNetworkikng第三方库的使用&#xff0c;可以极大地简化网络申请的代码量。 原生网络请求 网络请求主要分为上面五…

JAVA单列集合

List系列集合:添加的元素是 有序、可重复、有索引 Set系列集合:添加的元素是 无序、不重复、无索引 Collection Collection是单列集合的接口&#xff0c;它的功能是全部单列集合都可以继承使用的 public boolean add(E e) 把给定的对象添加到当前集合中 public void …

【大数据学习 | Zookeeper】Zookeeper的选举机制

zookeeper的选举机制分为第一次启动和非第一次启动两种情况。 1. 选举机制 - > 第一次启动 (1)服务器1启动&#xff0c;发起一次选举。服务器1投自己一票。此时服务器1票数一票&#xff0c;不够半数以上(3票)&#xff0c;选举无法完成&#xff0c;服务器1状态保持为 LOOKIN…

docker run 命令解析

docker run 命令解析 docker run 命令用于从给定的镜像启动一个新的容器。这个命令可以包含许多选项&#xff0c;下面是一些常用的选项&#xff1a; -d&#xff1a;后台运行容器&#xff0c;并返回容器ID&#xff1b;-i&#xff1a;以交互模式运行容器&#xff0c;通常与 -t …

中国信通院联合中国电促会开展电力行业企业开源典型实践案例征集

自2021年被首次写入国家“十四五”规划以来&#xff0c;开源技术发展凭借其平等、开放、协作、共享的优秀创作模式&#xff0c;正持续成为推动数字技术创新、优化软件生产模式、赋能传统行业转型升级、助力企业降本增效的重要引擎。电力是国民经济的重要基础性产业&#xff0c;…

Atlassian Team ‘24 Europe:推出Rovo、开发人员AI助手、新版Jira等多款AI创新,重塑团队协作

过去一周&#xff0c;Atlassian Team 24 Europe在巴塞罗那盛大举行&#xff01;展示了Atlassian在推动团队协作和业务发展方面的最新成果与前沿见解。 本文&#xff0c;Atlassian联合创始人兼首席执行官Mike Cannon-Brookes为我们分享了Atlassian在AI创新和系统化工作两项关键任…

2019年计算机网络408真题解析

第一题&#xff1a; 解析&#xff1a;OSI参考模型第5层完成的功能 首先&#xff0c;我们需要对OSI参考模型很熟悉&#xff1a;从下到上依次是&#xff1a;物理层-数据链路层-网络层- 运输层-会话层-表示层-应用层&#xff0c;由此可知&#xff0c;题目要问的是会话层的主要功能…

python 爬虫 入门 二、数据解析(正则、bs4、xpath)

目录 一、待匹配数据获取 二、正则 三、bs4 &#xff08;一&#xff09;、访问属性 &#xff08;二&#xff09;、获取标签的值 &#xff08;三&#xff09;、查询方法 四、xpath 后续&#xff1a;登录和代理 上一节我们已经知道了如何向服务器发送请求以获得数据&#x…

三周精通FastAPI:10 Cookie 参数 和Cookie 参数模型

官方文档&#xff1a;Cookie 参数 - FastAPI Cookie 参数 定义 Cookie 参数与定义 Query 和 Path 参数一样。 源码&#xff1a; from typing import Annotatedfrom fastapi import Cookie, FastAPIapp FastAPI()app.get("/items/") async def read_items(ads_id…

PHP泵的比例流量控制阀放大器

01 PHP 05 PCS005、01 PHP 1 PCS005、01 PHP 2 PCS005、01 PHP 3 PCS005&#xff0c;01 FCV 2 M、01 FCV 3 M、01 PHP 05 PCLS005、01 PHP 1 PCLS005、01 PHP 2 PCLS005、01 PHP 3 PCLS005&#xff0c;FCV比例流量控制阀旨在与Berarma的PHP2和PHP3泵最佳集成&#xff0c;但由于…

戴尔电脑win11找不到D盘的解决办法

新公司给配的戴尔电脑&#xff0c;系统是win11&#xff0c;第一天用的好好地&#xff0c;第二天不知道什么原因&#xff0c;D盘找不到了&#xff0c;在网上搜了好多教程也没用&#xff0c;最后咨询客服&#xff0c;找到了解决办法 若【磁盘管理】界面看不到硬盘分区或无法进入…

比亚迪车机安装第三方应用教程

比亚迪车机安装第三方应用教程 比亚迪车机U盘安装APP&#xff0c; 无论是dlink3.0还是4.0都是安卓系统&#xff0c;因此理论上安卓应用是都可以安装的&#xff0c;主要就是横屏和竖屏的区别。在比亚迪上安装软件我主要推荐两种方法。 第一种&#xff0c;直接从电脑端下载安装布…

《计算机视觉》—— 基于dlib库的人检检测

文章目录 一、dlib库的安装1. 通过PyCharm的Settings安装2. 通过Anaconda安装&#xff08;适用于Windows等操作系统&#xff09;3. 通过命令行安装4.懒人安装 二、基于dlib库的人检测1.对图像进行人脸检测2.打开电脑摄像头&#xff0c;检测人脸 一、dlib库的安装 在PyCharm中&…

域渗透AD渗透攻击利用 MS14-068漏洞利用过程 以及域渗透中票据是什么 如何利用

目录 wmi协议远程执行 ptt票据传递使用 命令传递方式 明文口令传递 hash口令传递 票据分类 kerberos认证的简述流程 PTT攻击的过程 MS14-068 漏洞 执行过程 wmi协议远程执行 wmi服务是比smb服务高级一些的&#xff0c;在日志中是找不到痕迹的&#xff0c;但是这个主…

mongodb在linux下的部署

目录 版本部署上传到服务器中创建目录&#xff0c;解压创建mongodb.conf添加环境变量添加到系统服务中centos6 中添加到服务中 重新加载服务并启动卸载&#xff0c;mongodb.conf配置项等 创建用户&#xff0c;设置用户名密码角色启动验证验证 常见知识mongodb角色mongodb.conf配…

如何禁止上班期间浏览无关网站?

禁止员工在上班期间浏览无关网页主要是为了提升工作效率和生产力&#xff0c;确保员工能够专注于工作任务。同时&#xff0c;这种做法有助于降低网络安全风险&#xff0c;防止恶意软件和钓鱼攻击&#xff0c;减少数据泄露和法律风险&#xff0c;维护公司的专业形象&#xff0c;…

数据结构深度优先搜索遍历连通图+非连通图(C语言代码+遍历+终端输入内容)

首先数据结构(C语言版第二版)的关于深度优先搜索遍历连通图的图G4如下: 使用邻接表去创建上面这个无向图&#xff0c;然后再使用书本DFS函数以及DFSTraverse函数实现深度优先搜索遍历 #define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> #include<stdlib.h> #…