Libevent源码深度刨析

1、Libevent的核心-event

Libevent 是基于事件驱动(event-driven)的,从名字也可以看到 event 是整个库的核心。
event 就是 Reactor 框架中的事件处理程序组件;它提供了函数接口,供 Reactor 在事件发生
时调用,以执行相应的事件处理,通常它会绑定一个有效的句柄。
首先给出 event 结构体的声明,它位于 event_struct.h 文件中:

struct event {
   
    struct event_callback ev_evcallback; // 事件回调结构体,存储回调函数和参数

    // 用于管理超时事件
    union {
   
        TAILQ_ENTRY(event) ev_next_with_common_timeout; // 通用超时事件的链表指针
        int min_heap_idx; // 在最小堆中的位置索引
    } ev_timeout_pos;

    evutil_socket_t ev_fd; // 事件关联的文件描述符

    struct event_base *ev_base; // 事件所属的事件基础结构体指针

    union {
   
        // 用于 I/O 事件
        struct {
   
            LIST_ENTRY (event) ev_io_next; // I/O 事件链表指针
            struct timeval ev_timeout; // I/O 事件的超时时间
        } ev_io;

        // 用于信号事件
        struct {
   
            LIST_ENTRY (event) ev_signal_next; // 信号事件链表指针
            short ev_ncalls; // 信号事件回调的调用次数
            // 通常指向 ev_ncalls 或者为 NULL
            short *ev_pncalls;
        } ev_signal;
    } ev_;

    short ev_events; // 事件感兴趣的事件类型(如 EV_READ、EV_WRITE)
    short ev_res; // 事件回调的结果
    struct timeval ev_timeout; // 事件的超时时间
};
#define TAILQ_ENTRY(type)						\
struct {
     								\
	struct type *tqe_next;	/* next element */			\
	struct type **tqe_prev;	/* address of previous next element */	\
}
#define LIST_ENTRY(type)						\
struct {
     								\
	struct type *le_next;	/* next element */			\
	struct type **le_prev;	/* address of previous next element */	\
}

下面详细解释一下结构体中各字段的含义。
1)ev_events:event关注的事件类型,它可以是以下3种类型:
I/O事件: EV_WRITE和EV_READ
定时事件:EV_TIMEOUT
信号: EV_SIGNAL
辅助选项:EV_PERSIST,表明是一个永久事件
Libevent中的定义为:
#define EV_TIMEOUT 0x01
#define EV_READ 0x02
#define EV_WRITE 0x04
#define EV_SIGNAL 0x08
#define EV_PERSIST 0x10 /* Persistant event */

可以看出事件类型可以使用“|”运算符进行组合,需要说明的是,信号和I/O事件不能同时设置;
还可以看出libevent使用event结构体将这3种事件的处理统一起来;
(2)ev_timeout_pos.ev_next_with_common_timeout和ev_.ev_io.ev_io_next、ev_.ev_io.ev_signal_next都是双向链表的节点。

1.1事件设置接口的函数

要向 libevent 添加一个事件,需要首先设置 event 对象,这通过调用 libevent 提供的函数有:event_assign(), event_base_set(), event_priority_set()来完成;

int event_assign(struct event *ev, struct event_base *base, evutil_socket_t fd, short events, void (*callback)(evutil_socket_t, short, void *), void *arg)

1.设置事件 ev 绑定的文件描述符或者信号,对于定时事件,设为-1 即可;
2.设置事件类型,比如 EV_READ|EV_PERSIST, EV_WRITE, EV_SIGNAL 等;
3.设置事件的回调函数以及参数 arg;
4.初始化其它字段,比如缺省的 event_base 和优先级;

int event_base_set(struct event_base *base, struct event *ev)

设置 event ev 将要注册到的 event_base和优先级;
libevent 有一个全局 event_base 指针 current_base,默认情况下事件 ev将被注册到 current_base 上,使用该函数或在event_assign函数可以指定不同的 event_base;

int event_priority_set(struct event *ev, int pri)

设置event ev的优先级,注意:当ev正处于就绪状态时,不能设置,返回-1。

2、事件处理框架

2.1 事件处理框架-event_base

struct event_base {
   
	//事件选择机制(如 select、poll、epoll)的函数指针和数据
	const struct eventop *evsel;
	//用于存储事件循环相关信息的对象指针,和evsel是类似与类和静态函数的关系
	void *evbase;

	//一个事件变化列表,用于存储在下一次事件分派之前需要告知后端的事件更改。
	struct event_changelist changelist;

	//与信号处理相关的函数指针,用于处理信号事件。
	const struct eventop *evsigsel;
	//与信号相关的数据结构
	struct evsig_info sig;

	//虚拟事件的数量和最大数量。虚拟事件是 libevent 内部用于优化事件处理的事件。
	int virtual_event_count;
	int virtual_event_count_max;
	
	//添加到 event_base 的总事件数量和最大数量
	int event_count;
	int event_count_max;
	
	//当前活动事件的数量和最大数量。
	int event_count_active;
	int event_count_active_max;

	//控制事件循环的变量,用于终止、立即中断或继续事件循环。
	int event_gotterm;
	int event_break;
	//当往活动事件队列添加低于当前执行事件优先级的事件时,被设置为1,在当前处理中的事件处理完之后会马上中断循环退出
	int event_continue;

	//事件当前运行的优先级
	int event_running_priority;

	//一个标志,用于指示是否正在运行 event_base_loop 函数,以防止递归调用。
	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;

	//活动事件队列的数组和数组长度,用于存储已经触发的事件。
	struct evcallback_list *activequeues;
	//队列长度
	int nactivequeues;
	//下次应该激活的event_callback的列表我们处理事件,但不是这次。
	struct evcallback_list active_later_queue;

	//用于存储具有相同超时时间的事件队列。
	struct common_timeout_list **common_timeout_queues;
	//common_timeout_queues中使用的条目数
	int n_common_timeouts;
	//common_timeout_queues超时队列的总大小
	int n_common_timeouts_allocated;

	//文件描述符和信号的映射结构,用于跟踪已启用的事件。
	struct event_io_map io;
	struct event_signal_map sigmap;

	//一个最小堆,用于存储具有超时的事件。
	struct min_heap timeheap;

	//用于优化时间获取的缓存和单调计时器。
	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
	//与线程支持相关的变量,用于在多线程环境中同步对 event_base 的访问。
	//线程ID
	unsigned long th_owner_id;
	//防止对event_base访问冲突的锁指针
	void *th_base_lock;
	//条件变量指针
	void *current_event_cond;
	//在current_event_cond上阻塞的线程数
	int current_event_waiters;
#endif
	//当前正在执行回调的事件。
	struct event_callback *current_event;

#ifdef _WIN32
	//在 Windows 平台上,指向 IOCP(I/O 完成端口)支持结构的指针
	struct event_iocp_port *iocp;
#endif

	/** Flags that this base was configured with */
	enum event_base_config_flag flags;

	//最大处理时间限制,搭配limit_callbacks_after_prio使用
	struct timeval max_dispatch_time;
	//最大回调事件限制,搭配limit_callbacks_after_prio使用
	int max_dispatch_callbacks;
	//优先级限制,低于limit_callbacks_after_prio的优先级队列有处理最大时间和最大回调事件限制
	int limit_callbacks_after_prio;

	//唤醒状态
	int is_notify_pending;
	/** th_notify函数用来唤醒主线程的套接字对。 */
	evutil_socket_t th_notify_fd[2];
	
	//用于在多线程环境中唤醒主线程的机制。
	struct event th_notify;
	int (*th_notify_fn)(struct event_base *base);

	//用于产生公平性的弱随机数生成器的种子。
	struct evutil_weakrand_state weakrand_seed;

	//存储尚未触发的 event_once 事件
	LIST_HEAD(once_event_list, event_once) once_events;

};

(1)evsel 和 evbase可以看作是类和静态函数的关系,比如添加事件时的调用行为:evsel->add(evbase, ev),实际执行操作的是 evbase;这相当于 class::add(instance, ev),instance 就是 class 的一个对象实例。evsel指向了全局变量static const struct eventop *eventops[]中的一个;

struct eventop {
   
	/** The name of this backend. */
	const char *name;
	初始化
	void *(*init)(struct event_base *);
	//注册事件
	int (*add)(struct event_base *, evutil_socket_t fd, short old, short events, void *fdinfo);
	//删除事件
	int (*del)(struct event_base *, evutil_socket_t fd, short old, short events, void *fdinfo);
	//事件分发
	int (*dispatch)(struct event_base *, struct timeval *);
	//注销,释放资源
	void (*dealloc)(struct event_base *);
	//初始化标志
	int need_reinit;
	enum event_method_feature features;
	size_t fdinfo_len;
};

在 libevent 中,每种 I/O demultiplex 机制的实现都必须提供这五个函数接口,来完成自身的初始化、销毁释放;对事件的注册、注销和分发。
2)activequeues 是一个二级指针,前面讲过 libevent 支持事件优先级,因此你可以把它看作是数组,其中的元素activequeues[priority]是一个链表,链表的每个节点指向一个优先级为 priority 的就绪事件 event。
3)io和sigmap是一个哈希数组,哈希值使用的是fd或sig作为数组下班,保存了所有的注册事件 event 的指针。
4)timeheap 是管理定时事件的小根堆,
(5) current_event_cond、current_event_waiters用于当前event_base正在执行的函数和当前添加的信号事件函数回调一致,且当前线程不是event_base运行线程时,用条件变量等待event_base线程的唤醒;event_base线程在事件处理完之后,如果有线程在等待处理完成,那么会唤醒这些线程。
(6)is_notify_pending、th_notify_fd、th_notify、th_notify_fn用于event添加成功、删除成功、或者设置event_base_loop退出标志的时候唤醒event_base运行线程,这些标志只有设置了多线程宏定义才会初始化。th_notify、th_notify_fn初始化如下所示:
其机制其实和触发信号唤醒差不多:在唤醒的时候往fd里写数据进行唤醒,然后在回调里读数据。

//event.c
static int evthread_make_base_notifiable_nolock_(struct event_base *base)
{
   
    void (*cb)(evutil_socket_t, short, void *); // 定义回调函数类型
    int (*notify)(struct event_base *); // 定义通知函数类型

    if (base->th_notify_fn != NULL) {
   
        /* 如果事件基础已经可以被通知,则直接返回成功 */
        return 0;
    }

#if defined(EVENT__HAVE_WORKING_KQUEUE)
    /* 如果事件基础使用的是 kqueue 后端 */
    if (base->evsel == &kqops && event_kq_add_notify_event_(base) == 0) {
   
        base->th_notify_fn = event_kq_notify_base_; // 设置通知函数
        /* 不需要添加事件,因为后端可以自己唤醒自己 */
        return 0;
    }
#endif

#ifdef EVENT__HAVE_EVENTFD
    /* 如果系统支持 eventfd */
    base->th_notify_fd[0] = evutil_eventfd_(0, EVUTIL_EFD_CLOEXEC|EVUTIL_EFD_NONBLOCK);
    if (base->th_notify_fd[0] >= 0) {
   
        base->th_notify_fd[1] = -1; // 初始化第二个文件描述符为 -1
        notify = evthread_notify_base_eventfd; // 设置通知函数
        cb = evthread_notify_drain_eventfd; // 设置回调函数
    } else
#endif
    if (evutil_make_internal_pipe_(base->th_notify_fd) == 0) {
   
        notify = evthread_notify_base_default; // 设置默认的通知函数
        cb = evthread_notify_drain_default; // 设置默认的回调函数
    } else {
   
        return -1; // 如果创建管道失败,则返回错误
    }

    base->th_notify_fn = notify; // 设置事件基础的 notification 函数

    /* 准备一个用于唤醒的事件 */
    event_assign(&base->th_notify, base, base->th_notify_fd[0],
                 EV_READ|EV_PERSIST, cb, base);

    /* 标记这个事件为内部事件 */
    base->th_notify.ev_flags |= EVLIST_INTERNAL;
    event_priority_set(&base->th_notify, 0); // 设置优先级为 0

    return event_add_nolock_(&base->th_notify, NULL, 0); // 添加事件并返回结果
}

2.2 创建和初始化 event_base

创建一个 event_base 对象也既是创建了一个新的 libevent 实例,程序需要通过调用event_init()或event_base_new()(内部调用 event_base_new 函数执行具体操作)函数来创建,该函数同时还对新生成的 libevent 实例进行了初始化。
内部实际调用了event_base_new_with_config函数。
该函数做了一下工作:
分配内存:为新的 event_base 结构体分配内存。
设置标志:如果提供了 event_config 结构体,将其标志复制到 event_base。
检查环境变量:根据是否提供了 event_config 结构体,决定是否检查环境变量。
配置定时器:根据配置和环境变量设置定时器的精确度。
初始化数据结构:初始化时间堆、信号对、通知文件描述符、活动队列等。
选择事件后端:遍历可用的事件操作列表,选择一个合适的事件后端。
初始化事件后端:调用选定的事件后端的初始化函数。
处理线程:如果启用了线程支持,分配锁和条件变量,使事件基础可通知。
启动 IOCP:在 Windows 平台上,如果配置了 IOCP,启动 IOCP 支持。
返回事件基础:返回新创建的 event_base 结构体。
注意:如果event_base默认优先级为1,即没有优先级概念。如果需要设置优先级,那么需要调用event_base_priority_init函数进行优先级的初始化。

2.3 接口函数

Libevent 中对应的接口函数主要就是:

int event_add(struct event *ev, const struct timeval *tv);
int event_del(struct event *ev);
int event_base_loop(struct event_base *base, int flags);
void event_active(struct event *ev, int res, short ncalls);
static int event_process_active(struct event_base *base);

2.3.1 注册事件

int event_add(struct event *ev, const struct timeval *tv)
ev:指向要注册的事件
tv:超时时间
函数将 ev 注册到 ev->ev_base 上,事件类型由 ev->ev_events 指明,如果是IO事件,注册成功,ev将被插入到iomap中;如果 tv 不是 NULL,则会同时注册定时事件,将 ev 添加到 timer堆上;如果超时事件,那么ev会添加到timer堆上。
如果其中有一步操作失败,那么函数保证没有事件会被注册,可以讲这相当于一个原子操作。

int event_add(struct event *ev, 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值