这篇文章将分析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. 洛谷P1352 没有上司的舞会题解

    题目描述 某大学有N个职员,编号为1~N.他们之间有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直接上司.现在有个周年庆宴会,宴会每邀请来一个职员都会增加一定的快乐指数Ri, ...

  2. 内核过DSE驱动签名验证.

    一丶简介 现在的驱动,必须都有签名才能加载.那么如何加载无签名的驱动模块那. 下面可以说下方法.但是挺尴尬的是,代码必须在驱动中编写.所以就形成了 你必须一个驱动带有一个签名加载进去.执行你的代码.p ...

  3. SpringMVC相关试题

    1.下列相关Spring自动装配的说法中,错误的是( ). (选择一项) A:在Spring配置文件中,可以通过<bean>元素的autowire属性指定自动装配方式B: autowire ...

  4. linux高性能服务器编程 (六) --高级I/O函数

    第六章 高级I/O函数 Linux提供了很多高级的I/O函数,它不是基础的I/O函数(open/read) 1.创建文件描述符的函数比如:pipe.dup/dup2函数 2.读写数据的函数比如:rea ...

  5. ARC098F Donation

    传送门 Atcoder Solution 首先是几个引理: 重新定义权值\(val_i=max(a_i-b_i,0)\),那么通过这个点必须需要\(val_i+b_i\)的钱. 多次经过一个点一定是在 ...

  6. KAFKA && zookeeper 集群安装

    服务器:#vim /etc/hosts10.16.166.90 sh-xxx-xxx-xxx-online-0110.16.168.220 sh-xx-xxx-xxx-online-0210.16.1 ...

  7. Spring Boot打war包和jar包的目录结构简单讲解

    Spring Boot项目可以制作成jar包和war包,其目录结构是不一样的,具体的如下所示: 1.war包目录结构分析WAR(Web Archivefile)网络应用程序文件,是与平台无关的文件格式 ...

  8. javascript的Map使用

    setExpenseAndAmountSum: function() { var detailList = vehicleVueObj.vehicleData; var expenseAmountSu ...

  9. python confluent kafka客户端配置kerberos认证

    kafka的认证方式一般有如下3种: 1. SASL/GSSAPI  从版本0.9.0.0开始支持 2. SASL/PLAIN   从版本0.10.0.0开始支持 3. SASL/SCRAM-SHA- ...

  10. Xamarin.FormsShell基础教程(2)创建Shell解决方案

    Xamarin.FormsShell基础教程(2)创建Shell解决方案 创建Shell解决方案 在开发Shell的应用程序时,首先需要创建一个Shell解决方案,其具体操作步骤如下: (1)在VS的 ...