Libevent实例:


这段代码描述了Libevent库的主要逻辑:

  1. 调用event_init函数创建event_base对象。一个event_base相当于一个Reator实例。

  2. 创建具体的事件处理器,并设置它们所从属的Reactor实例。evsignal_new和evtimer_new分别用于创建信号事件和定时事件处理。#define evtimer_new(b, cb, arg) event_new((b), -1, 0, (cb), (arg))#define evsignal_new(b, x, cb, arg) \ event_new((b), (x), EV_SIGNAL|EV_PERSIST, (cb), (arg))是定义在include/event2/event.h文件中的宏,是对event_new函数的调用(该函数用于创建通用事件处理器)。

    base参数指定新创建的事件处理器从属的Reactor。fd参数指定与该事件处理器关联的句柄。创建I/O事件处理器时,应该给fd参数传递文件描述符值;创建信号事件处理器时,应该给fd参数传递信号值;创建定时事件处理器时,则应该给fd参数传递-1。cb参数指定目标事件对应的回调函数,arg参数则是Reactor传递给回调函数的参数。event_new函数成功时返回一个event类型的对象,也就是Libevent的事件处理器。

  3. 调用event_add函数,将事件处理器添加到注册事件队列中,并将该事件处理器对应的事件添加到事件多路分发器中。

  4. 调用event_base_dispatch函数来执行事件循环。

事件处理器的数据结构event结构体

event结构体封装了句柄、事件类型、回调函数,以及其他必要的标志和数据。该结构体在include/event2/event_struct.h文件中定义。在include/event2/event_struct.h文件中注释中说明该event结构体是由文件event.h使用,直接引入工程将会有损移植性。


ev_base:该事件处理器从属的event_base实例

ev_events:由于event结构体是事件处理器的数据结构,且Libevent支持的事件类型是使用该结构统一管理,所以ev_events代表注册的事件类型,其取值可以是下图中的标志的按位或(互斥的事件类型除外,比如读写事件和信号事件就不能同时被设置)。EV_TIMEOUT是定时事件,EV_READ是可读事件,EV_WRITE是可写事件,EV_SIGNAL是信号事件、EV_PERSIST是永久事件(事件被触发后,自动重新对这个event调用event_add函数)、EV_ET是边沿触发事件(需要I/O复用系统调用支持,比如epoll)。
ev_res:它记录当前激活事件的类型
ev_fd:对于I/O事件处理器,它是文件描述符;对于信号事件处理器,他是信号值。
ev_callback:它是事件处理器的回调函数,由event_base调用。回调函数被调用时,它的3个参数分别被传入事件处理器的如下3个成员:ev_fd、ev_res和ev_arg。
ev_arg:回调函数的参数
ev_closure:它指定event_base执行事件处理器的回调函数时的行为。其可选值定义于event-internal.h文件中。EV_CLOSURE_NONE是默认行为,EV_CLOSURE_SIGNAL是指执行信号事件处理器的回调函数时,调用ev.ev_signal.ev_ncalls次该回调函数。EV_CLOSURE_PERSIST是指执行完回调函数后再次将事件处理器加入注册事件队列中。

宏TAILQ_ENTRY是尾队列中的节点类型,它定义在compat/sys/queue.h文件中。可以看出其节点结构体中指针所指向的类型可以定制为参数type的类型。这是宏函数的一种使用方法。可以看出该节点中有一个指向前一个节点和后一个节点的指针。

ev_next:所有已经注册的事件处理器(包括I/O事件处理器和信号事件处理器)通过该成员串联成一个尾队列,称为注册事件队列
ev_active_next:所有被激活的事件处理器通过该成员串联成一个尾队列,称为活动事件队列。活动事件队列不止一个,不同优先级的事件处理器被激活后将被插入不同的活动事件队列中。
ev_pri:它指定事件处理器优先级,值越小则优先级越高。

ev_flags:他是一些事件标志。其可选值定义在include/event2/event_struct.h文件中,EVLIST_TIMEOUT指明事件处理器从属于通用定时器队列或时间堆,EVLIST_INSERTED指明事件处理器从属于注册事件队列,EVLIST_SIGNAL没有使用,EVLIST_ACTIVE指明事件处理器从属于活动事件队列,EVLIST_INTERNAL指明其是内部使用,EVLIST_INIT指明事件处理器已经被初始化,EVLIST_ALL指明定义所以标志。

_ev:这是一个由ev_io和ev_signal组成的联合体。所有具有相同文件描述符值得I/O事件处理器通过ev.ev_io.ev_io_next成员串联成一个尾队列,称为I/O事件队列;所有具有相同信号值的信号事件处理器通过ev.ev_signal.ev_signal_next成员串联成一个尾队列,我们称之为信号事件队列。ev.ev_signal.ev_ncalls成员指定信号事件发生时,Reactor需要执行多少次该事件对应的事件处理器中的回调函数。ev.ev_signal.ev_pncalls指针成员要么是NULL,要么指向ev.ev_signal.ev_ncalls。Libevent支持针对同一个socket文件描述符上的可读/可写事件创建多个事件处理器(拥有不同的回调函数),它使用I/O事件队列将具有相同文件描述符值得事件处理器组织在一起。这样,当一个文件描述符上有事件发生时,事件多路分发器就能很快地把所有相关得事件处理器添加到活动事件队列中。信号事件队列得存在也是出于相同得原因。

ev_timeout_pos:这是一个指定是使用通用定时器队列还是时间堆的联合体,它仅用于定时事件处理器。老版本的Libevent中,定时器都是由时间堆来管理的。但开发者认为有时候使用简单的链表来管理定时器将具有更高的效率。因此,新版本的就引入了称为通用定时器队列的尾队列。对于通用定时器而言,ev_timeout_pos联合体的ev_next_with_common_timeout成员指出了该定时器在通用定时器队列中的位置。对于其他定时器,ev_time_pos的min_heap_idx成员指出该定时器在时间堆中的位置。
ev_timeout:它仅对定时器有效,指定定时器的超时值。一个定时器是否是通用定时器取决于其超时值大小。

往注册事件队列中添加事件处理器

创建一个event对象的函数是event_new及其变体,它在event.c文件中实现。event对象创建好之后,应用程序需要调用event_add函数将其添加到注册事件队列中,并将对应的事件注册到事件多路分发器上。event_add函数主要调用另外一个内部函数event_add_internal函数。

/* Implementation function to add an event.  Works just like event_add,* except: 1) it requires that we have the lock.  2) if tv_is_absolute is set,* we treat tv as an absolute time, not as an interval to add to the current* time */
static inline int
event_add_internal(struct event *ev, const struct timeval *tv,int tv_is_absolute)
{struct event_base *base = ev->ev_base; //取出该事件处理器从属的event_base实例int res = 0;int notify = 0;EVENT_BASE_ASSERT_LOCKED(base);_event_debug_assert_is_setup(ev);event_debug(("event_add: event: %p (fd %d), %s%s%scall %p",ev,(int)ev->ev_fd,ev->ev_events & EV_READ ? "EV_READ " : " ",ev->ev_events & EV_WRITE ? "EV_WRITE " : " ",tv ? "EV_TIMEOUT " : " ",ev->ev_callback));EVUTIL_ASSERT(!(ev->ev_flags & ~EVLIST_ALL));/** prepare for timeout insertion further below, if we get a* failure on any step, we should not change any state.*///如果新添加的事件处理器是定时器,且它尚未被添加到通用定时器队列或事件堆中,则为该定时器在时间堆上预留一个位置if (tv != NULL && !(ev->ev_flags & EVLIST_TIMEOUT)) {if (min_heap_reserve(&base->timeheap,1 + min_heap_size(&base->timeheap)) == -1)return (-1);  /* ENOMEM == errno */}/* If the main thread is currently executing a signal event's* callback, and we are not the main thread, then we want to wait* until the callback is done before we mess with the event, or else* we can race on ev_ncalls and ev_pncalls below. *///如果当前调用者不是主线程(执行事件循环的线程),并且被添加的事件处理器是信号事件处理器,而且主线程正在执行该信号事件处理器的回调函数,则当前调用者必须等待主线程完成调用,否则将引起惊态条件(考虑event结构体额ev_ncalls和ev_pncalls成员)
#ifndef _EVENT_DISABLE_THREAD_SUPPORTif (base->current_event == ev && (ev->ev_events & EV_SIGNAL)&& !EVBASE_IN_THREAD(base)) {++base->current_event_waiters;EVTHREAD_COND_WAIT(base->current_event_cond, base->th_base_lock);}
#endifif ((ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL)) &&!(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE))) {if (ev->ev_events & (EV_READ|EV_WRITE))// 添加I/O事件和I/O事件处理器的映射关系res = evmap_io_add(base, ev->ev_fd, ev);else if (ev->ev_events & EV_SIGNAL)// 添加信号事件和信号事件处理器的映射关系res = evmap_signal_add(base, (int)ev->ev_fd, ev);if (res != -1)// 将事件处理器插入注册事件队列event_queue_insert(base, ev, EVLIST_INSERTED);if (res == 1) {/* evmap says we need to notify the main thread. */// 事件多路分发器中添加了新的事件,所以要通知主线程notify = 1;res = 0;}}/** we should change the timeout state only if the previous event* addition succeeded.*/// 下面将事件处理器添加至通用定时器队列或时间堆中。// 对于信号事件处理器和I/O事件处理器,根据evmap_*_add函数的结果决定是否添加(为了给事件设置超时)// 对于定时器,则始终应该添加if (res != -1 && tv != NULL) {struct timeval now;int common_timeout;/** for persistent timeout events, we remember the* timeout value and re-add the event.** If tv_is_absolute, this was already set.*/// 对于永久性事件处理器,如果其超时时间不是绝对时间,则将该事件处理器的超时时间记录在变量ev->ev_io_timeout中。if (ev->ev_closure == EV_CLOSURE_PERSIST && !tv_is_absolute)ev->ev_io_timeout = *tv;/** we already reserved memory above for the case where we* are not replacing an existing timeout.*/// 如果该事件处理器已经被插入通用定时器队列或时间堆中,则先删除它if (ev->ev_flags & EVLIST_TIMEOUT) {/* XXX I believe this is needless. */if (min_heap_elt_is_top(ev))notify = 1;event_queue_remove(base, ev, EVLIST_TIMEOUT);}/* Check if it is active due to a timeout.  Rescheduling* this timeout before the callback can be executed* removes it from the active list. */// 如果待添加的事件处理器已经被激活,且原因是超时,则从活动事件队列中删除它,以避免其回调函数被执行。// 对于信号事件处理器,必要时还需将其ncalls成员设置为0(注意,ev_pncalls如果不为NULL,它指向ncalls)。// 前面提到,信号事件被触发时,ncalls指定其回调函数被执行的次数。将ncalls设置为0,可以干净地终止信号事件的处理if ((ev->ev_flags & EVLIST_ACTIVE) &&(ev->ev_res & EV_TIMEOUT)) {if (ev->ev_events & EV_SIGNAL) {/* See if we are just active executing* this event in a loop*/if (ev->ev_ncalls && ev->ev_pncalls) {/* Abort loop */*ev->ev_pncalls = 0;}}event_queue_remove(base, ev, EVLIST_ACTIVE);}gettime(base, &now);common_timeout = is_common_timeout(tv, base);if (tv_is_absolute) {ev->ev_timeout = *tv;// 判断应该将定时器插入通用定时器队列,还是插入时间堆  } else if (common_timeout) {struct timeval tmp = *tv;tmp.tv_usec &= MICROSECONDS_MASK;evutil_timeradd(&now, &tmp, &ev->ev_timeout);ev->ev_timeout.tv_usec |=(tv->tv_usec & ~MICROSECONDS_MASK);} else {// 加上当前系统时间,以取得定时器超时的绝对时间evutil_timeradd(&now, tv, &ev->ev_timeout);}event_debug(("event_add: timeout in %d seconds, call %p",(int)tv->tv_sec, ev->ev_callback));event_queue_insert(base, ev, EVLIST_TIMEOUT);  // 最后插入定时器// 如果被插入的事件处理器是通用定时器队列中的第一个元素,则通过调用common_timeout_schedule函数将其转移到时间堆中// 这样通用定时器链表和时间堆中的定时器就得到了统一的处理if (common_timeout) {struct common_timeout_list *ctl =get_common_timeout_list(base, &ev->ev_timeout);if (ev == TAILQ_FIRST(&ctl->events)) {common_timeout_schedule(ctl, &now, ev);}} else {/* See if the earliest timeout is now earlier than it* was before: if so, we will need to tell the main* thread to wake up earlier than it would* otherwise. */if (min_heap_elt_is_top(ev))notify = 1;}}//如果必要,唤醒主线程/* if we are not in the right thread, we need to wake up the loop */if (res != -1 && notify && EVBASE_NEED_NOTIFY(base))evthread_notify_base(base);_event_debug_note_add(ev);return (res);
}

往事件多路分发器中注册事件

event_queue_insert函数所做的仅仅是将一个事件处理器加入event_base的某个事件队列中。对于新添加的I/O事件处理器和信号事件处理器,还需要让事件多路分发器来监听其对应的事件,同时建立文件描述符、信号值与事件处理器之间的映射关系。通过evmap_io_add和evmap_signal_add两个函数实现,它们由evmap.c文件实现。

/* return -1 on error, 0 on success if nothing changed in the event backend,* and 1 on success if something did. */
int evmap_io_add(struct event_base *base, evutil_socket_t fd, struct event *ev)
{// 获得event_base的后端I/O复用机制实例const struct eventop *evsel = base->evsel;// 获得event_base中文件描述符与I/O事件队列的映射表(哈希表或数组)struct event_io_map *io = &base->io;// fd参数对应的I/O事件队列struct evmap_io *ctx = NULL;int nread, nwrite, retval = 0;short res = 0, old = 0;struct event *old_ev;EVUTIL_ASSERT(fd == ev->ev_fd);if (fd < 0)return 0;#ifndef EVMAP_USE_HT// I/O事件队列数组io.entries中,每个文件描述符占用一项。如果fd大于当前数组的大小,则增加数组的大小(扩大后的数组的容量要大于fd)if (fd >= io->nentries) {if (evmap_make_space(io, fd, sizeof(struct evmap_io *)) == -1)return (-1);}
#endif// 下面这个宏根据EVMAP_USE_HT是否被定义而有不同的实现,但目的都是创建ctx,在映射表io中为fd和ctx添加映射关系GET_IO_SLOT_AND_CTOR(ctx, io, fd, evmap_io, evmap_io_init,evsel->fdinfo_len);nread = ctx->nread;nwrite = ctx->nwrite;if (nread)old |= EV_READ;if (nwrite)old |= EV_WRITE;if (ev->ev_events & EV_READ) {if (++nread == 1)res |= EV_READ;}if (ev->ev_events & EV_WRITE) {if (++nwrite == 1)res |= EV_WRITE;}if (EVUTIL_UNLIKELY(nread > 0xffff || nwrite > 0xffff)) {event_warnx("Too many events reading or writing on fd %d",(int)fd);return -1;}if (EVENT_DEBUG_MODE_IS_ON() &&(old_ev = TAILQ_FIRST(&ctx->events)) &&(old_ev->ev_events&EV_ET) != (ev->ev_events&EV_ET)) {event_warnx("Tried to mix edge-triggered and non-edge-triggered"" events on fd %d", (int)fd);return -1;}if (res) {void *extra = ((char*)ctx) + sizeof(struct evmap_io);/* XXX(niels): we cannot mix edge-triggered and* level-triggered, we should probably assert on* this. */// 往事件多路分发器中注册事件。add是事件多路分发器的接口函数之一。对不同的后端I/O复用机制,这些接口函数有不同的实现。if (evsel->add(base, ev->ev_fd,old, (ev->ev_events & EV_ET) | res, extra) == -1)return (-1);retval = 1;}ctx->nread = (ev_uint16_t) nread;ctx->nwrite = (ev_uint16_t) nwrite;// 将ev插入I/O事件队列ctx的尾部。ev_io_next是定义在event-internal.h文件中的宏:// #define ev_io_next _ev.ev_io.ev_io_nextTAILQ_INSERT_TAIL(&ctx->events, ev, ev_io_next);return (retval);
}

eventop结构体
eventop结构体封装了I/O复用机制必要的一些操作,比如注册事件、等待事件等。它为event_base支持的所有后端I/O复用机制提供了一个统一的接口。该结构体定义在event-internal.h文件中。

const char *name后端I/O复用技术的名称,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 *)等待事件

devpoll.c、kqueue.c、evport.c、select.c、win32select.c、poll.c和epoll.c文件分别针对不同的I/O复用技术实现eventop定义的这套接口。那么,在支持多种I/O复用技术的系统上,Libevent将根据I/O复用技术的优先级来选择。Libevent支持的后端I/O复用技术及它们的优先级在event.c文件中定义。Libevent通过遍历eventops数组来选择其后端I/O复用技术。遍历的顺序是从数组的第一个元素开始,到最后一个元素结束。

Libevent 2.0.20源码分析-event、eventop结构体分析相关推荐

  1. Pushlet 2.0.3 源码分析

    转载地址:http://blog.csdn.net/yxw246/article/details/2418255 Pushlet 2.0.3 源码分析 ----服务器端 1 总体架构 Pushlet从 ...

  2. Android 11.0 Settings源码分析 - 主界面加载

    Android 11.0 Settings源码分析 - 主界面加载 本篇主要记录AndroidR Settings源码主界面加载流程,方便后续工作调试其流程. Settings代码路径: packag ...

  3. Android 8.0系统源码分析--Camera processCaptureResult结果回传源码分析

    相机,从上到下概览一下,真是太大了,上面的APP->Framework->CameraServer->CameraHAL,HAL进程中Pipeline.接各种算法的Node.再往下的 ...

  4. Spark2.4.0 SparkEnv 源码分析

    Spark2.4.0 SparkEnv 源码分析 更多资源 github: https://github.com/opensourceteams/spark-scala-maven-2.4.0 时序图 ...

  5. 菜鸟读jQuery 2.0.3 源码分析系列(1)

    原文链接在这里,作为一个菜鸟,我就一边读一边写 jQuery 2.0.3 源码分析系列 前面看着差不多了,看到下面一条(我是真菜鸟),推荐木有入门或者刚刚JS入门摸不着边的看看,大大们手下留情,想一起 ...

  6. Android 8.0系统源码分析--开篇

    个人分类: Android框架总结Android源码解析android framework 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/sinat ...

  7. photoshop-v.1.0.1源码分析第三篇–FilterInterface.p

    photoshop-v.1.0.1源码分析第三篇–FilterInterface.p 总体预览 一.源码预览 二.语法解释 三.结构预览 四:语句分析 五:思维导图 六:疑留问题 一.源码预览 {Ph ...

  8. Spark2.0.2源码分析——RPC 通信机制(消息处理)

    RPC 是一种远程过程的调用,即两台节点之间的数据传输. 每个组件都有它自己的执行环境,RPC 的执行环境就是 RPCENV,RPCENV 是 Spark 2.x.x 新增加的,用于替代之前版本的 a ...

  9. libevent源码学习-----event操作

    libevent核心结构是event_base和event,接下来主要介绍event结构 /* event的定义的主要部分 */ struct event {/* ... *//* event监听的描 ...

最新文章

  1. Oracle 启动,建表
  2. 公积金贷款不受影响 组合贷款有特殊
  3. python软件代码示例-Python学习示例源码
  4. linux查询关键词上下行_【已解决】Linux下通过关键字模糊查找搜索文件
  5. 普通计算机怎么算根号_混凝土花盆叠加做花园围墙,比普通红砖块好看多了,怎么算都赚到...
  6. 线性及非线性方程组的解法
  7. python习题week3
  8. STM32 - 定时器的设定 - 基础 01 - Timer Base - Prescaler description - Upcounting mode
  9. python对象模型映射_【500 Lines or Less】-【翻译练习】-【chapter 14】-【简单对象模型】-【第一部分】...
  10. Windows 常用的 CMD 命令
  11. SQLServer数据库增、删、改、查简单操作示例
  12. 程序员代码面试指南 IT名企算法与数据结构题目最优解
  13. CAD贱人工具箱插件
  14. 2022年全球市场电动线性气缸总体规模、主要生产商、主要地区、产品和应用细分研究报告
  15. Airtest多点触控测试
  16. 关于CREO图纸导出到CAD后尺寸不对的问题
  17. 一维条形码Code128的编码与生成
  18. 1000个已成功入职的软件测试工程师简历经验总结:软件测试工程师简历项目经验怎么写?(含真实简历模板)
  19. 为什么我从PR里面导出来的视频,在电脑上播放是正常的,微信发给朋友后,形状就变了,扭曲了一样的
  20. linux系统硬盘坏道,linux系统下检测硬盘上的坏道和坏块

热门文章

  1. python神经网络编程 豆瓣,python训练神经网络模型
  2. char wchar_t TCHAR
  3. 图像处理-遥感图像飞机小目标提取
  4. Oracle12c多租户数据库 - PDB数据库的unplug及plug 3
  5. php spl set,php的spl
  6. 11.4.4 UNIX_TIMESTAMP(date)函数
  7. 专题-子项-1-mysqlf-默认隔离级别为什么是可重复读?为什么业务代码建议用读已提交?
  8. Android Binder(C语言版本)
  9. 水资源税取水计量监管系统 取用水户水量在线监测平台 水资源远程实时监控管理系统
  10. 用汇编语言编写8X8LED点阵字符显示程序