实现一个基础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
在机制封装方面并不完善,也许是为了提供最大的灵活性,将一部分库的封装工作委托给了用户来处理。
网络库最核心的部分是其工作线程,我们可以想象,工作线程的主要任务就是通过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_READ
,EV_WRITE
,EV_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_callback
中EVLIST_FINALIZING
标志的分析。
(3). ev_res
,用于在监控的事件实际产生时,这里放置实际产生的事件类型。ev_res
应该是ev_events
的子集。
(4). ev_base
,event
需要注册到 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_next
,event_base
需要将后续得到处理的event_callback
放在链式容器管理。借此evcb_active_next
实现链式容器。
(2). evcb_flags
,标志信息。
我们暂且忽略服务于信号处理,定时机制的event
的event_callback
。
那么,对于服务于套接字事件监控event
的event_callback
,及不依赖于event
的event_callback
,可用标志及其含义为:
a. EVLIST_INIT
我们说过event_callback
可以依附于event
而存在,此时,event
表示要监控的事件,event_callback
表示监控事件产生时如何处理。这种情形下,event_callback
的evcb_flags
包含此标志。当event_callback
不依附于event
而存在时,无此标志。
b. EVLIST_INSERTED
对服务于套接字事件监控event
的event_callback
,当event
加入到event_base
中后,其内的event_callback
的evcb_flags
包含此标志。
c. EVLIST_INTERNAL
有些event
,并非外部用户注册到event_base
的。
而是event_base
自身需要实现某些特性,比如通知机制,自己向自己注册的。对这样的event
,其内的event_callback
的evcb_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
的回调处理又很可能会访问到event
,event_callback
或关联的资源。这时就会引发程序崩溃。
所以,这时安全的选择是:
f.1.1.取消注册过程阻塞等待,待event_base
结束此event_callback
的回调处理后再继续运行。
这样,外部用户在取消注册后,再释放资源就是安全的。
f.1.2.取消注册过程不阻塞等待。
但为了保证安全释放。我们手动分发此event
,将其ev_res
设置为EV_FINALIZE
,表示其被分发的原因是需要释放。为其event_callback
的evcb_flags
添加EVLIST_FINALIZING
。表示此event_callback
服务于后续的资源异步释放。
外部用户此时在取消注册后不执行event
及其关联资源的释放。将释放动作放在一个typedef void (*event_finalize_callback_fn)(struct event *, void *);
类别的回调函数里。将此函数名设置到event_callback
的evcb_evfinalize
中。
这样,通过异步释放资源,既保证了取消关联过程不阻塞,又保证了后续资源的安全释放。
f.2.我们向event_base
注册了某个不依赖于event
的event_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_callback
的evcb_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
有效。参数1
为event_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. features
,io
复用器支持的特性集合。
h. fdinfo_len
,每个套接字执行add
,del
时最后一个参数可传递自定义参数,这个参数尺寸。
具体到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_gotterm
,event_break
,event_continue
用于event_base
事件循环流程控制
(8). running_loop
用于防止对一个event_base
并发执行多个事件循环。
(9). n_deferreds_queued
用于统计一次循环里有多少不依赖event
的event_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
复用器的add
向io
复用器注册即可。
b. 同样的,只需再某个event
移除后导致套接字的某个事件类型关联的event
数量发生从非0
到0
的变化时,再通过io
复用器的del
向io
复用器取消注册即可。
c. 这样也会我们在event_base
运行事件循环过程中,外部从event_base
里快速移除某个event
提供了可能。因为一旦我们将event
从event_base
移除后,即使io
复用器得到了套接字的某个事件,但若对应的event
不存在了,相应的依附于此event
的event_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_break
为1
,再将其及时从阻塞中唤醒。以便及时结束事件循环运行。
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
,只有这样,当我们通过th_notify_fn
向eventfd
写入数据后,event_base
才能从阻塞等待事件中醒来。
3.动作
3.1.io
复用器提供的动作
(1). 初始化
我们考察epoll
作为io
复用器的初始化。
a. 首先需要通过epoll_create
创建epoll
描述符
b. 然后分配一个epollop
实例,并为其epoll_event
数组分配空间。
c. 实例地址会作为io
复用器init
函数返回值存储到event_base
的evbase
中。
/*
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_event是linux下结构,用于向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
操作,返回错误码ENOENT
、EBADF
、EPERM
,一般不认为是出错。可记录日志。按成功返回。
出现上述情况,一般由于使用者对套接字关闭前未取消注册等使用不当造成,我们做相应补救。
(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_callback
的evcb_flags
包含EVLIST_FINALIZING
,这表达的意思是此event
之前已做了取消处理,依附的event_callback
被激活用于异步释放。此时我们应该释放锁。结束处理。
c. 若依附的event_callback
此时位于激活集合或延迟激活集合,则我们将其从集合移除并更新相应字段。
d. 若event
此前被添加到event_base
的io
结构,则将其从io
结构指定描述符所在槽位移除。引发此套接字事件监控变化时,需借助io
复用器的del
实现注册变更。若此后,io
结构中没有外部event
,event_base
中也没活动的event_callback
,应触发一次通知机制,以避免不必要的阻塞等待。
e. 针对要移除的event
依附的event_callback
此时正被回调处理,且移除是外部线程发起的,且event
的ev_events
不含EV_FINALIZE
,默认下我们需要释放锁,阻塞等待此event_callback
的回调执行完毕,再获得锁。继续。前面描述ev_events
标志时说过,只有某个event
希望移除并采用异步方式执行释放时,才会设置EV_FINALIZE
。
f. 释放锁,结束。
3.2.3.手动分发event
对于event
,除了注册到event_base
并等待事件产生时自动分发使得依附其的event_callback
中的回调处理被触发。
我们也可直接针对event
采用手动分发的方式,将依附于其的event_callback
加入激活集合,并在event
的ev_res
中记录手动分发的原因。
过程可简要描述为:
a. 获得event_base
的互斥锁。
b. 若依附其的event_callback
的evcb_flags
含EVLIST_FINALIZING
,则表示此event
已被移除,依附其的event_callback
加入激活集合用于异步释放。此时,应释放锁。结束。
c. 设置event
的ev_res
记录得到分发的原因。
d. 将依附其的event_callback
加入激活集合。
e. 若是外部线程执行的手动分发,则需触发event_base
激活机制,避免阻塞等待io
事件。
f. 释放锁,结束。
3.2.4.手动分发event
到延迟激活集合
类似3.2.3
,只不过此时依附的event_callback
被分发到延迟激活集合。
延迟激活集合中的event_callback
在event_base
事件循环下一轮会转入立即激活集合。
3.2.5.手动分发event_callback
我们前面说过。对套接字事件监控,是event
,其内包含event_callback
的模式。此时,注册,取消注册,自动分发,手动分发操作的对象都是event
,操作内会相应操作依附其的event_callback
。
但我们也允许独立存在的不依附于event
的event_callback
。对于这样的event_callback
对象,若希望在event_base
的事件循环中执行其内的回调处理,我们只能采用手动分发的方式,将event_callback
加入到激活集合来实现。
3.2.6.手动分发event_callback
到延迟激活集合
类似3.2.5
,只不过分发到了延迟激活集合。
3.2.7.取消的event_callback
对于独立的event_callback
或依附于event
的event_callback
,我们可通过取消操作在event_callback
已经位于激活或延迟激活集合时,将其从集合移除,从而使得其回调处理不会被执行。
对依附于event
的event_callback
,操作应该均以event
为对象进行。所以,我们转而去执行event
的取消注册即可。
对独立使用的event_callback
,操作可简要描述为:
a. 获得event_base
的互斥锁。
b. 若event_callback
的evcb_flags
含EVLIST_FINALIZING
,表示此event_callback
之前已被移除,此刻处于激活集合用于异步释放机制。我们应释放锁。结束。
c. 若event_callback
位于激活或延迟激活集合,将其从集合移除。
d. 释放锁。结束。
3.2.8.事件循环
事件循环进入前需设置event_base
的th_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. 若依附的event
的ev_events
含EV_PERSIST
或此event_callback
服务于event
的异步释放
则将此event_callback
从激活集合移除。依附的event
依然存在于event_base
的io
结构中。
b. 其他情况下
则将event
,event_callback
分别从event_base
的io
结构,激活集合移除。
值得注意的是从io
结构移除时,可能触发io
复用器的del
操作来更新套接字的监控事件信息。
(2). 若此event_callback
独立使用
将其从激活集合移除。
(3). 若此event_callback
是外部的,计数加1
。
(4). 设置event_base
的current_event
执行此event_callback
,设置event_base
的current_event_waiters
为0
。
(5). 在这一步执行回调处理
a. 若此event_callback
的evcb_closure
为EV_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_callback
的evcb_closure
为EV_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_callback
的evcb_closure
为EV_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_callback
的evcb_closure
为EV_CLOSURE_EVENT_FINALIZE
或EV_CLOSURE_EVENT_FINALIZE_FREE
。
依据此前event_callback
结构的分析部分,我们可知此event_callback
依附于event
,且event
及event_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;
evcb_evfinalize(ev, ev->ev_arg);// 这个回调提供给外部一个时间点告知此关联的event此时可以安全释放了。外部可完成event关联资源释放。
if (evcb_closure == EV_CLOSURE_EVENT_FINALIZE_FREE)
mm_free(ev);// 内部完成event释放。
触发一次回调,以便应用层执行异步释放。特别的,evcb_closure
为EV_CLOSURE_EVENT_FINALIZE_FREE
时,我们在事件循环里执行event
释放动作。
e. 若此event_callback
的evcb_closure
为EV_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). 若上一步触发了break
,return
则函数结束。
(8). 判断当前优先级的激活集合,是否还有event_callback
。
若有,继续开启下一次迭代,以便对其处理。
若无,函数结束。
3.2.10.通知
我们以基于eventfd
实现通知机制为例分析event_base
的通知机制。
通知机制存在的意义在于,有时我们对event_base
做了某些操作使得无需继续阻塞等待io
事件,我们可利用通知机制及时将event_base
的事件循环从io
阻塞唤醒。
为了实现通知机制,event_base
的th_notify
用于在初始化阶段作为内部event
向自身注册,作为持久事件,监控可读事件。
为了达到通知的效果,我们在需要触发通知时,向eventfd
描述符写入8
字节数据。这样将使其产生可读事件,从而结束io
事件阻塞等待。
在依附此event
的event_callback
的回调处理中我们只需读出一个8
字节即可。
3.2.11.中止
借助event_base
的event_break
或event_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_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_AUTOBLOCK
或event_del
+EVENT_DEL_NOBLOCK
均可以。
使用时需要知道,此时event_del
返回时,若当前event_base
事件循环回调处理恰好是移除event
中event_callback
的回调处理,此回调处理有可能还在进行中。因此,不应该随后去执行释放event
及其关联资源的操作。
a.2.阻塞取消
event_del
+EVENT_DEL_BLOCK
此时event_del
返回时,event
,event_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_BLOCK
,再执行event
及其关联资源释放。
但此种方式使用不当存在死锁风险。
(2). 若event
不包含EV_FINALIZE
a. 仅仅取消注册此event
a.1.无阻塞取消
event_del
+EVENT_DEL_NOBLOCK
。
使用时需要知道,此时event_del
返回时,若当前event_base
事件循环回调处理恰好是移除event
中event_callback
的回调处理,此回调处理有可能还在进行中。因此,不应该随后去执行释放event
及其关联资源的操作。
a.2.阻塞取消
event_del
+EVENT_DEL_BLOCK
或event_del
+EVENT_DEL_AUTOBLOCK
此时event_del
返回时,event
,event_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_BLOCK
或event_del
+EVENT_DEL_AUTOBLOCK
,再执行event
及其关联资源释放。
但此种方式使用不当存在死锁风险。