CppGuide社区 CppGuide社区
首页
  • 🔥最新谷歌C++风格指南(含C++17/20)
  • 🔥C++17详解
  • 🔥C++20完全指南
  • 🔥C++23快速入门
  • C++语言面试问题集锦
  • 🔥交易系统开发岗位求职与面试指南 (opens new window)
  • 第1章 高频C++11重难点知识解析
  • 第2章 Linux GDB高级调试指南
  • 第3章 C++多线程编程从入门到进阶
  • 第4章 C++网络编程重难点解析
  • 第5章 网络通信故障排查常用命令
  • 第6章 高性能网络通信协议设计精要
  • 第7章 高性能服务结构设计
  • 第8章 Redis网络通信模块源码分析
  • 第9章 后端服务重要模块设计探索
  • 🚀 全部章节.pdf 下载 (opens new window)
  • 源码分析系列

    • leveldb源码分析
    • libevent源码分析
    • Memcached源码分析
    • TeamTalk源码分析
    • 优质源码分享 (opens new window)
    • 🔥远程控制软件gh0st源码分析
  • 从零手写C++项目系列

    • 🔥C++游戏编程入门(零基础学C++)
    • 🔥使用C++17从零开发一个调试器 (opens new window)
    • 🔥使用C++20从零构建一个完整的低延迟交易系统 (opens new window)
    • 🔥使用C++从零写一个C语言编译器 (opens new window)
    • 🔥从零用C语言写一个Redis
  • 🔥Windows 10系统编程
  • 🔥Linux 5.x内核开发与调试 完全指南 (opens new window)
  • TCP源码实现超详细注释版.pdf (opens new window)
  • Go语言特性

    • Go系统接口编程
    • 高效Go并发编程
    • Go性能调优
    • Go项目架构设计
  • Go项目实战

    • 🔥使用Go从零开发一个数据库
    • 🔥使用Go从零开发一个编译器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥用Go从零写一个编排器(类Kubernetes) (opens new window)
Rust编程指南
  • SQL零基础指南
  • MySQL开发与调试指南
GitHub (opens new window)
首页
  • 🔥最新谷歌C++风格指南(含C++17/20)
  • 🔥C++17详解
  • 🔥C++20完全指南
  • 🔥C++23快速入门
  • C++语言面试问题集锦
  • 🔥交易系统开发岗位求职与面试指南 (opens new window)
  • 第1章 高频C++11重难点知识解析
  • 第2章 Linux GDB高级调试指南
  • 第3章 C++多线程编程从入门到进阶
  • 第4章 C++网络编程重难点解析
  • 第5章 网络通信故障排查常用命令
  • 第6章 高性能网络通信协议设计精要
  • 第7章 高性能服务结构设计
  • 第8章 Redis网络通信模块源码分析
  • 第9章 后端服务重要模块设计探索
  • 🚀 全部章节.pdf 下载 (opens new window)
  • 源码分析系列

    • leveldb源码分析
    • libevent源码分析
    • Memcached源码分析
    • TeamTalk源码分析
    • 优质源码分享 (opens new window)
    • 🔥远程控制软件gh0st源码分析
  • 从零手写C++项目系列

    • 🔥C++游戏编程入门(零基础学C++)
    • 🔥使用C++17从零开发一个调试器 (opens new window)
    • 🔥使用C++20从零构建一个完整的低延迟交易系统 (opens new window)
    • 🔥使用C++从零写一个C语言编译器 (opens new window)
    • 🔥从零用C语言写一个Redis
  • 🔥Windows 10系统编程
  • 🔥Linux 5.x内核开发与调试 完全指南 (opens new window)
  • TCP源码实现超详细注释版.pdf (opens new window)
  • Go语言特性

    • Go系统接口编程
    • 高效Go并发编程
    • Go性能调优
    • Go项目架构设计
  • Go项目实战

    • 🔥使用Go从零开发一个数据库
    • 🔥使用Go从零开发一个编译器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥用Go从零写一个编排器(类Kubernetes) (opens new window)
Rust编程指南
  • SQL零基础指南
  • MySQL开发与调试指南
GitHub (opens new window)
  • libevent源码深度剖析01
  • libevent源码深度剖析02
  • libevent源码深度剖析03
  • libevent源码深度剖析04
  • libevent源码深度剖析05
  • libevent源码深度剖析06
    • libevent源码深度剖析07
    • libevent源码深度剖析08
    • libevent源码深度剖析09
    • libevent源码深度剖析10
    • libevent源码深度剖析11
    • libevent源码深度剖析12
    • libevent源码深度剖析13
    • libevent源码深度剖析
    zhangxf
    2023-04-02
    目录

    libevent源码深度剖析06

    # libevent源码深度剖析06

    初见事件处理框架

    前面已经对libevent的事件处理框架和event结构体做了描述,现在是时候剖析libevent对事件的详细处理流程了,本节将分析libevent的事件处理框架event_base和libevent注册、删除事件的具体流程,可结合前一节libevent对event的管理。

    # 1. 事件处理框架-event_base

    回想Reactor模式的几个基本组件,本节讲解的部分对应于Reactor框架组件。在libevent中,这就表现为event_base结构体,结构体声明如下,它位于event-internal.h文件中:

    struct event_base {
        const struct eventop *evsel;
        void *evbase; 
        int event_count;  /* counts number of total events */
        int event_count_active; /* counts number of active events */
        int event_gotterm;  /* Set to terminate loop */
        int event_break;  /* Set to terminate loop immediately */
        /* active event management */
        struct event_list **activequeues;
        int nactivequeues;
        /* signal handling info */
        struct evsignal_info sig;
        struct event_list eventqueue;
        struct timeval event_tv;
        struct min_heap timeheap;
        struct timeval tv_cache;
    };
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

    下面详细解释一下结构体中各字段的含义。

    1. evsel和evbase这两个字段的设置可能会让人有些迷惑,这里你可以把evsel和evbase看作是类和静态函数的关系,比如添加事件时的调用行为:evsel->add(evbase, ev),实际执行操作的是evbase;这相当于class::add(instance, ev),instance就是class的一个对象实例。 evsel指向了全局变量static const struct eventop *eventops[]中的一个; 前面也说过,libevent将系统提供的I/O demultiplex机制统一封装成了eventop结构;因此eventops[]包含了select、poll、kequeue和epoll等等其中的若干个全局实例对象。 evbase实际上是一个eventop实例对象; 先来看看eventop结构体,它的成员是一系列的函数指针, 在event-internal.h文件中:

      struct eventop {
          const char *name;
          void *(*init)(struct event_base *); // 初始化
          int (*add)(void *, struct event *); // 注册事件
          int (*del)(void *, struct event *); // 删除事件
          int (*dispatch)(struct event_base *, void *, struct timeval *); // 事件分发
          void (*dealloc)(struct event_base *, void *); // 注销,释放资源
          /* set if we need to reinitialize the event base */
          int need_reinit;
      };
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10

      也就是说,在libevent中,每种I/O demultiplex机制的实现都必须提供这五个函数接口,来完成自身的初始化、销毁释放;对事件的注册、注销和分发。 比如对于epoll,libevent实现了5个对应的接口函数,并在初始化时并将eventop的5个函数指针指向这5个函数,那么程序就可以使用epoll作为I/O demultiplex机制了,这个在后面会再次提到。

    2. activequeues是一个二级指针,前面讲过libevent支持事件优先级,因此你可以把它看作是数组,其中的元素activequeues[priority]是一个链表,链表的每个节点指向一个优先级为priority的就绪事件event。

    3. eventqueue,链表,保存了所有的注册事件event的指针。

    4. sig是由来管理信号的结构体,将在后面信号处理时专门讲解;

    5. timeheap是管理定时事件的小根堆,将在后面定时事件处理时专门讲解;

    6. event_tv和tv_cache是libevent用于时间管理的变量,将在后面讲到; 其它各个变量都能因名知意,就不再啰嗦了。

    # 2. 创建和初始化event_base

    创建一个event_base对象也既是创建了一个新的libevent实例,程序需要通过调用event_init()(内部调用event_base_new函数执行具体操作)函数来创建,该函数同时还对新生成的libevent实例进行了初始化。

    • 该函数首先为event_base实例申请空间,
    • 然后初始化timer mini-heap,选择并初始化合适的系统I/O 的demultiplexer机制,初始化各事件链表;

    函数还检测了系统的时间设置,为后面的时间管理打下基础。

    # 3. 接口函数

    前面提到Reactor框架的作用就是提供事件的注册、注销接口;根据系统提供的事件多路分发机制执行事件循环,当有事件进入“就绪”状态时,调用注册事件的回调函数来处理事件。 Libevent中对应的接口函数主要就是:

    int  event_add(struct event *ev, const struct timeval *timeout);
    int  event_del(struct event *ev);
    int  event_base_loop(struct event_base *base, int loops);
    void event_active(struct event *event, int res, short events);
    void event_process_active(struct event_base *base); 
    
    1
    2
    3
    4
    5

    本节将按介绍事件注册和删除的代码流程,libevent的事件循环框架将在下一节再具体描述。

    • 对于定时事件,这些函数将调用timer heap管理接口执行插入和删除操作;
    • 对于I/O和Signal事件将调用eventopadd和delete接口函数执行插入和删除操作(eventop会对Signal事件调用Signal处理接口执行操作);

    这些组件将在后面的内容描述。

    1)注册事件 函数原型:

    int event_add(struct event *ev, const struct timeval *tv)
    
    1

    参数:ev:指向要注册的事件; tv:超时时间;

    e函数将ev注册到ev->ev_base上,事件类型由ev->ev_events指明,

    • 如果注册成功,v将被插入到已注册链表中;
    • 如果tv不是NULL,则会同时注册定时事件,将ev添加到timer堆上;

    如果其中有一步操作失败,那么函数保证没有事件会被注册,可以讲这相当于一个原子操作。这个函数也体现了libevent细节之处的巧妙设计,且仔细看程序代码,部分有省略,注释直接附在代码中。

    int event_add(struct event *ev, const struct timeval *tv) {
    	struct event_base *base = ev->ev_base;
    	// 要注册到的event_base
    	const struct eventop *evsel = base->evsel;
    	void *evbase = base->evbase;
    	// base使用的系统I/O策略
    	// 新的timer事件,调用timer heap接口在堆上预留一个位置
    	// 注:这样能保证该操作的原子性:
    	// 向系统I/O机制注册可能会失败,而当在堆上预留成功后,
    	// 定时事件的添加将肯定不会失败;
    	// 而预留位置的可能结果是堆扩充,但是内部元素并不会改变
    	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 */
    	}
    	// 如果事件ev不在已注册或者激活链表中,则调用evbase注册事件
    	if ((ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL)) && !(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE))) {
    		res = evsel->add(evbase, ev);
    		if (res != -1) // 注册成功,插入event到已注册链表中
    		event_queue_insert(base, ev, EVLIST_INSERTED);
    	}
    	// 准备添加定时事件
    	if (res != -1 && tv != NULL) {
    		struct timeval now;
    		// EVLIST_TIMEOUT表明event已经在定时器堆中了,删除旧的
    		if (ev->ev_flags & EVLIST_TIMEOUT)
    		        	event_queue_remove(base, ev, EVLIST_TIMEOUT);
    		// 如果事件已经是就绪状态则从激活链表中删除
    		if ((ev->ev_flags & EVLIST_ACTIVE) &&
    		        (ev->ev_res & EV_TIMEOUT)) {
    			// 将ev_callback调用次数设置为0
    			if (ev->ev_ncalls && ev->ev_pncalls) {
    				*ev->ev_pncalls = 0;
    			}
    			event_queue_remove(base, ev, EVLIST_ACTIVE);
    		}
    		// 计算时间,并插入到timer小根堆中
    		gettime(base, &now);
    		evutil_timeradd(&now, tv, &ev->ev_timeout);
    		event_queue_insert(base, ev, EVLIST_TIMEOUT);
    	}
    	return (res);
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    • event_queue_insert()负责将事件插入到对应的链表中,下面是程序代码;
    • event_queue_remove()负责将事件从对应的链表中删除,这里就不再重复贴代码了;
    void event_queue_insert(struct event_base *base, struct event *ev, int queue) {
    	// ev可能已经在激活列表中了,避免重复插入
    	if (ev->ev_flags & queue) {
    		if (queue & EVLIST_ACTIVE)
    		   return;
    	}
    	// ...
    	ev->ev_flags |= queue;
    	// 记录queue标记
    	switch (queue) {
    		case EVLIST_INSERTED: // I/O或Signal事件,加入已注册事件链表
    		TAILQ_INSERT_TAIL(&base->eventqueue, ev, ev_next);
    		break;
    		case EVLIST_ACTIVE: // 就绪事件,加入激活链表
    		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;
    	}
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22

    2)删除事件: 函数原型为:

    int event_del(struct event *ev);
    
    1

    该函数将删除事件ev

    • 对于I/O事件,从I/O 的demultiplexer上将事件注销;
    • 对于Signal事件,将从Signal事件链表中删除;
    • 对于定时事件,将从堆上删除;

    同样删除事件的操作则不一定是原子的,比如删除时间事件之后,有可能从系统I/O机制中注销会失败。

    int event_del(struct event *ev) {
    	struct event_base *base;
    	const struct eventop *evsel;
    	void *evbase;
    	// ev_base为NULL,表明ev没有被注册
    	if (ev->ev_base == NULL)
    	  return (-1);
    	// 取得ev注册的event_base和eventop指针
    	base = ev->ev_base;
    	evsel = base->evsel;
    	evbase = base->evbase;
    	// 将ev_callback调用次数设置为
    	if (ev->ev_ncalls && ev->ev_pncalls) {
    		*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);
    		// EVLIST_INSERTED表明是I/O或者Signal事件,
    		// 需要调用I/O demultiplexer注销事件
    		return (evsel->del(evbase, ev));
    	}
    	return (0);
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28

    # 4. 小结

    分析了event_base这一重要结构体,初步看到了libevent对系统的I/O demultiplex机制的封装event_op结构,并结合源代码分析了事件的注册和删除处理,下面将会接着分析事件管理框架中的主事件循环部分。

    编辑 (opens new window)
    上次更新: 2023/12/11, 22:32:09
    libevent源码深度剖析05
    libevent源码深度剖析07

    ← libevent源码深度剖析05 libevent源码深度剖析07→

    最近更新
    01
    第二章 关键字static及其不同用法
    03-27
    02
    第一章 auto与类型推导
    03-27
    03
    C++语言面试问题集锦 目录与说明
    03-27
    更多文章>
    Copyright © 2024-2025 沪ICP备2023015129号 张小方 版权所有
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式