Dispatch类似于一个消息泵,在一个死循环中,不停地检查IO的状态(可以想像成不断从消息队列中读取消息),将状态的改变变成事件,再进行事件的响应。

主要代码如下:

[event.c]

int

event_base_loop(struct event_base *base, int flags)

{

const struct eventop *evsel = base->evsel;

...

done = 0;

base->event_gotterm = base->event_break = 0;

while (!done) {

base->event_continue = 0;

/* Terminate the loop if we have been asked to */

if (base->event_gotterm) {

break;

}

if (base->event_break) {

break;

}

...

/// 在这里调用底层的 dispatch

res = evsel->dispatch(base, tv_p);

...

if (N_ACTIVE_CALLBACKS(base)) {

/// 处理和响应事件

int n = event_process_active(base);

if ((flags & EVLOOP_ONCE)

&& N_ACTIVE_CALLBACKS(base) == 0

&& n != 0)

done = 1;

} else if (flags & EVLOOP_NONBLOCK)

done = 1;

}

done:

...

return (retval);

}

如果不是一次性事件,由while (!done)是一个死循环。这样可以反复地调用底层的dispatch去获取fd的状态. 在dispatch之后,又调用了event_process_active这个重要的函数。后面会讲到,它的作用是调用event绑定的回调函数。

select_dispatch

其主工作是调用了select函数,然后调用 evmap_io_active 触发事件。这个函数的实现有一个小技巧值得学习就是如何对随时变化的集合进行操作。

static int select_dispatch(struct event_base *base, struct timeval *tv)

{

int res=0, i, j, nfds;

struct selectop *sop = base->evbase;

///...

/// 主要工作是调整 event_XXXset_out 与 event_XXXset_in, select()基于前者,而对set的修改基于后者,在select之前有必要做一次同步

memcpy(sop->event_readset_out, sop->event_readset_in,

sop->event_fdsz);

memcpy(sop->event_writeset_out, sop->event_writeset_in,

sop->event_fdsz);

nfds = sop->event_fds+1;

EVBASE_RELEASE_LOCK(base, th_base_lock);

res = select(nfds, sop->event_readset_out,

sop->event_writeset_out, NULL, tv);

EVBASE_ACQUIRE_LOCK(base, th_base_lock);

...

i = random() % nfds;

for (j = 0; j < nfds; ++j) {

if (++i >= nfds)

i = 0;

res = 0;

if (FD_ISSET(i, sop->event_readset_out))

res |= EV_READ;

if (FD_ISSET(i, sop->event_writeset_out))

res |= EV_WRITE;

if (res == 0)

continue;

evmap_io_active(base, i, res);

}

check_selectop(sop);

return (0);

}

上面的代码中,先调用select获取fd的状态。注意一次并非返回一个fd,而操作两个fd的列表:读的fd列表,写的fd列表。接下来对select之后的队列进行操作,如果有读、写事件,则调用evmap_io_active在一个io map中登记,实际是将事件放到active队列中。此时只是登记,还没有调起事件对应的回调函数。

event_process_active

接下来分析如何调用事件对应的回调函数。回到event_base_dispatch(event_base_loop),从调用底层dispatch之后继续分析。到了最后调用了event_process_active。这个函数就是处理就绪队列的,就是它的内部实现调用了event关联的回调函数。event_process_active的处理流程是这样的:

event_process_active -> event_process_active_single_queue->(*ev->callback)(...)

[event.c]

static int event_process_active(struct event_base *base)

{

/* Caller must hold th_base_lock */

struct event_list *activeq = NULL;

int i, c = 0;

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);

if (c < 0) {

base->event_running_priority = -1;

return -1;

} else if (c > 0)

break; /* Processed a real event; do not

* consider lower-priority events */

/* If we get here, all of the events we processed

* were internal.  Continue. */

}

}

event_process_deferred_callbacks(&base->defer_queue,&base->event_break);

base->event_running_priority = -1;

return c;

}

遍历activequeues,对每个项(一个队列),处理此项。

static int

event_process_active_single_queue(struct event_base *base,

struct event_list *activeq)

{

struct event *ev;

int count = 0;

EVUTIL_ASSERT(activeq != NULL);

for (ev = TAILQ_FIRST(activeq); ev; ev = TAILQ_FIRST(activeq)) {

if (ev->ev_events & EV_PERSIST)

event_queue_remove(base, ev, EVLIST_ACTIVE);

else

event_del_internal(ev);

switch (ev->ev_closure) {

case EV_CLOSURE_SIGNAL:

event_signal_closure(base, ev);

break;

case EV_CLOSURE_PERSIST:

event_persist_closure(base, ev);

break;

default:

case EV_CLOSURE_NONE:

EVBASE_RELEASE_LOCK(base, th_base_lock);

(*ev->ev_callback)(

ev->ev_fd, ev->ev_res, ev->ev_arg);

break;

}

...

if (base->event_break)

return -1;

if (base->event_continue)

break;

}

return count;

}

libevent2源码分析之四:libevent2的消息泵的更多相关文章

  1. zookeeper源码分析之四服务端(单机)处理请求流程

    上文: zookeeper源码分析之一服务端启动过程 中,我们介绍了zookeeper服务器的启动过程,其中单机是ZookeeperServer启动,集群使用QuorumPeer启动,那么这次我们分析 ...

  2. RocketMQ源码分析之RocketMQ事务消息实现原理上篇(二阶段提交)

    在阅读本文前,若您对RocketMQ技术感兴趣,请加入 RocketMQ技术交流群 根据上文的描述,发送事务消息的入口为: TransactionMQProducer#sendMessageInTra ...

  3. 【TencentOS tiny】深度源码分析(4)——消息队列

    消息队列 在前一篇文章中[TencentOS tiny学习]源码分析(3)--队列 我们描述了TencentOS tiny的队列实现,同时也点出了TencentOS tiny的队列是依赖于消息队列的, ...

  4. Spark源码分析之四:Stage提交

    各位看官,上一篇<Spark源码分析之Stage划分>详细讲述了Spark中Stage的划分,下面,我们进入第三个阶段--Stage提交. Stage提交阶段的主要目的就一个,就是将每个S ...

  5. spark 源码分析之四 -- TaskScheduler的创建和启动过程

    在 spark 源码分析之二 -- SparkContext 的初始化过程 中,第 14 步 和 16 步分别描述了 TaskScheduler的 初始化 和 启动过程. 话分两头,先说 TaskSc ...

  6. libevent2源码分析之五:关键的调用链

    用一个调用链来表示函数调用的流程,看起来更直观.根据上面的分析,总结了一些重要的调用链. 初始化 event_base_new event_base_new_with_config min_heap_ ...

  7. libevent2源码分析之三:信号的初始化流程

    libevent2对信号的响应也进行了封装,使之与socket操作一样对外提供统一的接口.这里的信号一般指linux的信号.由于信号与socket相关的编程接口有较大的不同,因此在内部实现也有一些区别 ...

  8. libevent2源码分析之一:前言

    event的本质 libevent2中的event的本质是什么?只要是非同步阻塞的运行方式,肯定遵循事件的订阅-发布模型.通过event_new的函数原型可以理解,一个event即代表一次订阅,建立起 ...

  9. libevent2源码分析之二:初始化流程

    本文并不很详细地分析初始化的各个细节,而重点分析如何将底层操作关联到event_base的相关字段.初始化工作主要是针对event_base的.libevent2支持多种底层实现,有epoll, se ...

随机推荐

  1. Linux上安装Redis教程

    Redis的安装步骤: 步骤1.安装redis必须已经安装了gcc,如果没安装gcc 就使用命令 yum install -y gcc步骤2.下载redis包 下载地址:http://download ...

  2. 线段树【p4879】ycz的妹子

    Description 机房神犇yczycz有n个青梅竹马,她们分别住在1~n号城市中.小时候的她们美丽可爱,但是由于女大十八变,有些妹子的颜值发生了变化,但是十分重感情的\(ycz\)神犇不忍心抛弃 ...

  3. 学习LSM(Linux security module)之四:一个基于LSM的简单沙箱的设计与实现

    嗯!如题,一个简单的基于LSM的沙箱设计.环境是Linux v4.4.28.一个比较新的版本,所以在实现过程中很难找到资料,而且还有各种坑逼,所以大部分的时间都是在看源码,虽然写的很烂,但是感觉收获还 ...

  4. Cent OS 运行 Cuberite

    Cuberite 是一个轻量级的Minecraft服务端,由C++编写,性能比Mojang等等用java写的高很多. 在腾讯云的最低端VPS上,用Spigot建服的话,从主世界传送到下界用时要五六秒的 ...

  5. 【set】bzoj3715 [PA2014]Lustra

    对每种属性开一个set,只要某个厂家符合该属性的最值,就加进set,最后判断是否有某个厂家在4个set里都存在即可. #include<cstdio> #include<set> ...

  6. [HNOI/AHOI2018]寻宝游戏

    题目大意: $n(n\le1000)$个$m(m\le5000)$位的二进制数,第$0$个数为$0$.用$\wedge$和$\vee$将这些数连接起来.$q(q\le1000)$次询问,每次给定一个$ ...

  7. OC语言基础之NSString

    1.字符串的创建 1: NSString *s1 = @"jack"; 2: 3: //NSString *s2 = [[NSString alloc] initWithStrin ...

  8. IntelliJ IDEA导入Maven之后强制刷新项目解决无法识别为Maven项目的问题

    先点击左下角按钮以显示Maven Project 再点击右侧Maven Project 点击刷新按钮,当然也可以点击加号选择pom.xml文件. 最后是等待项目的更新.

  9. Linux查找某个时间点后生成的文件(转)

    需要找到某天(例如2017-04-13)以及这之后生成的空文件.那么这个要怎么处理呢?这个当然是用find命令来解决.如下所示, -mtime -5表示查找距现在5*24H内修改过的文件 -type ...

  10. <摘录>字节对齐(强制对齐以及自然对齐)

    struct {}node; 32为的x86,window下VC下sizeof(node)的值为1,而linux的gcc下值为0: 一.WINDOWS下(VC--其实GCC和其原理基本一样,象这种问题 ...