这篇文章将分析libevent如何组织io事件,如何捕捉事件的发生并进行相应的响应。这里不会详细分析event与event_base的细节,仅描述io事件如何存储与如何响应。

1.  select

libevent实现io事件的backend实际上使用的是io复用接口,如select, poll, epoll等,这里以最简单的select为例进行说明。首先简单介绍一下select接口:

int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

readfds, writefds, exceptfds均是文件描述符集合指针,调用该函数后,若readfds集合中的fd可读,或者writefds集合中的fd可写,或者exceptfds集合中的fd发生错误,或者阻塞的时间达到了timeout,函数返回。

函数返回0,返回结果readfds集合中包含了入参readfds中现在读不会被阻塞的fd,返回结果writefds包含了对应的写不会被阻塞的fd,exceptfds包含了所有发生异常的fd。如果超时,函数返回-1,这些集合为空。

可以看到,select调用需要传入感兴趣的io的文件描述符fd,而libevent中大家熟悉的是event_base,event结构体,event添加到event_base后就可以等待事件触发了,libevent是如何关联event, event_base与select的呢?

事实上,在libevent的源码select.c的,定义了一个数据结构struct selectop,

struct selectop {
int event_fds; /* Highest fd in fd set */
int event_fdsz;
int resize_out_sets;
fd_set *event_readset_in;
fd_set *event_writeset_in;
fd_set *event_readset_out;
fd_set *event_writeset_out;
};

它保存了libevent每次调用select需要使用到的入参与出参,这里保存的文件描述符是与加入到event_base中的event对应的。每当一个新的io的event加入到event_base, 内部都会将event对应的文件描述符fd加入到event_readset_in与event_writeset_in中,这取决于event感兴趣的是读事件还是写事件。相应的函数调用栈是:

event_add->event_add_nolock_->evmap_io_add_->select_add

当libevent使用select作为io的backend时,event_base中的成员evbase即指向动态分配的结构selectop,那么所有感兴趣的io事件的文件描述符都已经保存在了属于event_base的selectop结构中,调用select时即可使用。

2.  event_io_map

前面分析了select使用的入参如何保存,但除了事件对应的文件描述符,libevent同样需要保存结构event,因为event中还记录了许多其它的信息,如事件发生时调用的回调函数,事件的超时时间等。因此,加入到event_base中的event结构也需要保存,event_base中的成员struct event_io_map io的作用正是用来保存添加的io事件的event结构。

struct event_io_map是一个hash表,如果是在非windows环境下,这个hash表可以简单地以一个动态数组实现。为描述简单,这里假设是在非windows环境下。其定义如下:

struct event_signal_map {
/* An array of evmap_io * or of evmap_signal *; empty entries are
* set to NULL. */
void **entries;
/* The number of entries available in entries */
int nentries;
};

它包含一个动态数组entries,数组长度以nentries表示。动态数组的内容是指向动态分配的结构evmap_io的指针。event对应的文件描述符fd作为它在动态数组中的索引,而同一个fd可能有多个感兴趣的事件加入到同一个event_base中,因此将它们连接起来构成双向链表以解决冲突,这个双向链表就是struct evmap_io,即以event的fd作为索引即可以找到这些事件组成的双向链表。event结构中的成员ev_io包含两个指针即双向链表的前驱指针与后继指针。event_base中的成员io的存储结构可以用图2-1表示,

图2-1 event_io_map

3.  event_base_loop

包含回调信息的event结构已经存入了event_base的io成员,对于select,event相应的文件描述符也已经保存在了event_base的evbase成员指向的struct selectop结构中。那么libevent最终是如何等待读写事件的发生并最终调用相应的回调函数的呢?答案是event_base_loop函数。

首先,针对io事件,event_base_loop每一次循环,都会调用后端的dispatch函数,针对select后端,这个dispatch函数是select_dispatch,而select_dispatch又会调用select函数。然后,当select返回结果后,select_dispatch根据文件描述符从event_base的hash表io中将触发的event的回调函数加入到event_base的待执行回调函数链表,这个待执行回调函数链表由event_base的成员struct evcallback_list *activequeues保存。最后,event_base_loop在dispatch之后即会执行链表上的回调函数,完成事件响应。

4.  activequeues

activequeues是一个evcallback_list类型的动态数组,用来实现事件的优先级,数组每一个成员都是一个待执行回调函数的链表。event_base_loop中执行这些链表上的函数时,以索引0开始按递增的顺序扫描数组,若数组成员指向的链表不为空,则依次执行上面的回调函数,索引越小,对应链表上的回调函数越先被执行,构成了事件的优先级。activequeues的结构可使用图4-1表示,

图4-1 event_base的activequeues成员结构

libevent处理io事件的流程简化总结为:首先将io事件的event结构加入到event_base的io成员中,并将对应的fd保存到evbase指向的结构中;然后event_base_loop调用dispatch,将触发的事件的回调函数添加到activequeues多优先级链表上;最后event_base_loop中执行activequeues上的回调函数,完成事件响应。

5.  部分概念说明表

event_base

libevent中的基本结构,所有的event添加到该结构的实例中,再调用event_base_loop处理其中的事件

event

libevent中表达一个事件的结构,包含了文件描述符,回调函数等信息

struct selectop

select后端的结构,存储select函数需要的参数信息的结构,event_base中使用evbase指向这些信息

select.c

libevent中实现select后端的源码文件

struct event_io_map

event_base中io成员的类型,用来存储io类型的event结构的hash表

struct evmap_io

双向链表描述结构,用于io类型的event

activequeues

event_base结构中的成员,evcallback_list类型的动态数组,保存待执行的回调函数的链表。

select后端的定义在select.c源码文件中

event/event_callback结构定义在源码文件event_struct.h中

event_base结构定义在源码文件event-internal.h中

event_add/event_base_loop/event_add_nolock函数定义在event.c函数中

evmap_io_add_函数与evmap_io/event_io_map结构定义在evmap.c文件中

libevent源码分析一--io事件响应的更多相关文章

  1. libevent源码分析二--timeout事件响应

    libevent不仅支持io事件,同时还支持timeout事件与signal事件,这篇文件将分析libevent是如何组织timeout事件以及如何响应timeout事件. 1.  min_heap ...

  2. libevent源码分析三--signal事件响应

    libevent支持io事件,timeout事件,signal事件,这篇文件将分析libevent是如何组织signal事件,以及如何实现signal事件响应的. 1.  sigmap 类似于io事件 ...

  3. Libevent源码分析 (1) hello-world

    Libevent源码分析 (1) hello-world ⑨月份接触了久闻大名的libevent,当时想读读源码,可是由于事情比较多一直没有时间,现在手头的东西基本告一段落了,我准备读读libeven ...

  4. 【转】libevent源码分析

    libevent源码分析 转自:http://www.cnblogs.com/hustcat/archive/2010/08/31/1814022.html 这两天没事,看了一下Memcached和l ...

  5. [Abp vNext 源码分析] - 13. 本地事件总线与分布式事件总线 (Rabbit MQ)

    一.简要介绍 ABP vNext 封装了两种事件总线结构,第一种是 ABP vNext 自己实现的本地事件总线,这种事件总线无法跨项目发布和订阅.第二种则是分布式事件总线,ABP vNext 自己封装 ...

  6. Vue.js 源码分析(四) 基础篇 响应式原理 data属性

    官网对data属性的介绍如下: 意思就是:data保存着Vue实例里用到的数据,Vue会修改data里的每个属性的访问控制器属性,当访问每个属性时会访问对应的get方法,修改属性时会执行对应的set方 ...

  7. UiAutomator源码分析之注入事件

    上一篇文章<UiAutomator源码分析之UiAutomatorBridge框架>中我们把UiAutomatorBridge以及它相关的类进行的描述,往下我们会尝试根据两个实例将这些类给 ...

  8. [置顶] Android源码分析-点击事件派发机制

    转载请注明出处:http://blog.csdn.net/singwhatiwanna/article/details/17339857 概述 一直想写篇关于Android事件派发机制的文章,却一直没 ...

  9. Libevent源码分析—event, event_base

    event和event_base是libevent的两个核心结构体,分别是反应堆模式中的Event和Reactor.源码分别位于event.h和event-internal.h中 1.event: s ...

随机推荐

  1. AcWing 95 费解的开关

    目录 前言 题目链接 思路 代码 前言 博客咕咕咕了好久了,是时候写一下了 题目链接 AcWing 95 费解的开关 思路 首先可以看出 1.每一个位置顶多只会操作一次.因为如果操作两次的话,相当于不 ...

  2. share memory between guest and nic

    通过硬件的IOMMU,内核提供的共享内存.VFIO可以实现. REF: 1. offical DPDK API Doc, 简书有翻译版 DPDK编程指南(翻译)(一)  (二十七) 2. dpdk v ...

  3. js中实现函数防抖跟函数节流

    最近刚接触两个新概念函数防抖与函数节流,虽然这些内容网上可以搜到很多,大家都有自己的一套的理解方式,都写得很好, 而自己则想在理解的基础上自己把代码实现一遍,加深印象. 一.函数防抖 假如我们有这样的 ...

  4. Mongoose 两个表关联查询aggregate 以及 Mongoose中获取ObjectId

    Mongoose 两个表关联查询aggregate 通常两个表关联查询的时候,是一种一对多的关系,比如订单与订单详情就是一对多的关系,一个订单下面有多个商品 数据模拟 首先我们先将数据模拟出来,先选择 ...

  5. opener和parent的区别

    openeropener用于在window.open的页面引用执行该window.open方法的的页面的对象.例如:A页面通过window.open()方法弹出了B页面,在B页面中就可以通过opene ...

  6. 【转】linux sed命令

    转自:linux sed命令就是这么简单 参考:Linux三大剑客之sed:https://blog.csdn.net/solaraceboy/article/details/79272344 阅读目 ...

  7. 如何查看电脑的GPU信息

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/qq_33690342/article/ ...

  8. Arbitrary Style Transfer in Real-time with Adaptive Instance Normalization

    Arbitrary Style Transfer in Real-time with Adaptive Instance Normalization 2019-10-10 10:50:19 Paper ...

  9. 使用analyze命令统计信息

    ① 搜集和删除索引.表和簇的统计信息② 验证表.索引和簇的结构③ 鉴定表和簇和行迁移和行链接针对analyze的搜集和删除统计信息功能而言Oracle推荐使用DBMS_STATS包来代替analyze ...

  10. intellij idea远程调试

    有时候发布后的包不得不进行debug,但是又不方便本地开发环境直接debug模拟,所以不得不需要远程debug. 启动参数 首先在服务端使用JVM的-Xdebug参数启动Jar包. java -Xde ...