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 提供的 evtimer 和 evtimer_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(¤t_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_base 是 reactor 模式的核心,它通过统一的事件循环 来处理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 内部工作流程:
- 程序通过 event_base_dispatch 进入事件循环,libevent 开始监控所有注册的事件。
- 当有事件发生时,libevent 检测事件类型(如 I/O 可读、信号触发、定时器到期)。
- 根据事件类型和触发条件,libevent 调用对应的回调函数,处理该事件。
- 事件处理完成后,继续监听其他事件,循环往复,直到主动退出。
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
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
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.3 signal事件
未完待续......
4.4 timer事件
定时器事件(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_insert 、min_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 将该事件标记为活动状态。
- 事件循环会在适当的时机调用与该事件相关的回调函数。
关于 小根堆 的实现原理,将另起一篇文章介绍。