大型门户网站开发公司,郑州seo技术服务顾问,中山网站建设收费标准,自己搭建个人网站的注意事项1.概述
实现一个基础tcp网络库#xff0c;以基于tcp网络库构建服务端应用#xff0c;客户端应用为起点#xff0c;我们的核心诉求有#xff1a; a. tcp网络库管理工作线程。
b. tcp网络库产生服务端对象#xff0c;通过启动接口#xff0c;开启服务端监听。进一步…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_callback中EVLIST_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标志信息。 我们暂且忽略服务于信号处理定时机制的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的回调处理又很可能会访问到eventevent_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. featuresio复用器支持的特性集合。 h. fdinfo_len每个套接字执行adddel时最后一个参数可传递自定义参数这个参数尺寸。 具体到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 用于统计一次循环里有多少不依赖event的event_callback对象从延迟集合转移到激活集合。 (10). event_running_priority 若event_base的事件循环此时在对立即激活的event_callback执行事件处理。则表示当前处理的event_callback的优先级。 (11). current_event 当event_base的事件循环在执行EV_CLOSURE_EVENT_PERSISTEV_CLOSURE_EVENTEV_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,255if (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_ADDelse:op EPOLL_CTL_MODreturn |.join(events), opif (del in (rc, wc, cc)):delevents []modevents []op EPOLL_CTL_DELif 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_MODelse:return |.join(delevents), EPOLL_CTL_DELreturn 0, 0def 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_ADDEPOLL_CTL_DELEPOLL_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_CLOSEDd. 上述数组起作用的前提是 我们每次对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_READEV_WRITEEV_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结构中没有外部eventevent_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(flagsEVLOOP_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. 其他情况下 则将eventevent_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). 若上一步触发了breakreturn则函数结束。 (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_deleted0;
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_delEVENT_DEL_AUTOBLOCK或event_delEVENT_DEL_NOBLOCK均可以。 使用时需要知道此时event_del返回时若当前event_base事件循环回调处理恰好是移除event中event_callback的回调处理此回调处理有可能还在进行中。因此不应该随后去执行释放event及其关联资源的操作。 a.2.阻塞取消 event_delEVENT_DEL_BLOCK 此时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_delEVENT_DEL_BLOCK再执行event及其关联资源释放。 但此种方式使用不当存在死锁风险。
(2). 若event不包含EV_FINALIZE a. 仅仅取消注册此event a.1.无阻塞取消 event_delEVENT_DEL_NOBLOCK。 使用时需要知道此时event_del返回时若当前event_base事件循环回调处理恰好是移除event中event_callback的回调处理此回调处理有可能还在进行中。因此不应该随后去执行释放event及其关联资源的操作。 a.2.阻塞取消 event_delEVENT_DEL_BLOCK或event_delEVENT_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_delEVENT_DEL_BLOCK或event_delEVENT_DEL_AUTOBLOCK再执行event及其关联资源释放。 但此种方式使用不当存在死锁风险。