libevent核心结构是event_base和event,接下来主要介绍event结构

/* event的定义的主要部分 */
struct event {/* ... *//* event监听的描述符,也可以是信号值 */evutil_socket_t ev_fd;/* 事件驱动主循环 */struct event_base *ev_base;short ev_events;short ev_res;       /* result passed to event callback */short ev_flags;ev_uint8_t ev_pri;  /* smaller numbers are higher priority */union {/* used for io events */struct {TAILQ_ENTRY(event) ev_io_next;struct timeval ev_timeout;} ev_io;/* used by signal events */struct {TAILQ_ENTRY(event) ev_signal_next;short ev_ncalls;/* Allows deletes in callback */short *ev_pncalls;} ev_signal;} _ev;struct timeval ev_timeout;/* allows us to adopt for different types of events */void (*ev_callback)(evutil_socket_t, short, void *arg);void *ev_arg;
};

程序中使用event最开始需要event_new创建一个event

/* 创建事件驱动 */
struct event_base* base = event_base_new();
/**创建一个事件*@param base: 事件驱动*@param fd: event对应的文件描述符,通常是通过socket创建的套接字*@param EV_READ: 想要监听fd的哪些事件,EV_READ表示监听fd是否可读,也可以是EV_PERSIST代表这个event是永久事件,在调用一次回调函数后仍然继续监听,对应一次性event,调用后不再监听*@param cb: 当fd对应的事件发生后调用的回调函数,用户提供*@param arg: 传给回调函数cb的参数*/
struct event* ev = event_new(base, fd, EV_READ|EV_PERSIST, cb, arg);

接下来一个个解释每个变量的作用
evutil_socket_t ev_fd;
event负责监听的描述符,也可以是信号值

struct event_base *ev_base;
事件驱动base

short ev_events;
对应监听fd的某些事件,上述代码中是EV_READ | EV_PERSIST,可用的events包括

  • EV_READ:fd可读
  • EV_WRITE:fd可写
  • EV_PERSIST:永久事件,激活一次后仍然继续监听,对应一次事件,激活一次后不再监听
  • EV_SIGNAL:代表这个event监听的是一个信号
  • EV_TIMEOUT:代表这个event具有超时时长

short ev_res;
当event被激活时,ev_res的值记录着是被哪些事件(上述)激活,即激活的原因

short ev_flags;
event处于的状态,其实是event都在哪几个队列中(base中有多个队列),可以是以下几种的或运算

  • EVLIST_INIT:表示event刚被初始化,不在任何队列中,通常是刚调用完event_new
  • EVLIST_INSERTED:表示event处于base的注册队列中,通常是调用event_add后
  • EVLIST_ACTIVE:表示event处于base的激活队列中,通常是event被激活,等待调用回调函数
  • EVLIST_TIMEOUT:表示event处于最小堆中,表示event具有超时时间
  • EVLIST_ALL:私有空间,不明

ev_uint8_t ev_pri;
event的优先级,base的激活队列是一个数组,每个数组元素是一个队列,数组下标越低优先级越高,在统一处理激活event时,从优先级高的event开始调用回调函数

_ev

    union {/* used for io events */struct {TAILQ_ENTRY(event) ev_io_next;struct timeval ev_timeout;} ev_io;/* used by signal events */struct {TAILQ_ENTRY(event) ev_signal_next;short ev_ncalls;/* Allows deletes in callback */short *ev_pncalls;} ev_signal;} _ev;

主要用于记录用户提供的相对时间,ev_timeout变量

struct timeval ev_timeout;
event超时的绝对时间

void (*ev_callback)(evutil_socket_t, short, void *arg);
用户提供的回调函数,函数指针

void *ev_arg;
传给回调函数的参数


对event的初始化操作主要集中在event_new,event_add上

event_new调用event_assign注册一个event

/** 每次要添加事件都需要先调用event_new函数创建一个event,函数参数指明* 事件所属的驱动base* 事件对应的文件描述符或者信号类型fd* fd对应的事件events, 如EV_READ, EV_WRITE, EV_PERSIST,注意信号是EV_SIGNAL* 当响应事件发生时调用的回调函数cb以及传给cb的参数* * 使用者不需要自己判断什么时候事件发生然后调用事件处理函数,而只需要将关注的这些东西* 传给event,唯一需要的就是自己定义一个回调函数* * 当调用event_add后* event_base会统一管理它接受的所有事件,当某一个事件发生时,取得相应的event,然后调用event中存储* 的回调函数,同时将需要的参数传入。包括fd, 回调函数这些变量都在每一个event中存储着* 这就是Reactor事件驱动* * event_new的内部调用的是event_assign函数,作用是创建一个event并初始化然后返回,* 用户也可以自己调用这个函数* * 注意:这个函数并没有将event注册到base中,那是event_add的任务*/
struct event *
event_new(struct event_base *base, evutil_socket_t fd, short events, void (*cb)(evutil_socket_t, short, void *), void *arg)
{struct event *ev;ev = mm_malloc(sizeof(struct event));if (ev == NULL)return (NULL);if (event_assign(ev, base, fd, events, cb, arg) < 0) {mm_free(ev);return (NULL);}return (ev);
}

event_assign其实就是各种初始化,没什么特别的地方

/* 函数对struct event这个结构体的成员变量赋值 */
int
event_assign(struct event *ev, struct event_base *base, evutil_socket_t fd, short events, void (*callback)(evutil_socket_t, short, void *), void *arg)
{if (!base)base = current_base;/* * 仅仅是将event的base成员变量进行绑定,此时仍然没有将event添加到base中* 如果需要添加,需要手动调用event_add()函数*/ev->ev_base = base;ev->ev_callback = callback;ev->ev_arg = arg;ev->ev_fd = fd;/** ev_events表示的是需要监听的事件,此外有几个值用于区分io和信号,一次和永久* EV_READ/EV_WRITE:读写事件* EV_SIGNAL:表示这个事件是一个信号* EV_PERSIST:表示这个事件是一个永久事件,当处理过一次之后仍然继续监听,否则处理一次后就被删除掉*/ev->ev_events = events;/* ev_res表示event被哪个事件激活 */ev->ev_res = 0;/** ev_flags表示的是这个event目前的状态,其实就是event在base的哪几个队列里/或最小堆* 初始状态EVLIST_INIT:表示刚刚初始化,还没有注册到base中,不在任何队列中* 注册状态EVLIST_INSERTED:表示已经注册到base中,在base的注册队列中* 活跃状态EVLIST_ACTIVE:表示监听的某个事件发生,等待着调用回调函数,在激活队列中* 超时状态EVLIST_TIMEOUT:表示具有超时时间的事件超时,在最小堆中* 内部事件EVLIST_INTERNAL:表示这个event是一个内部event,用于信号的统一*/ev->ev_flags = EVLIST_INIT;/* 被激活的次数,对信号有用,因为一段时间内可能传来多个同样的信号 */ev->ev_ncalls = 0;ev->ev_pncalls = NULL;/** ev_closure* 用于区分信号/io,永久/一次event* 在event_base_active_single_queue中用于判断是否是永久/信号event* 对于永久event且有超时时间,重新计算时间然后调用event_add* 对于永久信号,调用用户的信号处理函数,可能多次调用,如果同一个信号发生多次的话*/if (events & EV_SIGNAL) {if ((events & (EV_READ|EV_WRITE)) != 0) {event_warnx("%s: EV_SIGNAL is not compatible with ""EV_READ or EV_WRITE", __func__);return -1;}ev->ev_closure = EV_CLOSURE_SIGNAL;} else {if (events & EV_PERSIST) {evutil_timerclear(&ev->ev_io_timeout);ev->ev_closure = EV_CLOSURE_PERSIST;} else {ev->ev_closure = EV_CLOSURE_NONE;}}/** 如果事件具有超时时间,那么它将被添加到base的时间最小堆上* 而最小堆其实就是一个struct event*类型的数组,首先需要初始化每个事件* 在最小堆中的索引为-1,表示当前不在最小堆中* 下标用处不明*/min_heap_elem_init(ev);if (base != NULL) {/* by default, we put new events into the middle priority *//** base中有的激活队列是一个队列数组,数组的每一个元素是一个队列* 数组下标代表响应队列的优先级,下标越低,优先级越高* 所有如果某个事件发生了,会将响应的event放入对应优先级的队列里,* event优先级初始化工作在这里,默认优先级时base中激活队列数量的一半* 也就是优先级是中等水平,可以手动修改*/ev->ev_pri = base->nactivequeues / 2;}return 0;
}

event_add调用event_add_internal函数

/** 这个函数用来将event添加到base中,也就是添加到base的注册队列中* 函数内部通过调用event_add_internal()来完成工作,不过再次之前需要为base加锁,* 因为需要更改base中的队列,而其他线程此时可能正在访问队列,这就造成了race condition* 所以需要锁来保护*/
int
event_add(struct event *ev, const struct timeval *tv)
{int res;if (EVUTIL_FAILURE_CHECK(!ev->ev_base)) {event_warnx("%s: event has no event_base set.", __func__);return -1;}EVBASE_ACQUIRE_LOCK(ev->ev_base, th_base_lock);res = event_add_internal(ev, tv, 0);EVBASE_RELEASE_LOCK(ev->ev_base, th_base_lock);return (res);
}

event_add_internal比较重要,它根据event的不同类型添加到不同的base队列中

/** event_add调用的内部函数,用于将event添加到base的注册队列中* 同时添加到相应的map中* * 注意:这个函数不仅仅只由event_add调用,还有event_persist_closure调用* 由这个函数调用是因为当具有超时时间的event被激活后,需要先从base中的所有队列中删除* 然后重新计算超时时间,再重新添加到base中,所以又重新调用了这个函数* * 注意:event不仅代表文件描述符,还有可能是信号的event,当是信号时,会递归* 调用两遍这个函数,第一遍调用时判断是信号则调用evsig_map_add函数,在这个函数中* 进行两步*   将信号event添加到base的信号map中*   调用evsigops的add函数,即调用evsig_add,这个函数中绑定内部信号处理函数,同时将socketpair的event*   添加到base中,使用event_add,也就是调用event_add_internal* 不过只会执行两遍,因为在evsig_add中会进行判断,只有第一次添加socketpair的event时才会执行第二次调用* * 见evsig_add*/
static inline int
event_add_internal(struct event *ev, const struct timeval *tv,int tv_is_absolute)
{struct event_base *base = ev->ev_base;int res = 0;int notify = 0;/** 这一步主要是用来让最小堆增加一个位置,并没有实际添加到最小堆上* 判断条件是这是一个具有超时时间的event,同时在最小堆中没有这个event* 这样就需要在最小堆上留出一个位置来存放这个event* 因为用户可以对同一个event调用event_add多次,这就可能两次event_add除了超时时间不同* 其他的都相同,这样就不需要在留出一个位置,直接替换以前的就可以* * 如果已经在最小堆中,ev_flags将是EVLIST_TIMEOUT*/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 */}/** 这一步就是根据io还是signal添加到base的不同map中,然后加入到base的队列中,* 注意event_queue_insert的最后一个参数* 这个函数可以根据参数的不同选择添加不同的队列中,同时为这个event的flags添加* 不同的状态,此时为EVLIST_INSERTED会另event->flags |= EVLIST_INSERTED,同时添加到注册队列上* 表示这个event处于base的注册队列中** 此时仍然没有考虑具有超时时间的event,所以这种event也同样会进入这个语句* 添加到不同的map中,然后添加到base的注册队列中* 在下面还会单独为具有超时时间的event调用一次,那时为EVLIST_TIMEOUT*/if ((ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL)) &&!(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE))) {/** 注意只有文件描述符的event会添加到io函数中,信号event不添加,它不需要监听* 有信号发生会由内核通知进程*/if (ev->ev_events & (EV_READ|EV_WRITE))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);/** 注意如果是信号,在两层递归调用时会将* 信号event,socketpair读端event都添加到signal map和base注册队列中*/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.*//* 这一步开始处理具有超时时间的event */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.*//** event分为永久的和一次的,是用户在调用event_new时传入的参数* 对于永久的event,在被激活一次之后还需要继续监听,* 而对于有超时时间的event,需要对event的超时时间进行更新* * 为什么:因为base在进行超时判断时是通过绝对时间进行判断的,也就是说在添加event的时候* 将当前时间+时间间隔获得的绝对时间作为判断超时的依据* 这样做的原因是不需要在判断超时时比较时间差,只需要比较当前时间和超时时间即可* * 所以,如果event是永久的,那么再处理过一次之后需要更新超时绝对时间,方法就是保存用户* 传入的时间间隔,再下一次添加时使用** tv_is_absolute是传入的参数,event_add传入时设为0,表示传入的时间是时间间隔,不是绝对时间*/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.*//** 对于用户对同一个event调用event_add多次的情况,先将以前的从最小堆* 中删除,再添加更新的这个*/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. *//* * 如果此时event正处于激活队列中,从激活队列删了 * 如果是信号,将其发生次数设为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_queue_insert()将具有超时时间的event添加到base最小堆中 */event_queue_insert(base, ev, EVLIST_TIMEOUT);if (min_heap_elt_is_top(ev))notify = 1;}return (res);
}

总结
event是整个libevent的核心,通过指针保存回调函数,这样当fd被激活,就可以由libevent调用这个回调函数而无需用户关系什么时候调用。libevent内部函数指针的使用特别频繁,主要就集中在这里
对于初始化工作,其实就是程序用到什么就初始化什么,比较无脑,但是具体的细节还是值得细看的
event_add_internal这个函数尤为重要,它不仅由event_add函数调用,对于超时event仍然需要重复调用这个函数,所以在对超时event的管理上libevent的做法还是很值得学习的,不过在设计过程中要做到思路清晰真的不容易,可以细细研究大神之作

libevent源码学习-----event操作相关推荐

  1. Libevent源码学习笔记一:event2/event.h

    一.libevent标准使用方法: 每个程序使用Libevent必须include <event2/event.h> 头文件,并 传给 -levent  链接器.如果只是想使用主要的eve ...

  2. libevent源码学习-----阅读心得

    框架设计思路 libevent使用统一事件源将所有问题都转化为event,比如将套接字/信号/描述符都在内部转化为event,由相应的io多路复用函数进行监控. 为了提供对超时event的支持,lib ...

  3. libevent源码学习-----时间管理

    libevent监听的event有以下几种 文件描述符/套接字,没有设定超时时长 信号 文件描述符/套接字,设定超时时长 对于时间,libevent内部的时间管理是通过最小堆实现的,原因如下 既然某些 ...

  4. libevent源码学习-----事件驱动流程分析

    libevent中事件驱动的大体流程如下 /* 创建事件驱动 */ struct event_base* base = event_base_new(); /**创建一个事件*@param base: ...

  5. Libevent 源码学习笔记(1)event 与 event_base

    目录 event event_base eventop evcb_closure event_callback event_changelist evsig_info event_io_map eve ...

  6. libevent源码学习-----event_base事件循环

    event_base是libevent的事件驱动,也是Reactor模式的直接体现.任何使用libevent的代码最开始都需要创建一个base,之后的任何接口函数都和这个base关联着,下面是stru ...

  7. libevent源码学习----io多路复用的封装和使用

    因为是非阻塞监听事件的发生,所以内部其实还是采用io多路复用函数实现的. 又因为可供选择的io函数很多,linux下有epoll, poll, select等,window下有ICOP, select ...

  8. libevent源码学习-----统一事件源及信号绑定函数

    libevent在对文件描述符,套接字进行监控时直接放到event,这些event通过io多路复用函数进行监控,然而对应信号来说io复用函数却无能为力,为了解决问题,libevent采用统一事件源的方 ...

  9. libevent源码学习-----Reactor模型

    libevent内部采用了reactor模型 所谓reactor模型,其实就是一套事件注册机制,用来解决单线程的阻塞问题.reactor核心思想是将事件和相应事件发生时想要调用的函数都记录下来,在事件 ...

最新文章

  1. 干货丨数据科学、机器学习、人工智能,究竟有什么区别?
  2. Oracle宣称Java将每半年发布一个版本
  3. Openlayers中使用Cluster实现点位元素重合时动态聚合与取消聚合
  4. VTK:图像收缩用法实战
  5. webstorm中自动插入的代码和ts冲突的解决办法
  6. IEnumerable和IQueryable的区别以及背后的ExpressionTree表达式树
  7. Dataphin功能:集成——如何将业务系统的数据抽取汇聚到数据中台
  8. android gif转jpg格式文件,android使用多张图片合成gif文件
  9. poco c++ library 特性
  10. python接口自动化(二十七)--html 测试报告——上(详解)
  11. shell脚本实现printf数字转换N位补零
  12. Java MD5 加密工具类
  13. 遗传算法优化模糊控制规则
  14. 关于Windows下使用CuteFTP向Ubuntu传文件时提示“请求被拒绝”
  15. 传统企业数字化营销转型必经之路
  16. 手机问卷调查报告 html,关于手机的问卷调查调研报告.doc
  17. java中修改上传图片大小,springMVC MultipartFile 上传图片时修改图片大小
  18. Windows Server2012搭建邮件服务器
  19. 【行业专题报告】酒类(白酒、啤酒)-专题资料
  20. 【opencv14】cv::Mat---Desne数组类

热门文章

  1. python调用zabbixapi接口_python3 + zabbix api 的使用
  2. python如何离线安装第三方模块_扣丁学堂python开发之第三方模块pip离线安装
  3. Java黑皮书课后题第6章:6.1(数学:五角数)一个五角数被定义为n*(3*n-1)/2,其中n=1、2…。所以开始的几个数字就是1、5、12、22…,编写具有以下方法头的方法,返回一个五角数
  4. 浙江绿盟科技2011.10.14校园招聘会笔试题
  5. python-基础day10
  6. [BZOJ 2200][Usaco2011 Jan]道路和航线 spfa+SLF优化
  7. 对于理想的团队模式的设想和对软件流程的理解
  8. UVALive 3942 Remember the Word(字典树+DP)
  9. 【openlayers】修改源码支持SLD的graphicfill属性
  10. MVP:界面与业务逻辑分离在Winform中的应用