libevent源码解析--event,event_callback,event_base

1.概述

实现一个基础tcp网络库,以基于tcp网络库构建服务端应用,客户端应用为起点,我们的核心诉求有:
a. tcp网络库管理工作线程

b. tcp网络库产生服务端对象,通过启动接口,开启服务端监听。进一步,对于服务端对象我们希望:
b.1. 网络库内部帮助我们监控监听描述符可读事件,自动帮我们处理此事件,产生被动连接。
b.2. 可以在被动连接产生,被动连接关闭时触发我们提供得事件回调函数,通知应用层执行必要处理。
b.2. 网络库内部在执行accept调用时,自动分配被动连接对象。进一步,对于被动连接对象我们希望:
b.2.1.网络库内部帮助我们监听被动连接对象上可读事件,自动帮我们处理此事件,然后触发我们提供的包长回调函数,将当前收取到的数据作为参数。
b.2.2.网络库内部判断当收取的数据达到一个完整包时,触发我们提供的收包回调函数
b.2.3.通过数据发送接口, 将数据通过套接字发送出去。
b.2.4.通过被动连接断开接口,主动断开被动连接。
b.3.通过服务端销毁接口,断开服务端上所有被动连接,停止监听,释放所有关联资源。

c.tcp网络库产生客户端对象。进一步,对于客户端对象我们希望,
c.1.通过连接接口,创建套接字,发起到目标的连接,在连接成功或失败时,触发我们提供的事件回调函数
c.2.通过断开接口,主动断开和对端的连接。
c.3.网络库内部帮助我们监听描述符可读事件,自动帮我们处理此事件,然后触发我们提供的包长回调函数,将当前收取到的数据作为参数。
c.4.网络库内部判断当收取的数据达到一个完整包时,触发我们提供的收包回调函数
c.5.通过数据发送接口,将数据通过套接字发送出去。
c.6.通过客户端销毁接口,断开客户端连接,释放所有关联资源。

d.tcp网络库集成日志功能,可以记录各种级别的日志信息,帮助分析内部运行。
e.tcp网络库集成信号处理功能,帮助处理信号。
f.tcp网络库集成定时机制,帮助我们处理定时任务。

作为一个完备的网络库,以下是一些关联的诉求:
a. 支持流量控制
b. 支持dns功能
c. 对http服务端,客户端提供支持。
d. 支持rpc
e. 支持加密通讯
f. 支持序列化,反向序列化

libevent是一个可以帮助我们实现上述核心诉求的功能完备的tcp网络库。但是libevent在机制封装方面并不完善,也许是为了提供最大的灵活性,将一部分库的封装工作委托给了用户来处理。

2.event

网络库最核心的部分是其工作线程,我们可以想象,工作线程的主要任务就是通过io复用器监控多个套接字所注册的事件是否发生。在事件发生时,触发相应套接字相应事件的处理函数完成事件处理。

这里涉及两个概念,一个是事件,一个是事件分发。
对于事件,我们用event来描述。
我们可以向event_base注册event,这个过程的意思是告诉event_base需要帮我们监控对应的事件。
我们可以向event_base取消注册event,这个过程的意思是告诉event_base不再需要帮我们监控此事件。

libevent支持的event可以用来实现:
(1). 套接字可写,可读,关闭事件监控。
(2). 信号监控。
(3). 定时机制。

我们以下分析的基于服务于套接字可写,可读,关闭事件监控的event
2.1.结构
2.1.1.event

struct event {
	struct event_callback ev_evcallback;
	evutil_socket_t ev_fd;
	short ev_events;
	short ev_res;		
	struct event_base *ev_base;
};

服务于套接字可读,可写,关闭事件监控的event结构如上。
各个字段含义:
(1). ev_fd,要监控的套接字
(2). ev_events,要监控的事件集合EV_READEV_WRITEEV_CLOSED
EV_READ的意思是监控套接字内核接收缓存区是否存在数据可供读取。
EV_WRITE的意思是监控套接字内核发送缓存区是否存在空间可供数据写入。
EV_CLOSED的意思是,在注册了EV_READ,但没注册EV_CLOSED,我们需要在可读事件处理中recv返回0,才能感知对端关闭了连接,但若注册了EV_CLOSED,我们可以提前感知到关闭事件。目前只有epoll支持此特性。

服务于套接字的event,除了上述用于描述监控事件类型的标志还支持以下标志:
a. EV_PERSIST,意思是当监控的事件产生后,需要执行一次事件处理。但此后依然需要继续维持原有监控。若没此标志,一旦监控的事件产生后,后续将不会继续监控了。
b. EV_ET,这个标志对于epoll具有意义。具体表现及使用规范放在io复用器分析部分。
c. EV_FINALIZE,表示此event的释放应该采用异步释放机制。何为异步释放机制?可以参考event_callbackEVLIST_FINALIZING标志的分析。

(3). ev_res,用于在监控的事件实际产生时,这里放置实际产生的事件类型。ev_res应该是ev_events的子集。
(4). ev_baseevent需要注册到 event_base 让其帮助我们监控事件,ev_base 指向关联的event_base
(5). ev_evcallback, 在每个event里我们既要说明清楚要监控的事件,监控方式。又要说明清楚,这个事件产生后,如何执行事件处理。我们用event_callback来说明事件产生后如何处理。

2.1.2.event_callback

struct event_callback {
	TAILQ_ENTRY(event_callback) evcb_active_next;
	short evcb_flags;
	ev_uint8_t evcb_pri;	
	ev_uint8_t 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;
	void *evcb_arg;
};

如果说event代表的是事件,那么event_callback代表的是事件的分发。
libevent中,事件需要经过分发转化为event_callback才能得到处理。也存在我们仅仅向event_base加入一个event_callback使得event_base可以在其处理流程里触发我们event_callback里面的处理机制的行为,所以需要用一个union来将不同使用场景下的回调处理函数集成到一个位置。evcb_closure是用来区分处理场景。

有了上述的铺垫,event_callback中各个字段含义为:
(1). evcb_active_nextevent_base需要将后续得到处理的event_callback放在链式容器管理。借此evcb_active_next实现链式容器。
(2). evcb_flags,标志信息。
我们暂且忽略服务于信号处理,定时机制的eventevent_callback
那么,对于服务于套接字事件监控eventevent_callback,及不依赖于eventevent_callback,可用标志及其含义为:
a. EVLIST_INIT
我们说过event_callback可以依附于event而存在,此时,event表示要监控的事件,event_callback表示监控事件产生时如何处理。这种情形下,event_callbackevcb_flags包含此标志。当event_callback不依附于event而存在时,无此标志。
b. EVLIST_INSERTED
对服务于套接字事件监控eventevent_callback,当event加入到event_base中后,其内的event_callbackevcb_flags包含此标志。
c. EVLIST_INTERNAL
有些event,并非外部用户注册到event_base的。
而是event_base自身需要实现某些特性,比如通知机制,自己向自己注册的。对这样的event,其内的event_callbackevcb_flags包含此标志。
d. EVLIST_ACTIVE
event_base中一次循环里需要被处理的event_callback会被加入到两个集合里。一个称为立即处理集合,一个称为延迟处理集合。
event_callback被加入到立即处理集合时,其evcb_flags包含此标志。
e. EVLIST_ACTIVE_LATER
event_callback被加入到延迟处理集合时,其evcb_flags包含此标志。

f. EVLIST_FINALIZING
我们考虑两种场景:
f.1.我们向event_base注册了某个event
在后续某个时刻,比如可能我们完成了需要的操作。我们需要取消注册此event并释放关联资源。
多线程并发下,event_base自身的锁,保证了多线程下event_base自身及外部使用者改变event_base内部状态时的互斥性。由于event_base在执行回调处理期间是释放锁的。

如果我们要取消注册的这个event所属的event_callback内的回调函数正被event_base执行。
如果我们让外部用户取消注册这个event的行为立即完成,那么用户随后很可能认为注册已被取消,此时我们可以释放event及关联资源了。而event_callback的回调处理又很可能会访问到eventevent_callback或关联的资源。这时就会引发程序崩溃。

所以,这时安全的选择是:
f.1.1.取消注册过程阻塞等待,待event_base结束此event_callback的回调处理后再继续运行。
这样,外部用户在取消注册后,再释放资源就是安全的。
f.1.2.取消注册过程不阻塞等待。
但为了保证安全释放。我们手动分发此event,将其ev_res设置为EV_FINALIZE,表示其被分发的原因是需要释放。为其event_callbackevcb_flags添加EVLIST_FINALIZING。表示此event_callback服务于后续的资源异步释放。
外部用户此时在取消注册后不执行event及其关联资源的释放。将释放动作放在一个typedef void (*event_finalize_callback_fn)(struct event *, void *);类别的回调函数里。将此函数名设置到event_callbackevcb_evfinalize中。

这样,通过异步释放资源,既保证了取消关联过程不阻塞,又保证了后续资源的安全释放。

f.2.我们向event_base注册了某个不依赖于eventevent_callback
在后续某个时刻,比如可能我们完成了需要的操作。我们需要取消注册此event_callback并释放关联资源。
多线程并发下,event_base自身的锁,保证了多线程下event_base自身及外部使用者改变event_base内部状态时的互斥性。由于event_base在执行回调处理期间是释放锁的。

如果我们要取消注册的这个event_callback内的回调函数正被event_base执行。
如果我们让外部用户取消注册这个event_callback的行为立即完成,那么用户随后很可能认为注册已被取消,此时我们可以释放event_callback及关联资源了。而event_callback的回调处理又很可能会访问到event_callback或关联的资源。这时就会引发程序崩溃。

所以,这时安全的选择是:
f.2.1.取消注册过程阻塞等待,待event_base结束此event_callback的回调处理后再继续运行。
这样,外部用户在取消注册后,再释放资源就是安全的。
f.2.2.取消注册过程不阻塞等待。
但为了保证安全释放。我们手动分发此event_callback,为其evcb_flags添加EVLIST_FINALIZING。表示此event_callback服务于后续的资源异步释放。
外部用户此时在取消注册后不执行event_callback及其关联资源的释放。将释放动作放在一个void (*cb)(struct event_callback *, void *)类别的回调函数里。将此函数名设置到event_callbackevcb_cbfinalize中。

这样,通过异步释放资源,既保证了取消关联过程不阻塞,又保证了后续资源的安全释放。

(3). evcb_pri
前面提到event_base一次循环里需处理的event_callback会被加入到两个集合里。
对于立即处理集合,又会按照优先级划分为多个子集合。evcb_pri表示此event_callback被放入立即激活集合的哪个子集合。

(4). evcb_closure
由于event_callback可以依附于event使用;可以独立使用;可以在依附于event下用于异步释放;可以在独立使用下用于异步释放;上述每种使用场景下,执行回调处理时的函数原型是不同的。这需要通过evcb_closure来指明。

依附于服务于套接字事件的event使用下,又可具备持久,非持久的性质。这些性质也需要通过evcb_closure来指明。依附于event下用于异步释放时,又可具备回调后自动释放event,不自动释放event的性质。这些性质也需要通过evcb_closure来指明。
综合,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;

a. EV_CLOSURE_EVENT_PERSIST
表示此event_callback依附于event(服务于套接字事件),且关联的event是持久的。
相应的此时evcb_cb_union.evcb_callback有效。参数1为套接字,参数2为实际发生的事件,参数3为提供的回调参数。
b. EV_CLOSURE_EVENT
表示此event_callback依附于event(服务于套接字事件),且关联的event是非持久的。
相应的此时evcb_cb_union.evcb_callback有效。参数1为套接字,参数2为实际发生的事件,参数3为提供的回调参数。
c. EV_CLOSURE_CB_SELF
表示此event_callback不依附于event,独立使用。
相应的此时evcb_cb_union.evcb_selfcb有效。参数1event_callback自身指针,参数2为提供的回调参数。
d. EV_CLOSURE_EVENT_FINALIZE
表示此event_callback依附于event,用于执行异步释放,且无需自动释放event
相应的此时evcb_cb_union.evcb_evfinalize有效。参数1为依附的event指针,参数2为提供的回调参数。
e. EV_CLOSURE_EVENT_FINALIZE_FREE
表示此event_callback依附于event,用于执行异步释放,且需自动释放event
相应的此时evcb_cb_union.evcb_evfinalize有效。参数1为依附的event指针,参数2为提供的回调参数。
f. EV_CLOSURE_CB_FINALIZE
表示此event_callback不依附于event,用于异步释放。
相应的此时evcb_cb_union.evcb_cbfinalize有效。参数1为自身指针,参数2为提供的回调参数。
(5). evcb_cb_union
参考(4)evcb_cb_union中使用那个需要和evcb_closure保持一致。
(6). evcb_arg
用于提供自定义的回调参数。

2.1.3.event_base

struct event_base {
	const struct eventop *evsel;
	void *evbase;
	int event_count;
	int event_count_max;
	int event_count_active;
	int event_count_active_max;
	int event_gotterm;
	int event_break;
	int event_continue;
	int event_running_priority;
	struct event_callback *current_event;
	int running_loop;
	int n_deferreds_queued;
	struct evcallback_list *activequeues;
	int nactivequeues;
	struct evcallback_list active_later_queue;
	struct event_io_map io;
	unsigned long th_owner_id;
	void *th_base_lock;
	void *current_event_cond;
	int current_event_waiters;

	int is_notify_pending;
	evutil_socket_t th_notify_fd[2];
	struct event th_notify;
	int (*th_notify_fn)(struct event_base *base);
};

我们依赖event_base实现事件循环,进而得到工作线程的逻辑。
作为承载事件分发的载体:
(1). event_base支持服务于套接字的event的注册,取消注册。
event_base可通过io复用器监控注册event的事件,在事件产生时用event下的event_callback来分发event
(2). 支持独立使用的event_callback的激活与取消
通过独立使用event_callback加入event_base的激活结构,使得在event_base的事件循环里执行关联的处理逻辑。
(3). 在事件分发后,对激活的event_callback执行其处理逻辑实现事件处理。
(4). 支持通知机制,方便外部及时将event_base从阻塞等待io事件里唤醒来执行后续处理。

event_base依赖io复用器实现对事件的监控。我们以下以epoll为例分析事件监控,事件自动分发的实现。
event_base 中各个字段含义为:
(1). evsel
一个eventop结构指针

struct eventop {
	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 *);
	enum event_method_feature features;
	size_t fdinfo_len;
};

event_base实现事件监控,事件自动分发需要io复用器的支持。eventop抽象出了一个io复用器需要支持的操作集合。
结构各个字段含义:
a. name,名称.
b. init,初始化操作.
c. add,向io复用器新增对套接字的某些事件的监控操作.
d. del,从io复用器移除对套接字的某些事件的监控操作.
e. dispatch,利用io复用器实现事件等待,事件分发操作.
f. dealloc,资源释放阶段用于执行清理动作。
g. featuresio复用器支持的特性集合。
h. fdinfo_len,每个套接字执行adddel时最后一个参数可传递自定义参数,fdinfo_len表示这个参数尺寸。
具体到epoll这个io复用器,其该结构字段取值为:

const struct eventop epollops = {
	"epoll",
	epoll_init,
	epoll_nochangelist_add,
	epoll_nochangelist_del,
	epoll_dispatch,
	epoll_dealloc,
	EV_FEATURE_ET|EV_FEATURE_O1|EV_FEATURE_EARLY_CLOSE,
	0
};

特性标志放在后续讲解。
(2). evbase
每个io复用器在执行init操作时,会返回一个关联对象指针,用于辅助此io复用器实现事件监控,事件分发。
epoll为,返回以下类型的一个实例对象的指针.

struct epollop {
	struct epoll_event *events;
	int nevents;
	epoll_handle epfd;
	int timerfd;
};

(3). event_count
用于统计event_base中此时容纳的外部event数量。
(4). event_count_max
event_count历史最大值
(5). event_count_active
用于统计event_base中此时激活集合,延迟激活集合中event_callback对象总数。
(6). event_count_active_max
event_count_active历史最大值
(7). event_gottermevent_breakevent_continue
用于event_base事件循环流程控制
(8). running_loop
用于防止对一个event_base并发执行多个事件循环。
(9). n_deferreds_queued
用于统计一次循环里有多少不依赖eventevent_callback对象从延迟集合转移到激活集合。
(10). event_running_priority
event_base的事件循环此时在对立即激活的某个event_callback执行其回调处理。则表示当前处理的event_callback的优先级。
(11). current_event
event_base的事件循环在执行EV_CLOSURE_EVENT_PERSIST,EV_CLOSURE_EVENT,EV_CLOSURE_CB_SELF类型event_callback的回调函数时,代表相应的event_callback对象指针。
(12). activequeues
用于容纳激活event_callback对象的队列数组
(13). nactivequeues
数组中元素数量
(14). active_later_queue
容纳延迟激活event_callback对象的队列
(15). event_io_map

#define event_io_map event_signal_map
struct event_signal_map {
	void **entries;
	int nentries;
};
struct evmap_io {
	struct event_dlist events;
	ev_uint16_t nread;
	ev_uint16_t nwrite;
	ev_uint16_t nclose;
};

我们通过一个event_io_map 实例对象来对添加到event_base的所有服务于套接字事件监控的event进行管理。
它的entries是一个指针数组,每个数组的元素指向类型为evmap_io 的实例对象。
当一个套接字关联到多个event,并将这些event添加到event_base后。
针对此套接字将占据entries数组中一项,套接字值作为数组索引。
这个套接字的多个event共享一个evmap_io 实例对象。该对象各个字段含义如下:
events,用于将多个event链接起来。
nread,记录这些event中需要监控EV_READ类型事件的event数量。
nwrite,记录这些event中需要监控EV_WRITE类型事件的event数量。
nclose,记录这些event中需要监控EV_CLOSE类型事件的event数量。
由于一个套接字可以关联到多个event对象并将这些event对象均添加到event_base,所以采用上述结构管理添加到event_base的所有服务于套接字事件监控的event很有必要。
a. 这样我们只需在某个event添加后导致套接字的某个事件类型关联的event数量发生从0到非0的变化时,再通过io复用器的addio复用器新增套接字的对应类型事件监控即可。
b. 同样的,只需在某个event移除后导致套接字的某个事件类型关联的event数量发生从非00的变化时,再通过io复用器的delio复用器移除套接字的对象类型事件监控即可。
c. 这样也会我们在event_base运行事件循环过程中,外部从event_base里快速移除某个event提供了可能。因为一旦我们将eventevent_base移除后,即使io复用器得到了套接字的某个事件,但若对应的event不存在了,相应的依附于此eventevent_callback也就不会被激活执行事件处理了。
(16). th_owner_id
用于表示当前执行此event_base的事件循环的线程的id
(17). th_base_lock
指向此event_base拥有的互斥锁对象指针。event_base通过互斥锁保证多线程环境下对event_base字段访问的互斥性。
event_base的事件循环会在阻塞等待io事件,触发event_callback回调处理期间释放互斥锁。其他时候,持有互斥锁。
event_base的互斥锁是非递归互斥锁。
(18). current_event_cond
指向此event_base拥有的条件变量对象指针。
互斥锁搭配条件变量可实现线程间的同步。比如前面说的event释放安全性里,当要移除并随后释放的event是正在指向回调处理的event_callback所依附的那个时,除了采用异步回调来安全释放,还有一种就是采用同步等待。同步等待需要条件变量来执行等待。event_base中在回调结束发现有人在等待,通过触发条件变量的广播通知来唤醒所有等待者。
互斥锁搭配条件变量实现线程间同步,总是遵循以下模式:

// 等待者:
获取锁
while(条件不足)
	进入等待 // 进入等待会自动释放互斥锁。被唤醒继续前自动获得互斥锁。
处理
释放锁

// 唤醒者:
获取锁
处理
有人等待且此时可唤醒,唤醒
释放锁

(19). current_event_waiters
表示当前在等待当前event_callback回调处理结束的等待者个数.
(20). is_notify_pending
event_base支持在事件循环阻塞于等待io事件时,使用者可利用通知机制及时将event_base从阻塞等待中唤醒,从而执行事件循环后续的处理。
比如event_base正在阻塞等待io事件,而我们此时想结束事件循环的运行,就可以先设置其event_break1,再将其及时从阻塞中唤醒。以便及时结束事件循环运行。
is_notify_pending表示此时是否存在一个进行中的唤醒处理,在唤醒对应的回调处理结束后is_notify_pending重新设置为false
(21). th_notify_fd
为唤醒机制服务,在利用eventfd实现的唤醒机制下,th_notify_fd[0]存储了用于唤醒的eventfd套接字。
(22). th_notify_fn
为唤醒机制服务,实现唤醒的方法,在利用eventfd实现的唤醒机制下,此方法向eventfd写入数据,引发其可读事件。从而使得event_base从阻塞等待事件中醒来。
(22). th_notify
为唤醒机制服务。在利用eventfd实现的唤醒机制下,我们需要通过此event来表示需要持续监控eventfd的可读事件。并在event_base初始化时将其注册到event_base,只有这样,当我们通过th_notify_fneventfd写入数据后,event_base才能从阻塞等待事件中醒来。

3.动作
3.1.io复用器提供的动作
(1). 初始化
我们考察epoll作为io复用器的初始化。
a. 首先需要通过epoll_create创建epoll描述符
b. 然后分配一个epollop实例,并为其epoll_event数组分配空间。
c. 实例地址会作为io复用器init函数返回值存储到event_baseevbase中。

/* typedef union epoll_data {
	 void *ptr;
	 int fd;
	 uint32_t u32;
	 uint64_t u64;
} epoll_data_t;
struct epoll_event {
	 uint32_t events;	
	 epoll_data_t data;
} __EPOLL_PACKED;*/
struct epollop {
	struct epoll_event *events;
	int nevents;
	epoll_handle epfd;
};

epollop其字段含义为:
a. events,数组首元素指针。其元素epoll_eventlinux下结构,用于向epoll注册套接字监控时,提供监控事件集合及一个自定义数据。
b. nevents,数组容量。
c. epfd,用以支持io复用的epoll套接字。
(2). 事件监控添加
我们分析,epoll下如何向epoll套接字新增对套接字的事件监控。
libevent采取的策略是列举所有可能性组合,针对每种组合指定对应的操作策略。

#!/usr/bin/python2
def get(old,wc,rc,cc):
    if ('xxx' in (rc, wc, cc)):
        return "0",255
    if ('add' in (rc, wc, cc)):
        events = []
        if rc == 'add' or (rc != 'del' and 'r' in old):
            events.append("EPOLLIN")
        if wc == 'add' or (wc != 'del' and 'w' in old):
            events.append("EPOLLOUT")
        if cc == 'add' or (cc != 'del' and 'c' in old):
            events.append("EPOLLRDHUP")
        if old == "0":
            op = "EPOLL_CTL_ADD"
        else:
            op = "EPOLL_CTL_MOD"
        return "|".join(events), op
    if ('del' in (rc, wc, cc)):
        delevents = []
        modevents = []
        op = "EPOLL_CTL_DEL"
        if 'r' in old:
            modevents.append("EPOLLIN")
        if 'w' in old:
            modevents.append("EPOLLOUT")
        if 'c' in old:
            modevents.append("EPOLLRDHUP")
        for item, event in [(rc,"EPOLLIN"), (wc,"EPOLLOUT"), (cc,"EPOLLRDHUP")]:
            if item == 'del':
                delevents.append(event)
                if event in modevents:
                    modevents.remove(event)
        if modevents:
            return "|".join(modevents), "EPOLL_CTL_MOD"
        else:
            return "|".join(delevents), "EPOLL_CTL_DEL"
    return 0, 0

def fmt(op, ev, old, wc, rc, cc):
    entry = "{ %s, %s }," %(op, ev)
    print "\t/* old=%3s, write:%3s, read:%3s, close:%3s */\n\t%s" % (old, wc, rc, cc, entry)
    return len(entry)

for old in ('0','r','w','rw','c','cr','cw','crw'):
    for wc in ('0', 'add', 'del', 'xxx'):
        for rc in ('0', 'add', 'del', 'xxx'):
            for cc in ('0', 'add', 'del', 'xxx'):
                op,ev = get(old,wc,rc,cc)
                fmt(op, ev, old, wc, rc, cc)

上述脚本执行后将生成一个包含512项的数组。
a. 数组的里每一项的op指示了应采取的操作类型可以是:EPOLL_CTL_ADD,EPOLL_CTL_DEL,EPOLL_CTL_MOD
b. 数组中ev表示了对应操作下的事件集合。
ADD操作,ev里表示新增后需监控的事件集合。
DEL操作,ev里表示要移除的事件集合,原有监控将全部被移除还可能额外移除原来没监控的事件类型。
MOD操作,ev里表示修改后最终将监控的事件集合。
c. 生成的512项的数组,可用9个比特位来索引。
9个比特位每一位的含义将如下:

Bit 0: close change is add
Bit 1: close change is del
Bit 2: read change is add
Bit 3: read change is del
Bit 4: write change is add
Bit 5: write change is del
Bit 6: old events had EV_READ
Bit 7: old events had EV_WRITE
Bit 8: old events had EV_CLOSED

d. 上述数组起作用的前提是
我们每次对epoll套接字执行操作是,要么需新增对某些事件的监控,要么移除对某些事件的监控。
不存在,一次操作里,既要新增对某些事件的监控,又要移除对另一些事件的监控的情况。

(3). 事件监控移除
参考事件监控添加。
我们总是可以基于本次操作前已经存在的事件监控集合old,本次要新增的事件类型集合,或本次要移除的事件类型集合。找到脚本生成数组里对应项,然后op是我们要执行的操作,ev是此操作下要传递的事件集合。

添加和移除注意点:
a. 执行EPOLL_CTL_MOD操作,返回错误码ENOENT。可以用EPOLL_CTL_ADD针对ev再次尝试下。
b. 执行EPOLL_CTL_ADD操作,返回错误码EEXIST。可用EPOLL_CTL_MOD再次尝试下。
c. 执行EPOLL_CTL_DEL操作,返回错误码ENOENTEBADFEPERM,一般不认为是出错。可记录日志。按成功返回。

出现上述情况,一般由于使用者对套接字关闭前未取消注册等使用不当造成,我们做相应补救。

(4). 自动分发事件
dispatch中,我们释放event_base的互斥锁。
通过epoll_wait阻塞等待监控的事件产生。
然后,再次获取锁。
对产生事件的套接字执行分发处理。

值得注意的是,epoll下:
a. EPOLLHUP
表示读写都关闭。当本端调用shutdown(SHUT_RDWR)时,或者对端发送RST时,会产生EPOLLHUP事件。
b. EPOLLRDHUP
表示对端关闭了发送功能,本端收到此通知时不会再继续读到对端发来的数据了,即读关闭。对端内核不能再往内核缓冲区中增加新的内容,但已经在内核缓冲区中的内容,本端用户态依然能够读取到。
对端发送FIN或者调用close或者shutdown(SHUT_WR)时,会产生EPOLLRDHUP事件。
c. EPOLLERR
套接字描述符上产生错误。
d. EPOLLIN
套接字描述符产生可读事件。
e. EPOLLOUT
套接字描述符产生可写事件。

分发处理中对有事件产生的套接字描述符:
a. dispatch中会将产生的事件类型统一转化为EV_READ,EV_WRITE,EV_CLOSED
b. dispatch会分析挂在产生事件的套接字上的每个event
event里要监控的事件集合包含本次产生的事件,则激活此event,即将依附其的event_callback设置其字段并加入激活集合实现分发。注意的是依附的event_callback在加入激活集合后,其evcb_flags将含EVLIST_ACTIVE

3.2.event_base提供的动作
3.2.1.注册event
注册过程有了前面的铺垫,可以简要描述为:
a. 获得event_base的互斥锁。
b. 向其io结构在指定描述符所在槽位里插入此event。引发此套接字事件监控变化时,需借助io复用器的add实现注册变更。且此时为了使得io复用器可以及时监控最新的事件类型,应触发一次通知机制。
c. 更新相应字段,注意的是依附的event_callback在所依附的event被插入io结构后其evcb_flags将含EVLIST_INSERTED标志。
d. 释放event_base的互斥锁

3.2.2.取消注册event
取消注册过程有了前面的铺垫,可以简要描述为:
a. 获得event_base的互斥锁。
b. 若依附的event_callbackevcb_flags包含EVLIST_FINALIZING,这表达的意思是此event之前已做了取消处理,依附的event_callback被激活用于异步释放。此时我们应该释放锁。结束处理。
c. 若依附的event_callback此时位于激活集合或延迟激活集合,则我们将其从集合移除并更新相应字段。
d. 若event此前被添加到event_baseio结构,则将其从io结构指定描述符所在槽位移除。引发此套接字事件监控变化时,需借助io复用器的del实现注册变更。若此后,io结构中没有外部eventevent_base中也没活动的event_callback,应触发一次通知机制,以避免不必要的阻塞等待。
e. 针对要移除的event依附的event_callback此时正被回调处理,且移除是外部线程发起的,且eventev_events不含EV_FINALIZE,默认下我们需要释放锁,阻塞等待此event_callback的回调执行完毕,再获得锁。继续。前面描述ev_events标志时说过,只有某个event希望移除并采用异步方式执行释放时,才会设置EV_FINALIZE
f. 释放锁,结束。

3.2.3.手动分发event
对于event,除了注册到event_base并等待事件产生时自动分发使得依附其的event_callback中的回调处理被触发。
我们也可直接针对event采用手动分发的方式,将依附于其的event_callback加入激活集合,并在eventev_res中记录手动分发的原因。
过程可简要描述为:
a. 获得event_base的互斥锁。
b. 若依附其的event_callbackevcb_flagsEVLIST_FINALIZING,则表示此event已被移除,依附其的event_callback加入激活集合用于异步释放。此时,应释放锁。结束。
c. 设置eventev_res记录得到分发的原因。
d. 将依附其的event_callback加入激活集合。
e. 若是外部线程执行的手动分发,则需触发event_base激活机制,避免阻塞等待io事件。
f. 释放锁,结束。

3.2.4.手动分发event到延迟激活集合
类似3.2.3,只不过此时依附的event_callback被分发到延迟激活集合。
延迟激活集合中的event_callbackevent_base事件循环下一轮会转入立即激活集合。

3.2.5.手动分发event_callback
我们前面说过。对套接字事件监控,是event其内包含event_callback的模式。此时,注册,取消注册,自动分发,手动分发操作的对象都是event,操作中会相应操作依附其的event_callback

但我们也允许独立存在的不依附于eventevent_callback。对于这样的event_callback对象,若希望在event_base的事件循环中执行其内的回调处理,我们只能采用手动分发的方式,将event_callback加入到激活集合来实现。

3.2.6.手动分发event_callback到延迟激活集合
类似3.2.5,只不过分发到了延迟激活集合。

3.2.7.取消的event_callback
对于独立的event_callback或依附于eventevent_callback,我们可通过取消操作在event_callback已经位于激活或延迟激活集合时,将其从集合移除,从而使得其回调处理不会被执行。

对依附于eventevent_callback,操作应该均以event为对象进行。所以,我们转而去执行event的取消注册即可。
对独立使用的event_callback,操作可简要描述为:
a. 获得event_base的互斥锁。
b. 若event_callbackevcb_flagsEVLIST_FINALIZING,表示此event_callback之前已被移除,此刻处于激活集合用于异步释放机制。我们应释放锁。结束。
c. 若event_callback位于激活或延迟激活集合,将其从集合移除。
d. 释放锁。结束。

3.2.8.事件循环
事件循环进入前需设置event_baseth_owner_id为执行事件循环的线程id

const struct eventop *evsel = base->evsel;
struct timeval tv;
struct timeval *tv_p;
int res, done, retval = 0;
EVBASE_ACQUIRE_LOCK(base, th_base_lock);
base->th_owner_id = EVTHREAD_GET_ID();
base->event_gotterm = base->event_break = 0;
while (!done) {
	// 每一轮开始前,重置
	base->event_continue = 0;
	base->n_deferreds_queued = 0;
	// 跳出检测
	if (base->event_gotterm) {
		break;
	}
	if (base->event_break) {
		break;
	}
	tv_p = &tv;
	// 在激活的event_callback不存在时
	if (!N_ACTIVE_CALLBACKS(base)) {
		timeout_next(base, &tv_p);// tv_p存储当前距离最近一个超期点的差值
	} else {
		evutil_timerclear(&tv);// 存在激活的event_callback,无需阻塞等待io事件
	}
	// 默认下,event_base里不存在注册的外部event,且激活的event_callback也没有时,会结束事件循环
	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;
	}
	// 每一轮将延迟激活的每个event_callback转变为立即激活
	event_queue_make_later_events_active(base);
	// 借助io复用器等待事件&分发事件,tv_p指示了分发时阻塞等待最大值
	res = evsel->dispatch(base, tv_p);
	// 事件循环过程遭遇非预期错误,需结束事件循环
	if (res == -1) {
		event_debug(("%s: dispatch returned unsuccessfully.", __func__));
		retval = -1;
		goto done;
	}
	if (N_ACTIVE_CALLBACKS(base)) {
		// 对立即激活集合中event_callback执行处理
		event_process_active(base);
	}
}
event_debug(("%s: asked to terminate loop.", __func__));
done:
base->running_loop = 0;
EVBASE_RELEASE_LOCK(base, th_base_lock);
return (retval);

3.2.9.激活事件处理

struct evcallback_list *activeq = NULL;
int i, c = 0;
struct timeval tv;
for (i = 0; i < base->nactivequeues; ++i) {
	if (TAILQ_FIRST(&base->activequeues[i]) != NULL) {
		base->event_running_priority = i;
		activeq = &base->activequeues[i];
		c = event_process_active_single_queue(base, activeq, INT_MAX, NULL);
		if (c < 0) {
			goto done;
		} else if (c > 0)// 这样设计,相比继续处理后续优先级集合中event_callback好处时,一旦处理中插入了更高优先级的event_callback,便可使其及时得到处理。
			break;
	}
}
done:
base->event_running_priority = -1;
return c;

我们分析event_process_active_single_queue,在这个函数中对特定优先级队列里每个event_callback执行回调处理。
针对每个要处理的event_callback
(1). 若此event_callback依附于event
a. 若依附的eventev_eventsEV_PERSIST或此event_callback服务于event的异步释放
则将此event_callback从激活集合移除。对依附的event无需执行从event_baseio结构移除操作。
b. 其他情况下
则将eventevent_callback分别从event_baseio结构,激活集合移除。
值得注意的是从io结构移除时,可能触发io复用器的del操作来更新套接字的监控事件信息。
(2). 若此event_callback独立使用
将其从激活集合移除。
(3). 若此event_callback是外部的,计数加1
(4). 设置event_basecurrent_event指向此event_callback,设置event_basecurrent_event_waiters0
(5). 在这一步执行回调处理
a. 若此event_callbackevcb_closureEV_CLOSURE_EVENT_PERSIST
依据此前event_callback结构的分析部分,我们可知此event_callback依附于event。且依附的event服务于套接字,是持久的。

evcb_callback = ev->ev_callback;
evcb_fd = ev->ev_fd;
evcb_res = ev->ev_res;
evcb_arg = ev->ev_arg;
EVBASE_RELEASE_LOCK(base, th_base_lock);
(evcb_callback)(evcb_fd, evcb_res, evcb_arg);

我们要做的是释放锁。触发一次其回调处理。
b. 若此event_callbackevcb_closureEV_CLOSURE_EVENT
依据此前event_callback结构的分析部分,我们可知此event_callback依附于event。且依附的event服务于套接字,是非持久的。

void (*evcb_callback)(evutil_socket_t, short, void *);
short res;
evcb_callback = *ev->ev_callback;
res = ev->ev_res;
EVBASE_RELEASE_LOCK(base, th_base_lock);
evcb_callback(ev->ev_fd, res, ev->ev_arg);

同样是释放锁,再触发一次回调处理。
c. 若此event_callbackevcb_closureEV_CLOSURE_CB_SELF
依据此前event_callback结构的分析部分,我们可知此event_callback是独立使用的。

void (*evcb_selfcb)(struct event_callback *, void *) = evcb->evcb_cb_union.evcb_selfcb;
EVBASE_RELEASE_LOCK(base, th_base_lock);
evcb_selfcb(evcb, evcb->evcb_arg);

要做的是释放锁,再触发一次回调处理。

d. 若此event_callbackevcb_closureEV_CLOSURE_EVENT_FINALIZEEV_CLOSURE_EVENT_FINALIZE_FREE
依据此前event_callback结构的分析部分,我们可知此event_callback依附于event,且eventevent_callback已被移除。这个event_callback之所以被激活,是要在激活回调里面让应用层执行event及关联资源异步释放的。

void (*evcb_evfinalize)(struct event *, void *);
int evcb_closure = evcb->evcb_closure;
// 当服务于异步释放的event_callback回调处理时,由于功能性的event_callback此前已被移除。所以,设置current_event为NULL。
base->current_event = NULL;
evcb_evfinalize = ev->ev_evcallback.evcb_cb_union.evcb_evfinalize;
EVBASE_RELEASE_LOCK(base, th_base_lock);
evcb_evfinalize(ev, ev->ev_arg);// 这个回调提供给外部一个时间点告知此关联的event此时可以安全释放了。外部可完成event关联资源释放。
if (evcb_closure == EV_CLOSURE_EVENT_FINALIZE_FREE)
	mm_free(ev);// 内部完成event释放。

要做的是释放锁,再触发一次回调,以便应用层执行异步释放。特别的,evcb_closureEV_CLOSURE_EVENT_FINALIZE_FREE时,我们在事件循环里执行event释放动作。
e. 若此event_callbackevcb_closureEV_CLOSURE_CB_FINALIZE
依据此前event_callback结构的分析部分,我们可知此event_callback独立使用。且此event_callback此前已被移除,此时激活是为了在激活回调里让应用层执行event_callback及其关联资源的异步释放。

void (*evcb_cbfinalize)(struct event_callback *, void *) = evcb->evcb_cb_union.evcb_cbfinalize;
// 当服务于异步释放的event_callback回调处理时,由于功能性的event_callback此前已被移除。所以,设置current_event为NULL。
base->current_event = NULL;
EVBASE_RELEASE_LOCK(base, th_base_lock);
evcb_cbfinalize(evcb, evcb->evcb_arg);

要做的是释放锁,再触发一次回调处理.
(6). 在事件处理后,我们需执行

// 再次获得锁
EVBASE_ACQUIRE_LOCK(base, th_base_lock);
// 设置current_event为NULL。
base->current_event = NULL;
// 若有人阻塞等待上个event_callback回调处理,此时让这些等待者继续
if (base->current_event_waiters) {
	base->current_event_waiters = 0;
	EVTHREAD_COND_BROADCAST(base->current_event_cond);
}
// 及时响应事件循环停止
if (base->event_break)
	return -1;
// 及时响应更高优先级event_callback。一般在更高优先级event_callback放入激活队列时,会设置event_continue为1。
if (base->event_continue)
	break;

(7). 若上一步触发了breakreturn则函数结束。
(8). 判断当前优先级的激活集合,是否还有event_callback
若有,继续开启下一次迭代,以便对其处理。
若无,函数结束。

3.2.10.通知
我们以基于eventfd实现通知机制为例分析event_base的通知机制。
通知机制存在的意义在于,有时我们对event_base做了某些操作使得无需继续阻塞等待io事件,我们可利用通知机制及时将event_base的事件循环从io阻塞唤醒。

为了实现通知机制,event_baseth_notify用于在初始化阶段作为内部event向自身注册,作为持久事件,监控可读事件。
为了达到通知的效果,我们在需要触发通知时,向eventfd描述符写入8字节数据。这样将使其产生可读事件,从而结束io事件阻塞等待。在依附此eventevent_callback的回调处理中我们只需读出一个8字节即可。

3.2.11.中止
借助event_baseevent_breakevent_gotterm及通知机制,我们很容易实现中止事件循环执行操作。

3.2.12.释放
我们分析如何释放event_base
要释放event_base我们应该在释放前保证此event_base的事件循环已被停止以便安全释放。

int i;
size_t n_deleted=0;
struct event *ev;
// 从event_base的io结构中移除用于通知机制的event
if (base->th_notify_fd[0] != -1) {
	event_del(&base->th_notify);
	// 关闭套接字
	EVUTIL_CLOSESOCKET(base->th_notify_fd[0]);
	if (base->th_notify_fd[1] != -1)
		EVUTIL_CLOSESOCKET(base->th_notify_fd[1]);
	base->th_notify_fd[0] = -1;
	base->th_notify_fd[1] = -1;
}
// 我们需遍历每个添加到event_base的外部event,将其从io结构移除
evmap_delete_all_(base);
// 此前停止event_base事件循环时
// 可能尚有遗留的用于异步释放的event_callback对象还存在于event_base的激活或延迟激活集合里
// 这里要做的就是对event_base中服务于异步释放的每个event_callback,将其移除,并触发异步释放回调。
for (;;) {
	int i = event_base_free_queues_(base, run_finalizers);
	event_debug(("%s: %d events freed", __func__, i));
	if (!i) {
		break;
	}
	n_deleted += i;
}
if (n_deleted)
	event_debug(("%s: "EV_SIZE_FMT" events were still set in base", __func__, n_deleted));
// 给使用的io复用器一个做清理的机会
if (base->evsel != NULL && base->evsel->dealloc != NULL)
	base->evsel->dealloc(base);
// 此时,若某个激活队列还有遗留的event_callback不能算错误。但调试模式可以断言提示。
for (i = 0; i < base->nactivequeues; ++i)
	EVUTIL_ASSERT(TAILQ_EMPTY(&base->activequeues[i]));
// 释放
mm_free(base->activequeues);
// io结构资源释放
evmap_io_clear_(&base->io);
// 释放
EVTHREAD_FREE_LOCK(base->th_base_lock, 0);
EVTHREAD_FREE_COND(base->current_event_cond);
// 释放event_base自身
mm_free(base);

4.关于event取消及释放的补充
(1). 若event包含EV_FINALIZE
a. 仅仅取消注册此event
a.1.无阻塞取消
event_del+EVENT_DEL_AUTOBLOCKevent_del+EVENT_DEL_NOBLOCK均可以。
使用时需要知道,此时event_del返回时,若当前event_base事件循环回调处理恰好是移除eventevent_callback的回调处理,此回调处理有可能还在进行中。因此,不应该随后去执行释放event及其关联资源的操作。
a.2.阻塞取消
event_del+EVENT_DEL_BLOCK
此时event_del返回时,eventevent_callback均已经从event_base中移除,且event_callback的回调也不会正在执行。随后执行event及其关联资源释放是安全的。

但此时应注意,若外部在执行event_del前加了某个互斥锁。关联event_callback的回调处理也需获取同一把互斥锁。则对执行event_del的使用者来说,它先取得外部锁,再取得event_base的锁,然后释放event_base的锁,阻塞等待于event_base的条件变量上.对回调处理来说,回调处理需获取外部锁,但此时外部锁无法获取,故而事件循环卡住了.这样,外部使用者也无法被event_base唤醒.死锁产生了.

推荐采用异步释放。异步释放可通过专门的接口event_finalize,此接口实现中会先无阻塞取消event,再激活一个异步回调处理。在异步回调处理中执行event及其关联资源释放即可。

b.释放event
b.1.异步释放
event_finalize,此接口实现中会先无阻塞取消event,再激活一个异步回调处理。在异步回调处理中执行event及其关联资源释放即可。
b.2.同步释放
先执行event_del+EVENT_DEL_BLOCK,再执行event及其关联资源释放。
但此种方式使用不当存在死锁风险。

(2). 若event不包含EV_FINALIZE
a. 仅仅取消注册此event
a.1.无阻塞取消
event_del+EVENT_DEL_NOBLOCK
使用时需要知道,此时event_del返回时,若当前event_base事件循环回调处理恰好是移除eventevent_callback的回调处理,此回调处理有可能还在进行中。因此,不应该随后去执行释放event及其关联资源的操作。
a.2.阻塞取消
event_del+EVENT_DEL_BLOCKevent_del+EVENT_DEL_AUTOBLOCK
此时event_del返回时,eventevent_callback均已经从event_base中移除,且event_callback的回调也不会正在执行。随后执行event及其关联资源释放是安全的。

但此时应注意,若外部在执行event_del前加了某个互斥锁。event_callback的回调处理也获取同一把互斥锁。则存在死锁风险。参考上述分析.
推荐采用异步释放。异步释放可通过专门的接口event_finalize,此接口实现中会先无阻塞取消event,再激活一个异步回调处理。在异步回调处理中执行event及其关联资源释放即可。

b.释放event
b.1.异步释放
event_finalize,此接口实现中会先无阻塞取消event,再激活一个异步回调处理。在异步回调处理中执行event及其关联资源释放即可。
b.2.同步释放
先执行event_del+EVENT_DEL_BLOCKevent_del+EVENT_DEL_AUTOBLOCK,再执行event及其关联资源释放。但此种方式使用不当存在死锁风险。

  • 29
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
event_base_new_with_threadpool是libevent库中的一个功能,它可以创建一个带有线程池的事件循环。 使用event_base_new_with_threadpool,可以在事件循环中创建一个线程池,用于处理事件的回调函数。这样可以避免在事件循环中阻塞,并且可以提高事件处理的并发性。 下面是一个使用event_base_new_with_threadpool的示例代码: ``` #include <event2/event.h> void callback_function(evutil_socket_t fd, short events, void* arg) { // 处理事件的回调函数 } int main() { // 创建线程池 struct event_config* cfg = event_config_new(); event_config_set_num_threads(cfg, 4); struct event_base* base = event_base_new_with_config(cfg); event_config_free(cfg); // 创建事件 struct event* ev = event_new(base, fd, EV_READ, callback_function, NULL); // 添加事件到事件循环中 event_add(ev, NULL); // 进入事件循环 event_base_dispatch(base); // 释放资源 event_free(ev); event_base_free(base); return 0; } ``` 在上面的示例代码中,我们先使用event_config_new创建一个线程池配置对象,然后使用event_config_set_num_threads设置线程池中的线程数量。接下来,我们使用event_base_new_with_config创建一个带有线程池的事件循环。然后创建一个事件,并使用event_add将事件添加到事件循环中。最后,我们使用event_base_dispatch进入事件循环,处理事件的回调函数将在线程池中被执行。在事件循环结束后,我们需要释放资源,包括事件和事件循环对象。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

raindayinrain

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值