libevent源码解析-定时机制,信号处理,流量控制

概述

libeventeventevent_callbackevent_base除了可以用来支持套接字的自动和手动分发,也可用来支持定时机制,信号处理.这里,我们补充对定时机制,信号处理的分析.

libevent中的通信对象集成了对流量控制的支持,我们这里补充对通信对象流量控制的分析.

libevent中的定时机制

1.概述
前面我们分析eventevent_callbackevent_base时,立足于服务于套接字的event来展开分析的.
libevent中的event既可以用于支持对套接字的自动分法,手动分发.也可用于支持定时机制.还可用于支持信号处理.

这一部分立足于支持定时机制,来分析libeventevent,event_callback,event_base
2. event对于定时支持

struct event {
	struct event_callback ev_evcallback;
	union {
		TAILQ_ENTRY(event) ev_next_with_common_timeout;
		size_t min_heap_idx;
	} ev_timeout_pos;
	evutil_socket_t ev_fd;
	short ev_events;
	short ev_res;		
	struct event_base *ev_base;
	union {
		struct {
			LIST_ENTRY (event) ev_io_next;
			struct timeval ev_timeout;// 当服务于持续性超时event时.这里存储超时间隔.
		} ev_io;
		struct {
			LIST_ENTRY (event) ev_signal_next;
			short ev_ncalls;
			short *ev_pncalls;
		} ev_signal;
	} ev_;
	struct timeval ev_timeout;// 服务于超时event时,这里存储下个超时时间点.
};

我们之前已经从服务于套接字事件分发的角度介绍过event,event_base及其各个字段含义.这里我们立足与服务于定时机制介绍相关字段含义.
(1). ev_evcallback服务于定时机制时,同样借助ev_evcallback指定事件被分发时的处理信息.
(2). ev_timeout_posevent服务于定时机制时,需加入到event_base相应结构中.
event_base为服务于定时的event准备了两类可选结构:通用结构,堆.
被放置于堆结构时,这里存储在堆内索引.
被放置于通用结构时,由于位于同一通用结构内各个event基于链表连接.这里用于构建链接信息.
(3). ev_fd
event_base对指定event支持其服务于三种类型:信号处理,套接字事件分发,定时机制.
其中,套接字事件分发与定时机制可同时存在于一个event身上.
某个event同时服务于套接字事件分发,定时机制时,ev_fd用于存储套接字描述符.
某个event独立服务于定时机制时,ev_fd应设置为-1
(4). ev_events
某个event同时服务于套接字事件分发,定时机制时,ev_events用于存储感兴趣的套接字事件类型.
某个event独立服务于定时机制时,ev_events应设置为0
(5). ev_res
若一个服务于定时机制的event,因为超时而被分发,ev_res用于存储分发原因.超时对应EV_TIMEOUT
(6). ev_base
存储关联到的event_base对象指针.
(7). ev_
当服务于定时机制下,且event属于持久性event.此时ev_io.ev_timeout用来存储超时间隔.
如何理解持久性event
a. 持久性event
a.1. 初次添加到event_base后,从添加时间点或指定时间点开始,直到达到超时间隔期间该event未被分发时,会被自动分发一次.分发原因将是超时.
a.2. 每次event被分发即将执行其回调处理前,依据当前时间点结合超时间隔计算下一次超时时间点并再次注册到event_base
b. 非持久性event
b.1. 初次添加到event_base后,从添加时间点或指定时间点开始,直到达到超时间隔期间该event未被分发时,会被自动分发一次.分发原因将是超时.若在此期间因为其他原因得到分发,分发处理前,此event会自动从event_base移除.这样,后续不会再被event_base分发了.

实现上,服务于超时的event因为超时被分发时,会自动从event_base移除.在服务于超时的event即将被回调处理前,判断若是持久性的,会重新计算下个超时时间点并再次添加到event_base

作为对比,服务于套接字的event因为套接字事件被分发时,不会从event_base移除.但在event即将被回调处理前,判断若是非持久性的,会将此eventevent_base移除.
(8). ev_timeout
服务于定时机制时,用于存储此event将发生超时的超时时间点信息.

3.event_callback对定时机制的支持
我们从支持定时机制角度分析其各个字段.

struct event_callback 
{
	TAILQ_ENTRY(event_callback) evcb_active_next;
	short evcb_flags;
	ev_uint8_t evcb_pri;	/* smaller numbers are higher priority */
	ev_uint8_t evcb_closure;
	/* allows us to adopt for different types of events */
    union 
	{
		void (*evcb_callback)(evutil_socket_t, short, void *);
		void (*evcb_selfcb)(struct event_callback *, void *);
		void (*evcb_evfinalize)(struct event *, void *);
		void (*evcb_cbfinalize)(struct event_callback *, void *);
	} evcb_cb_union;
	void *evcb_arg;
};

(1). evcb_active_next
用于将分发的event_callback组织在链表结构.
(2). evcb_flags
当此event_callback依附的event被插入服务于定时的堆结构或通用结构后,将包含EVLIST_TIMEOUT
(3). evcb_pri
特权级
(4). evcb_closure
当此event_callback依附的event服务于定时机制时,依据是否是持久的event
对持久的eventevcb_closureEV_CLOSURE_EVENT,相应的evcb_cb_unionevcb_callback
对非持久的eventevcb_closureEV_CLOSURE_EVENT_PERSIST,相应的evcb_cb_unionevcb_callback
(5). evcb_cb_union
参考上述.
(6). evcb_arg
自定义回调参数.

3.event_base对定时支持
(1). 提供通用结构,堆结构来容纳服务于定时的event
放置在通用结构的一组event,依赖一个内部的放置在堆结构的event实现分发处理.
(2). 对放置在通用结构的event
ev_timeout结构中tv_usec结构符合如下结构:
在这里插入图片描述
4比特位为tag,固定为0x5
中间8比特位用作索引.
20位为实际数值.
对持久的超时event,其用于时间间隔的ev_.ev_io.ev_timeout也是如此结构.
(3). event_base的事件循环会在每次io复用器分发后,依据当前时间点对超时的各个event执行分发处理.每次事件循环进入io阻塞等待前,也会保证最大等待时间不会超过当前时间点距离最近一个会超时event超时时间点的间隔.

4.io复用器对定时支持
io复用器在支持timerfd的情况下,允许利用timefd而非epoll_wait的超时参数的方式实现阻塞等待的超时唤醒.仅仅较新的linux内核支持此特性.

libevent中的信号机制

1.概述
前面我们分析eventevent_callbackevent_base时,立足于服务于套接字的event来展开分析的.
libevent中的event既可以用于支持对套接字的自动分法,手动分发.也可用于支持定时机制.还可用于支持信号处理.

这一部分立足于支持信号机制,来分析libeventevent,event_callback,event_base

2.服务于信号处理的event

struct event {
	struct event_callback ev_evcallback;
	/* for managing timeouts */
	union {
		TAILQ_ENTRY(event) ev_next_with_common_timeout;
		size_t min_heap_idx;
	} ev_timeout_pos;
	evutil_socket_t ev_fd;
	short ev_events;
	short ev_res;		/* result passed to event callback */
	struct event_base *ev_base;
	union {
		/* used for io events */
		struct {
			LIST_ENTRY (event) ev_io_next;
			struct timeval ev_timeout;
		} ev_io;
		/* used by signal events */
		struct {
			LIST_ENTRY (event) ev_signal_next;
			short ev_ncalls;
			/* Allows deletes in callback */
			short *ev_pncalls;
		} ev_signal;
	} ev_;
	struct timeval ev_timeout;
};

我们从信号处理视角分析各个字段.
(1). ev_evcallback
包含回调处理信息.
(2). ev_timeout_pos
服务于定时分发时,存储所在结构内位置信息.
(3). ev_fd
服务于信号处理时,这里存储要处理的信号编号.
(4). ev_events
服务于信号处理时,必然包含EV_SIGNAL.类似套接字event,信号处理event也支持持久,非持久概念.对持久的event必然包含EV_PERSIST
对非持久的服务于信号处理的event,因为信号产生被分发后,即从event_base移除.
对持久的服务于信号处理的event,信号产生被分发后,不会从event_base移除.除非显式请求.
(5). ev_res
服务于信号处理的event,因为信号产生而被分发时,这里包含EV_SIGNAL
(6). ev_base
指向关联到的event_base
(7). ev_
ev_signal中各个字段含义如下:
a. ev_signal_next
类似套接字event,服务于同一个信号编号的多个event,通过链式结构关联在一起.ev_signal_next用于构成链式结构.
b. ev_ncalls
信号event被分发时,允许设置一次分发时回调处理执行多次.这里记录回调处理要执行的次数.
c. ev_pncalls
这里安排这个指针的意义在于.
在信号event的回调处理进行中,让ev_pncalls指向ev_ncalls.如果再次期间,另一个用户将此eventevent_base移除.
移除时会通过ev_pncalls,设置其指向变量为0,从而使得当前信号处理回调可以尽快结束.

4.event_base事件循环对信号处理的支持

struct event_base {
	const struct eventop *evsigsel;
	struct evsig_info sig;
	struct event_signal_map sigmap;
};

我们立足于服务于信号处理的角度,截取event_base中相关字段并分析其含义.
(1). evsigsel
套接字event的自动分发依赖io复用器.信号event的分发也有自己的复用器来提供支持.
(2). sig

struct evsig_info {
	struct event ev_signal;
	evutil_socket_t ev_signal_pair[2];
	int ev_signal_added;
	int ev_n_signals_added;
#ifdef EVENT__HAVE_SIGACTION
	struct sigaction **sh_old;
#else
	ev_sighandler_t **sh_old;
#endif
	int sh_old_max;
};

这个结构来为event_base实现信号处理提供支持.其各个字段含义如下:
a. ev_signalev_signal_pair
我们需要一个独立的event来帮助实现信号event的分发.
event_base的初始化阶段,会创建一个pipe,得到两个套接字.并执行如下初始化:

event_assign(&base->sig.ev_signal, base, base->sig.ev_signal_pair[0], EV_READ | EV_PERSIST, evsig_cb, base);
static void evsig_cb(evutil_socket_t fd, short what, void *arg)
{
	static char signals[1024];
	ev_ssize_t n;
	int i;
	int ncaught[NSIG];
	struct event_base *base;
	base = arg;
	memset(&ncaught, 0, sizeof(ncaught));
	while (1) {
#ifdef _WIN32
		n = recv(fd, signals, sizeof(signals), 0);
#else
		n = read(fd, signals, sizeof(signals));// 读取pipe套接字数据
#endif
		if (n == -1) {
			int err = evutil_socket_geterror(fd);
			if (! EVUTIL_ERR_RW_RETRIABLE(err))
				event_sock_err(1, fd, "%s: recv", __func__);// 异常
			break;// 表示已经读取干净
		} else if (n == 0) {
			break;// 表示pipe套接字被关闭了
		}
		// 对读取内容逐个字节分析
		for (i = 0; i < n; ++i) {
			ev_uint8_t sig = signals[i];
			if (sig < NSIG)
				ncaught[sig]++;// 每个字节代表一个信号编号
		}
	}
	EVBASE_ACQUIRE_LOCK(base, th_base_lock);
	for (i = 0; i < NSIG; ++i) {
		// 允许特定信号的event被分发时,分发中执行多次回调处理
		if (ncaught[i])
			evmap_signal_active_(base, i, ncaught[i]);// 实现信号event分发.
	}
	EVBASE_RELEASE_LOCK(base, th_base_lock);
}

这样得到的效果时,若我们向使得某个信号编号下的各个event得到分发处理,我们只需向base->sig.ev_signal_pair[0]套接字写入信号编号即可.
b. ev_signal_added
0时表示依附的event_base上至少注册了一个服务于信号处理的event
c. ev_n_signals_added
记录依附的event_base上注册的服务于信号处理的event的数量.
d. sh_old,sh_old_max
当我们向event_base注册服务于某个信号编号的event时,会通过服务于信号处理的复用器执行信号处理函数安装.安装时,我们通过sh_old来记录此信号编号安装前的信号处理信息.这样,当此信号编号的最后一个eventevent_base移除时,我们需要借助sh_old中的信号处理信息来恢复.

5.服务于信号处理的复用器

// 专门服务于信号分发的复用器
static const struct eventop evsigops = {
	"signal",
	NULL,
	evsig_add,
	evsig_del,
	NULL,
	NULL,
	0, 0, 0
};

当我们最初向event_base添加某个信号编号的event时,将执行一次evsig_add.当我们将某个信号编号的最后一个eventevent_base移除时,将执行一次evsig_del
a. evsig_add

// 代表开始监控某个信号
static int evsig_add(struct event_base *base, evutil_socket_t evsignal, short old, short events, void *p)
{
	struct evsig_info *sig = &base->sig;
	(void)p;
	EVUTIL_ASSERT(evsignal >= 0 && evsignal < NSIG);
	EVSIGBASE_LOCK();
	// 这意味着至少两个event_base各自在监控信号event
	if (evsig_base != base && evsig_base_n_signals_added) {
		event_warnx("Added a signal to event base %p with signals "
			"already added to event_base %p.  Only one can have "
			"signals at a time with the %s backend.  The base with "
			"the most recently added signal or the most recent "
			"event_base_loop() call gets preference; do "
			"not rely on this behavior in future Libevent versions.",
			base, evsig_base, base->evsel->name);
	}
	// 通过静态结构记录最近负责信号event监控的event_base.
	// 及其上监控的信号数目.
	evsig_base = base;
	evsig_base_n_signals_added = ++sig->ev_n_signals_added;
	// 通过pipe的套接字实现分发通知
	evsig_base_fd = base->sig.ev_signal_pair[1];
	EVSIGBASE_UNLOCK();
	event_debug(("%s: %d: changing signal handler", __func__, (int)evsignal));
	// 信号处理设置
	if (evsig_set_handler_(base, (int)evsignal, evsig_handler) == -1) {
		goto err;
	}
	if (!sig->ev_signal_added) {
		if (event_add_nolock_(&sig->ev_signal, NULL, 0))
			goto err;
		sig->ev_signal_added = 1;
	}
	return (0);
err:
	EVSIGBASE_LOCK();
	--evsig_base_n_signals_added;
	--sig->ev_n_signals_added;
	EVSIGBASE_UNLOCK();
	return (-1);
}

上述过程主要为:
为信号编号安装信号处理.evsig_set_handler_处理中会安装信号处理,同时将此信号编号之前的信息处理保存起来.
这样,后续此信号产生时,将执行一次evsig_handler
evsig_handler中则通过向evsig_base_fd写入此信号编号内容.由于sig->ev_signal的作用.这将引发event_base后续对此信号相关的所有event执行一次分发.
b. evsig_del

static int evsig_del(struct event_base *base, evutil_socket_t evsignal, short old, short events, void *p)
{
	EVUTIL_ASSERT(evsignal >= 0 && evsignal < NSIG);
	event_debug(("%s: "EV_SOCK_FMT": restoring signal handler", __func__, EV_SOCK_ARG(evsignal)));
	EVSIGBASE_LOCK();
	--evsig_base_n_signals_added;
	--base->sig.ev_n_signals_added;
	EVSIGBASE_UNLOCK();
	return (evsig_restore_handler_(base, (int)evsignal));
}

当某个信号编号的最后一个eventevent_base移除时,会执行一次evsig_del
evsig_del会更新变量,并借助evsig_restore_handler_恢复此信号编号之前的信号处理.

6.最佳实践
因为linux多线程应用中,发给应用的信号只会传递给一个线程.激发一次信号处理.
所以相应的借助event_baseevent实现信号处理时,我们应该:
(1). 将服务于信号处理的event集中注册到一个event_base
libevent中相应的设置了以下局部静态变量来存储此event_base及关联信息.

#ifndef EVENT__DISABLE_THREAD_SUPPORT
static void *evsig_base_lock = NULL;
#endif
static struct event_base *evsig_base = NULL;
static int evsig_base_n_signals_added = 0;
static evutil_socket_t evsig_base_fd = -1;

libevent中通信对象的流量控制

1.流量控制初始化
流控初始化时会设置好时间点,最大单次允许量,速率,间隔单位.
2.读,写操作前更新并获取允许流量
以读为例,每次读时,依据上次余量,距离上次读取间隔,速率,最大单次允许量可以计算出本次操作允许量.从而控制本次操作时,允许读取的量,进而达到流控效果.
3.读,写操作后再次更新允许流量
以读为例,每次读后,依据读取量,上次余量重新计算余量.在余量降低到0时候,会通过移除可读事件来临时禁止后续读取操作.并同时启动一个定时event,在定时回调中重新计算余量.在余量大于0时,重新恢复可读事件,并取消定时event

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/525861.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2024.4.4-day09-CSS 布局模型(标准流模型、浮动模型)

个人主页&#xff1a;学习前端的小z 个人专栏&#xff1a;HTML5和CSS3悦读 本专栏旨在分享记录每日学习的前端知识和学习笔记的归纳总结&#xff0c;欢迎大家在评论区交流讨论&#xff01; 文章目录 作业 2024.4.4-学习笔记1 CSS 布局模型1.1 标准流1.2 CSS 浮动1.3 去除塌陷 2…

golang es查询的一些操作,has_child,inner_hit,对索引内父子文档的更新

1.因为业务需要查询父文档以及其下子文档&#xff0c;搞了很久才理清楚。 首先还是Inner_hits,inner_hits只能用在nested,has_child,has_parents查询里面 {"query": {"nested": {"path": "comments","query": {"match…

QMC5883芯片I2C驱动开发指南

这个芯片纯国产挺好用的&#xff0c;电路很好设计&#xff0c;我这垃圾焊功&#xff0c;纯手焊&#xff0c;&#xff0c;居然能用。 第一部分 硬件连接 画的很简陋&#xff0c;看看就可以了。 第二部分 软件驱动 I2C的具体时序实现需要自己搞定&#xff01;&#xff01; 2…

【工作实践-10】uniapp打包前注意事项

1.代码是否为最新代码 当前所要打包的分支是否已包含各个分支最新代码&#xff0c;是否是最新版。 2.APP版本是否需要提升 若APP用于上架&#xff0c;每次更改APP版本需要提升。依据版本规范来提升版本号。 3.APP启动界面配置 4.APP打包模块配置是否已配置好所需功能&#x…

Node.js创建第一个web服务

如果用PHP来编写后端代码&#xff0c;需要用Apache或者Nginx的服务器,来处理客户的请求响应。对于Node.js时&#xff0c;不仅实现了应用&#xff0c;同时还实现了整个HTTP服务器. 安装 Node Snippets插件&#xff08;编程自带提示&#xff09; console.log(你好nodejs); //表…

【漏洞复现】泰博云平台 solr SSRF漏洞

0x01 产品简介 泰博云平台,就是指以电商集群的方式,通过供应链有效连接组成“商务云”生态系统,在产品、服务、营销推广等方面实现资源共享,“物”就是线下实体店网络,以众包模式,将行业制造商、分销商、零售商,和提供本土化设计、物流、安装的优质服务商,纳入统一的云…

Vue项目打包成exe文件(electron)

1.将写好的vue项目打包 1.1运行vue ui命令 输出目标文件 如果打开index.html是空白的&#xff0c;而且控制台报错获取xxx资源失败的问题&#xff0c;你需要在vue.config.js 上加一个命令&#xff0c;如果没有你需要创建一个。 2.下载electron官方示例 git clone https://gith…

华为ensp中PPP(点对点协议)中的PAP认证 原理和配置命令

作者主页&#xff1a;点击&#xff01; ENSP专栏&#xff1a;点击&#xff01; 创作时间&#xff1a;2024年4月8日14点31分 PPP协议&#xff08;Point-to-Point Protocol&#xff09;是点到点协议&#xff0c;是一种常用的串行链路层协议&#xff0c;用于在两个节点之间建立点…

2024年4月7日16:58:09答辩笔记

尚硅谷总结毕业设计编写&#xff1a;&#xff08;ppt尽量好看点&#xff0c;放图&#xff08;流畅图&#xff0c;时序图放一放&#xff09;&#xff0c;少字&#xff0c;&#xff09; 总结&#xff1a;&#xff08;这样给人体验感要好&#xff0c;语言、逻辑清晰&#xff09; 1…

【LeetCode热题100】118. 杨辉三角(动态规划)

一.题目要求 给定一个非负整数 numRows&#xff0c;生成「杨辉三角」的前 numRows 行。 在「杨辉三角」中&#xff0c;每个数是它左上方和右上方的数的和。 二.题目难度 简单 三.输入样例 示例 1: 输入: numRows 5 输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]] 示…

笔记 | 编译原理L1

重点关注过程式程序设计语言编译程序的构造原理和技术 1 程序设计语言 1.1 依据不同范型 过程式(Procedural programming languages–imperative)函数式(Functional programming languages–declarative)逻辑式(Logical programming languages–declarative)对象式(Object-or…

Python基础教程:网络爬虫的工作原理

网络爬虫是一种数据收集的方式&#xff0c;广泛用于搜索引擎、市场分析等领域。 爬虫从一个或若干种子页面开始&#xff0c;获得种子页面上的链接&#xff0c;并根据需求来追踪其中的一些链接&#xff0c;达到遍历所有网页的目的。在抓取网页的过程中&#xff0c;一方面提取需…

「漫画」数据工程师面试常见问题之数据倾斜

话说&#xff0c;闹钟一响&#xff0c;现实照进梦想&#xff0c;又是李大虎面试找工作的一天。 李大虎心里一直有个想法&#xff0c;如果一天睡20个小时&#xff0c;然后这20个小时全做美梦&#xff0c;醒来的4个小时用来吃喝拉撒&#xff0c;这样岂不就和那些富二代一样了&am…

【机器学习入门】集成学习之梯度提升算法

系列文章目录 第1章 专家系统 第2章 决策树 第3章 神经元和感知机 识别手写数字——感知机 第4章 线性回归 第5章 逻辑斯蒂回归和分类 第5章 支持向量机 第6章 人工神经网络(一) 第6章 人工神经网络(二) 卷积和池化 第6章 使用pytorch进行手写数字识别 实操练习 使用Yolo模型进…

机器视觉系统光源的分类及选择

机器视觉系统 机器视觉系统是一种基于计算机视觉技术的智能系统&#xff0c;主要用于对图像或视频进行分析、处理和识别。一个典型的机器视觉系统通常由以下几个组成部分构成&#xff1a; 光源 机器视觉光源是机器视觉系统的关键组成部分之一。机器视觉光源直接影响图像质量&am…

Qt:窗口、按钮类、行编辑器、标签类

作业&#xff1a;QQ登录界面 mywidget.h #ifndef MYWIDGET_H #define MYWIDGET_H#include <QWidget> #include <QIcon> #include<QMovie> #include <QLabel> #include <QPushButton> #include <QLineEdit> class MyWidget : public QWid…

jest单元测试——项目实战

jest单元测试——项目实战 一、纯函数测试二、组件测试三、接口测试四、React Hook测试&#x1f4a5; 其他的疑难杂症另&#xff1a;好用的方法 &#x1f31f; 温故而知新&#xff1a;单元测试工具——JEST 包括&#xff1a;什么是单元测试、jest的基本配置、快照测试、mock函数…

加州大学欧文分校英语基础语法专项课程02:Questions, Present Progressive and Future Tenses 学习笔记

Questions, Present Progressive and Future Tenses Course Certificate 本文是学习 Questions, Present Progressive and Future Tenses 这门课的学习笔记&#xff0c;如有侵权&#xff0c;请联系删除。 文章目录 Questions, Present Progressive and Future TensesWeek 01: …

大语言模型(LLM)为什么会产生幻觉?

一、幻觉的概念 在大语言模型&#xff08;LLM&#xff09;的语境之下&#xff0c;“幻觉”&#xff08;hallucination&#xff09;指的是模型在没有足够证据支持的情况下&#xff0c;生成的错误或虚构的信息。这种现象在自然语言处理&#xff08;NLP&#xff09;任务中尤其突出…

2024年MathorCup妈妈杯数学建模思路C题思路解析+参考成品

1 赛题思路 (赛题出来以后第一时间在群内分享&#xff0c;点击下方群名片即可加群) 2 比赛日期和时间 报名截止时间&#xff1a;2024年4月11日&#xff08;周四&#xff09;12:00 比赛开始时间&#xff1a;2024年4月12日&#xff08;周五&#xff09;8:00 比赛结束时间&…