(七)注册/注销event事件

Unity插件EasyButton中委托事件的注册和调用 //消息传递Message //时间委托delegate&&event Easy Button事件监听的两种方式 (1).Message ①SeedMessage 向游戏物体发消息,遍历所有脚本中查找符合的定义的方法 然后调用 ②SeedMessageUpwards 向上父类遍历 ③BroadcastMessage 向下子类遍历 (2).Event 调用注册上的方法,是固定 阅读详情

前言

在本小节中,我们将看到event是如何注册到event_base上的,以及如何注销,如何激活等操作,最后还会梳理一下事件的状态是如何变化的。

event_add

int
event_add(struct event *ev, const struct timeval *tv)
{
    struct event_base *base = ev->ev_base;
    const struct eventop *evsel = base->evsel;
    void *evbase = base->evbase;
    int res = 0;

    event_debug((
         "event_add: event: %p, %s%s%scall %p",
         ev,
         ev->ev_events & EV_READ ? "EV_READ " : " ",
         ev->ev_events & EV_WRITE ? "EV_WRITE " : " ",
         tv ? "EV_TIMEOUT " : " ",
         ev->ev_callback));

    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 */
    }
    //如果事件未注册或激活并且是读/写/信号事件则注册事件(实质上就是调用相关多路I/O复用机制的add函数,比如epoll,则是epoll_ctl)
    if ((ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL)) &&
        !(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE))) {
        res = evsel->add(evbase, ev);
        //注册成功后,将event插入到注册链表中
        if (res != -1)
            event_queue_insert(base, ev, EVLIST_INSERTED);
    }

    /*
     * we should change the timout state only if the previous event
     * addition succeeded.
     */
    //添加定时事件(所以之前才说事件之间可以进行或操作,这是因为一个I/O事件或信号事件也可能是定时事件)
    if (res != -1 && tv != NULL) {
        struct timeval now;

        /*
         * we already reserved memory above for the case where we
         * are not replacing an exisiting timeout.
         */
        //如果已经在定时事件中,那么移除,添加新的
        if (ev->ev_flags & EVLIST_TIMEOUT)
            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. */
        //检测当前事件的状态是激活态并且激活的事件类型是定时事件(意思就是是因为超时了,然后该事件被激活的)
        //如果是,先从激活事件链表中移出再重新设置定时事件;如果不是,则在其还未超时之前将其插入到管理定时事件的小根堆中
        if ((ev->ev_flags & EVLIST_ACTIVE) &&
            (ev->ev_res & EV_TIMEOUT)) {
            /* See if we are just active executing this
             * event in a loop
             */
            //将调用的次数设置为0
            if (ev->ev_ncalls && ev->ev_pncalls) {
                /* Abort loop */
                *ev->ev_pncalls = 0;
            }
            //执行到此处证明该事件已经是因为超时而被激活过的了,将其移出
            event_queue_remove(base, ev, EVLIST_ACTIVE);
        }
        //计算超时的时间,并插入到小根堆中
        gettime(base, &now);
        evutil_timeradd(&now, tv, &ev->ev_timeout);

        event_debug((
             "event_add: timeout in %ld seconds, call %p",
             tv->tv_sec, ev->ev_callback));

        event_queue_insert(base, ev, EVLIST_TIMEOUT);
    }

    return (res);
}

这个注册event的函数大体上的逻辑还是比较清晰。需要关注的是:

  1. 定时事件比较特殊,需要先在小根堆上预留一个位置,如果失败了,接下来也不用继续了
  2. 当定时事件已经在小根堆是直接移除后再添加,这是因为定时的时间可能不同,那么处于小根堆的位置也不同
  3. 当该定时事件已经因为超时而被激活了,则从管理激活事件的链表中移出再插入到管理超时事件的小根堆中
  4. 注册事件的实质就是让对应的多路I/O机制进行监听,并将其插入到相应的事件链表/小根堆中。

event_del

int
event_del(struct event *ev)
{
    struct event_base *base;
    const struct eventop *evsel;
    void *evbase;

    event_debug(("event_del: %p, callback %p",
         ev, ev->ev_callback));

    /* An event without a base has not been added */
    if (ev->ev_base == NULL)
        return (-1);

    base = ev->ev_base;
    evsel = base->evsel;
    evbase = base->evbase;

    assert(!(ev->ev_flags & ~EVLIST_ALL));

    /* 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;
    }

    if (ev->ev_flags & EVLIST_TIMEOUT)
        event_queue_remove(base, ev, EVLIST_TIMEOUT);

    if (ev->ev_flags & EVLIST_ACTIVE)
        event_queue_remove(base, ev, EVLIST_ACTIVE);

    if (ev->ev_flags & EVLIST_INSERTED) {
        event_queue_remove(base, ev, EVLIST_INSERTED);
        //如果是I/O或signal事件,由于是对应的多路I/O机制在监听,所以注销的时候,也应该取消监听
        return (evsel->del(evbase, ev));
    }

    return (0);
}

这个函数没什么好讲的,无非就是将调用回调函数次数清0并且将该事件从管理各种事件的链表/小根堆上移出,不过如果是已注册的I/O或信号事件,最后一步需要用到对应的多路I/O机制的取消监听的函数。

接下来我们可以来看看event_queue_insert和event_queue_remove这两个函数,因为前面两个函数都有调用。

event_queue_insert

void
event_queue_insert(struct event_base *base, struct event *ev, int queue)
{
    if (ev->ev_flags & queue) {
        /* Double insertion is possible for active events */
        //这是为了避免重复的事件。因为虽然一个事件可以有多种类型,如同时是I/O和定时事件,但是该事件处于激活状态是绝对唯一的。
        if (queue & EVLIST_ACTIVE)
            return;

        event_errx(1, "%s: %p(fd %d) already on queue %x", __func__,
               ev, ev->ev_fd, queue);
    }
    //事件的数量+1
    if (~ev->ev_flags & EVLIST_INTERNAL)
        base->event_count++;
    //接下来就是根据queue设置的事件类型不同而插入到不同的链表/小根堆中了
    ev->ev_flags |= queue;
    switch (queue) {
    //I/O或signal事件,加入到已注册事件链表中
    case EVLIST_INSERTED:
        TAILQ_INSERT_TAIL(&base->eventqueue, ev, ev_next);
        break;
    //已经激活的事件,加入激活事件链表中
    case EVLIST_ACTIVE:
        //已激活事件数+1
        base->event_count_active++;
        TAILQ_INSERT_TAIL(base->activequeues[ev->ev_pri],
            ev,ev_active_next);
        break;
    //定时事件,插入到小根堆中
    case EVLIST_TIMEOUT: {
        min_heap_push(&base->timeheap, ev);
        break;
    }
    default:
        event_errx(1, "%s: unknown queue %x", __func__, queue);
    }
}

event_queue_insert函数的逻辑也很清晰,就是根据事件类型的不同插入到不同的管理事件的数据结构中。里面主要调用了TAILQ_INSERT_TAIL和min_heap_push这两个函数,它们的功能你应该也能猜到,所以我打算后面才来讲它们。

event_queue_remove

void
event_queue_remove(struct event_base *base, struct event *ev, int queue)
{
    if (!(ev->ev_flags & queue))
        event_errx(1, "%s: %p(fd %d) not on queue %x", __func__,
               ev, ev->ev_fd, queue);

    if (~ev->ev_flags & EVLIST_INTERNAL)
        base->event_count--;

    ev->ev_flags &= ~queue;
    switch (queue) {
    case EVLIST_INSERTED:
        TAILQ_REMOVE(&base->eventqueue, ev, ev_next);
        break;
    case EVLIST_ACTIVE:
        base->event_count_active--;
        TAILQ_REMOVE(base->activequeues[ev->ev_pri],
            ev, ev_active_next);
        break;
    case EVLIST_TIMEOUT:
        min_heap_erase(&base->timeheap, ev);
        break;
    default:
        event_errx(1, "%s: unknown queue %x", __func__, queue);
    }
}

这个函数的逻辑和insert大致相同,但是实现的功能相反。

小结

本节我们学到了如何注册/注销一个event,并且看到了事件链表/小根堆的实际应用,我相信此时你对libevent的大致框架已经有一点感觉了。一个事件从初始化,再到被注册到event_base上,根据事件类型的不同存放到不同的管理事件的数据结构上,此时事件处于已经注册但处于未决态,接着等待事件被激活,最后根据优先级被调度。
接下来我们就进入到主循环,看看一个事件到底是如何经过这些过程的。

libevent的event处理框架剖析 前几次剖析了libevent的tail queue和evbuffer,今天来剖析一下它的事件处理框架。这个在剖析evbuffer之前已经大致走过几遍,但思路不是很清晰,是因为我没有用实例去测试event流程。通过这次我学习到了剖析源码不仅要去看源码,而且你要测试它这项接口是怎么用的,不然只会似懂非懂。 首先来看一下event结构体: struct event { TAILQ_ENTR 阅读详情

相关推荐

C# 事件总线 Event Bus

事件总线:是对发布-订阅模式的一种实现,是一种集中式事件处理机制。它允许事件发送者(发布者)与事件接收者(订阅者)之间保持解耦,即它们不需要直接知道对方的存在,只需要通过事件总线进行通信。发布-订阅模式:是一种消息传递模式,消息发送者(称为发布者)不会将消息直接发送给特定的接收者。相反,发布者将发布的消息分类成不同的频道(Channel),不需要知道哪些消息接收者(称为订阅者)会存在或监听这些频道。首先,定义一个事件监听器接口,所有的事件监听器都需要实现这个接口。这个接口包含一个方法,用于处理事件。

学习和分享 2461

event 注册和注销

本节我们依旧来分析event的操作, 接着上节没有讲完的关于event的注册和注销两个功能的实现. event 注册 依旧来看一个event 注册函数的原型 int event_add(struct event *ev, const struct timeval *tv); 相比event_set函数event_add真的很干净. 参数 ev : 将要注册的event事件. 其实实际注册的是e...

啦啦啦 996

C#event EventHandler事件触发

在定义委托时,前面加上event关键字,可以保证该委托不能在外部被随意触发,两者异同: 注册 注销 内部触发 外部触发 delegate += -= Invoke Invoke event delegate += -= Invoke 不允许 所以,event关键字有助于提高类的封装性,物理隔绝代码耦合,...

qq_29136825的博客 3016

QT事件注册(捕获事件)

QObject::event(),    QObject::installEventFilter(),    QWidget::event(),    QCoreApplication::sendEvent(),    QCoreApplication::postEvent(),    QCoreApplication::processEvents().   普通事件:void customEve...

maonianlove的博客 1438

ESP32——事件监听

由于ESP32使用了FreeRTOS实时操作系统,所以避免不了事件,任务的使用。使用事件组件库可以申明其他组件可以向其注册处理程序的事件,代码将在事件发生时执行。这允许松耦合的组件将所需的行为附加到其他组件的状态更改。

chzt的博客 1716

Mac上安装Label Studio

以上步骤详细说明了如何在Mac上通过Anaconda安装Label Studio。记得根据你的具体需求选择是否创建新的环境,并使用conda或pip进行安装。安装完成后,务必验证Label Studio是否成功运行。

g2526的博客 1958

C++Qt开发——事件处理函数

C++Qt开发——事件处理函数

m0_60259116的博客 3612

SAP PI实战:5分钟搞定REST适配器同步接口配置(含Postman测试技巧)

本文详细介绍了如何在SAP PI中快速配置REST适配器同步接口,包括ESR设计、集成流配置及Postman测试技巧。通过实战步骤和关键参数设置,帮助开发者高效实现系统集成,特别适合需要实时数据交互的业务场景。

weixin_33738555的博客 485

event事件的注册,监听及触发

在JS的业务应用中,现代的js框架基本都提供了自定义事件的封装.. 那么其实现方式是如何的。。本章将一起瞻顾下其原生的event对象,以及最新的DOM3的Event事件支持.. > 1.如何自定义一个事件 var event = document.createEvent(type); * event是通过event创建的实例对...

weixin_30770783的博客 388

PayPal支付集成完全指南:开源库实践

本文介绍了如何从零开始集成PayPal支付功能。首先概述了PayPal提供的多语言SDK和开源库,包括PHP、Python、Node.js等版本。然后详细说明了准备工作,包括注册开发者账号、获取API凭证和使用沙盒环境测试。文章以Node.js为例,演示了安装SDK、基础配置和实现支付流程的完整代码,包括创建订单和捕获支付两个核心功能。最后提及了订阅支付等高级功能,为开发者提供了完整的支付集成方案。通过清晰的代码示例和步骤说明,帮助开发者快速实现PayPal支付功能集成。

2501_93501927的博客 1258

ABAP传输自定义IDOC实例

首先确定出站方和入站方,本例中我使用150作为发送方将200作为接收方。 发送端系统配置 进入tcode-SALE,点击定义逻辑系统,这里我定义D01CLNT150为发送端, D01CLNT200为接收端 2,将逻辑系统分配给对应的client 3,进入tcode-WE31,创建IDOC开发段(segment)这里可以按照实际需求创建想要传输的结构,为求简单我创建了ZKNA1并选取了几个客户主数据的字段作为示例 4,进入tcode-WE30,创建Idoc Ty...

Nathan的博客 4077

Libevent库源码介绍及TCP后端服务器

Libevent 是一个轻量级的开源高性能事件通知库,主要有以下几个亮点:事件驱动,高性能;轻量级,专注于网络,源代码相当精炼、易读;跨平台,支持 Windows、 Linux、 *BSD 和 Mac Os;支持多种 I/O 多路复用技术, epoll、 poll、 dev/poll、 select 和 kqueue 等;支持 I/O,定时器和信号等事件;注册事件优先级.

遛遛马,务务农 755

5060TI显卡深度学习环境搭建:CUDA12.8与CUDNN9.8.0的完美搭配

本文详细介绍了在Win10系统下为5060TI显卡搭建深度学习环境的最佳实践,重点配置CUDA12.8与CUDNN9.8.0的完美组合。通过优化OpenCV编译选项和YOLOv5环境设置,显著提升模型训练和推理效率,帮助开发者充分发挥5060TI显卡的性能潜力。

weixin_29065791的博客 487

Event事件的注册和注销

通过+/-来注册和注销定义的Event。 如下通过匿名Event举例: EventHandler _testHandler = null; //注册 TestClickEvent += _testHandler = { o,e } => { ........ } //注销 TestClickEvent -= _testHandler ;

tianyu0910的专栏 703

海康工业相机使用教程

海康工业相机连接电脑教程

qq_43251445的博客 2万+

Windows 2012 SYSVOL复制失败的处理

题外话:晓原理、知架构、查日志是排错的三件法宝今天在DC1上新建并链接了一个组策略对像,在客户端刷新时,却报下面的错误:“处理组策略失败。Windows无法应用组策略对象LDAP….”如下图所示:于是在客户端用\\domain.com访问,发现\\domain.com\SYSVOL\domain.com\policies下面没有上面报错的组策略对象。这是怎么一回事呢,pin...

weixin_33989780的博客 1669

Unity Event注册与注销

参考https://www.cnblogs.com/horse/archive/2010/02/19/1669333.html 因为程序一直处在运行中,所以主窗体对象一直驻留内存中并保持着自身的状态(它没有的disposed),所以,每次子窗体创建时,主窗体都会注册它的OnRequestUserList事件,同样的,该子窗体在加载时,自身也会把主窗体的OnUserListCreated事件注册...

yaoyutian的博客 1692

安卓手机老版本微信无法登录?无需root 一招教你如何搞定!旧版本照样登录,微信7.0亲测有效

微信我试了清缓存、用不同的版本、甚至关网登录都不行。于是我研究了一下背后的原理,发现这不仅是个技巧,还是微信设计的一点“疏忽”——**只要有方法,旧版本照样能用**。

代码简单说 Vue、JAVA、PHP、Node.js 熟练运用,接口、架构、性能全搞定。 1万+

esp-idf 之事件循环(Event Loop)例程详解

本文是将Default Event Loop:esp-idf/examples/system/esp_event/default_event_loop at release/v3.3 · espressif/esp-idf 例程内的README和代码注释翻译一下,便于各位看官理解并理顺思路。 README 事件循环库Event Loop Library (‘esp_event’) 默认事件循环例程Default Event Loop Example 本例程是事件循环库的一个示例。为了保持简单,示例仅限于使用

SimonLiu的博客 9736

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

【unity实战】事件(Event)的基本实战使用

【unity小技巧】委托(Delegate)的基础使用和介绍这期来讲讲事件,使用你会发现它和委托真的很像,那么他们具体有什么区别呢?委托(Delegate)委托是一种类型,用于引用一个或多个方法。委托可以看作是函数指针或函数的代理。委托用于实现回调函数、事件处理和方法的动态绑定。委托是一种类型安全的方式来封装方法调用。事件(Event)事件是建立在委托之上的高级概念,它提供了一种更加简洁、安全的方式来实现观察者模式。事件使用委托来通知其他对象或类,当特定情况发生时,让它们执行相应的操作。

向宇的博客,专注php/web全栈 unity游戏开发,欢迎大家评论纠错 1万+

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

C# 事件(event)

事件是一种特殊的多播委托,仅可以从声明事件的类(或派生类)或结构(发布服务器类)中对其进行调用。如果其他类或结构订阅该事件,则在发布服务器类引发该事件时,将调用其事件处理程序方法可以将事件标记为publicprivateprotectedinternal或。这些访问修饰符定义该类的用户访问该事件的方式。事件在我们平时开发中用的还是比较多的,大部分时候用在框架中,比如 Winform 中的 Timer 组件,如下面代码,Timer.Elapsed 就是一个事件//实例化Timer类,

熊思雨的博客 7775

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

Linux input子系统分析之三 input device/handler注册与注销分析

上一章我们分析了input子系统主要的数据结构,本章我们主要分析input hander、input device、input handle的注册与注销。本章的主要内容如下: 一、input device的注册与注销说明 二、input handler的注册与注销说明 三、input handle的注册与注销说明 四、input事件分发说明 在分析input device、input handler、input handle的注册与注销时,我们还是看下这些数据结构...

lickylin的专栏 1385

net_device的注册与注销

net_device对象只有通过register_netdev()注册到系统后才能被外部感知到。类似的,在释放之前需要通过unregister_netdev()将net_device对象先从系统中注销。这篇笔记分析了这两个过程的实现。

九天小哥的专栏 5377

委托的注册及注销+观察者模式

所以取消public,封闭它,我们可以自己书写两个方法,供外部注册与注销,委托调用在子方法里调用,这样封装委托变量可以使它更安全,这个就叫做事件。一系列对象来监听另外一个对象的行为,被监听者一旦触发事件/发布消息,则被所有监听者收到,然后执行自己的行为。就是使用委托/事件,让一系列对象把他们的行为来注册到我的委托中去。委托变量如果公开出去,很不安全,外部可以随意调用。小花想在打铃时去打羽毛球。小明想在打铃的时候去买东西吃,2.只能自己去调用自己的委托。小红想在打铃时开始练习,小张想在打铃时去打水,

m0_74236534的博客 593

libevent事件处理框架:event_base

回想Reactor 模式的几个基本组件, 本节讲解的部分对应于Reactor 框架组件。在libevent中,Reactor 框架组件表现为event_base结构体,结构体声明如下,它位于event-internal.h 文件中: struct event_base { const struct eventop * evsel ; void * evbase; int event_count ;...

shayne000的博客 752

libevent学习笔记七:libevent事件处理框架剖析

libevent学习笔记七:libevent事件处理框架剖析 前面几节已经对libevent的事件处理框架和event结构体做了描述和介绍,现在是需要进一步的深入剖析的时候了,更深入的对libevent事件的详细处理流程进行解析和理解,本节具体的将分析libevent的事件处理框架event_base和libevent注册、删除事件的流程,可结合前一节libevent对event的管理的流程进行...

jyl_sh的专栏 1266
上一篇: (六)初始化并设置event
下一篇: (八)事件主循环
UKey_
博客等级 码龄10年 258粉丝 186原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值