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_SIGACTIONstruct sigaction **sh_old;
#elseev_sighandler_t **sh_old;
#endifint 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 _WIN32n = recv(fd, signals, sizeof(signals), 0);
#elsen = read(fd, signals, sizeof(signals));// 读取pipe套接字数据
#endifif (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各自在监控信号eventif (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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

c# wpf datagrid 简单试验

1.概要 datagrid 一个列表类的控件 2.代码 <Window x:Class"WpfApp2.Window3"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d"http://schemas.mic…

MySOL之旅--------MySQL数据库基础( 3 )

本篇碎碎念:要相信啊,胜利就在前方,要是因为一点小事就停滞不前,可能你也不适合获取胜利,成功的路上会伴有泥石,但是走到最后,你会发现身上的泥泞皆是荣耀的勋章! 今日份励志文案: 凡是发生皆有利于我 目录 查询(select) 1.全列查询 2.指定列查询 3.查询字段为表达式 ​编…

嵌入式中常用的巧妙方法 - (汇总)

概述 做项目&#xff0c;掌握以下方法&#xff0c;可提高开发效率&#xff0c;把时间全部放在需求上。 1、快速获取结构体成员大小 #include <stdio.h> // 获取结构体成员大小 #define GET_MEMBER_SIZE(type, member) sizeof(((type*)0)->member)// 获取结构体成…

SNRO 编号范围对象管控,唯一ID

事务代码:SNRO 代码引用: DATA: MAXTID TYPE I,NEWNO TYPE CHAR8. CALL FUNCTION NUMBER_RANGE_ENQUEUE EXPORTING OBJECT ZQC57 EXCEPTIONS FOREIGN_LOCK 1 OBJECT_NOT_FOUND 2 SYSTEM_FAILURE 3 OTHERS …

Flutter学习13 - Widget

1、Flutter中常用 Widget 2、StatelessWidget 和 StateFulWidget Flutter 中的 widget 有很多&#xff0c;但主要分两种&#xff1a; StatelessWidget无状态的 widget如果一个 widget 是最终的或不可变的&#xff0c;那么它就是无状态的StatefulWidget有状态的 widget如果一个…

使用echarts控件,小程序分包处理上传失败

原因&#xff1a;在小程序中想要使用echarts控件&#xff0c;但是上传代码失败&#xff0c;错误码为主包超过2.5M&#xff0c;基于此&#xff0c;我们使用分包处理&#xff0c;并上传版本。 错误&#xff1a; 使用echarts&#xff1a;在小程序中引入echarts相关文件。 解决方式…

Arduino 项目笔记 |TH1621 LCD液晶显示屏驱动(SSOP-24封装)

LCD液晶屏资料 LCD液晶屏资料 重要参数&#xff1a; 工作电压&#xff1a; 3V可视角度&#xff1a;1201/4 &#xff0c;1/3 TH1621 驱动 HT1621 LCD控制驱动芯片介绍 VLCD 和 VCC 电压符合规格书&#xff0c;最好都取3.3V 。电压太高或太低都会出现段码液晶屏乱码的情况&am…

vue 原理【详解】MVVM、响应式、模板编译、虚拟节点 vDom、diff 算法

vue 的设计模式 —— MVVM M —— Model 模型&#xff0c;即数据V —— View 视图&#xff0c;即DOM渲染VM —— ViewModel 视图模型&#xff0c;用于实现Model和View的通信&#xff0c;即数据改变驱动视图渲染&#xff0c;监听视图事件修改数据 初次渲染 将模板编译为 render …

【Mybatis-Plus】Mybatis-Plus增删改查示例

示例一&#xff1a;delete 这个删除&#xff0c;是我们直接可以把这条记录给放进去&#xff0c;那么这条记录里面如果说有的属性为空的话&#xff0c;它是不会去管的&#xff0c;但是有些属性它不为空的话&#xff0c;那么它就会根据属性。作为一个equal的条件去做一个删除的一…

msvcr120.dll丢失的解决办法,msvcr120.dll文件丢失的相关介绍

今天小编在使用电脑的时候&#xff0c;突然弹出了一个关于msvcr120.dll文件丢失的问题&#xff01;当出现这种情况时&#xff0c;相信大家都会感到困惑吧&#xff1f;不过&#xff0c;别担心&#xff01;下面就来和大家分享一下解决msvcr120.dll文件丢失问题的几种方法&#xf…

城市内涝与海绵城市规划设计中的水文水动力模拟

原文链接&#xff1a;城市内涝与海绵城市规划设计中的水文水动力模拟https://mp.weixin.qq.com/s?__bizMzUzNTczMDMxMg&mid2247601198&idx5&sn35b9e5e3961ea2f190f9742236a7217f&chksmfa820dc9cdf584df97633f64d19bdc3e5f7d1a5a85000c8f040e1953c51b9b39c87b5…

Docker 集成 redis,并在nacos进行配置时需要注意点

安装redis镜像 docker pull redis:6.0.6redis配置文件 创建相关配置文件 mkdir /apps/redis cd /apps/redis touch redis.conf vim redis.confredis.conf内容&#xff1a; #开启保护 protected-mode yes #开启远程连接 bind 0.0.0.0 #自定义密码 port 6379 timeout 0 # 900s内…

Go语言中channel和互斥锁的应用场景

面对一个并发问题,我们的解决方案是使用channel还是互斥锁来实现并不总是很清晰。因为Go提倡使用通信来共享内存,所以一个常见的错误就是总是强制使用channel,不管实际情况如何。但是我们应该把这两种选择作为互补手段。 首先,简单回顾一下Go语言中的channel:channel是一种交…

DL00295-基于AirSim仿真环境的无人机深度强化学习算法路径规划完整实现含详细说明文档

-创建了一个开放的AI Gym环境&#xff0c;包括多旋翼和固定翼无人机的运动学模型。 -提供了一些UE4环境来训练和测试深度强化学习DRL导航策略。 -基于AirSim和SB3。 完整代码链接见文末。 DL00295-基于AirSim仿真环境的无人机深度强化学习算法路径规划完整实现含详细说明文档

Linux-docker安装数据库redis

1.拉取redis镜像 docker pull redis # 下载最新的redis版本 docker pull redis:版本号 # 下载指定的redis版本ps&#xff1a;我这是已经下载最新版本的redis 2.查看redis镜像 docker images3.创建挂在路径并授权 mkdir -p /usr/local/redis/data mkdir -p /usr/local…

AI大模型基石:文字与数字的起源与演变

AI大模型基石&#xff1a;文字与数字的起源与演变 1、文字 1.1、起源 我们的祖先在还没有发明文字和语言之前就已经开始使用“咿咿呀呀”的声音来传播信息了&#xff0c;比如在野外活动遇到危险&#xff0c;然后发出“咿咿呀呀”的声音来提醒同伴小心&#xff0c;同伴在接收到…

向大众日报投稿需要准备哪些材料?

向大众日报投稿通常需要准备以下材料&#xff1a; 稿件正文&#xff1a;这是投稿的核心部分&#xff0c;确保内容符合大众日报的主题和风格。作者信息&#xff1a;包括姓名、联系方式&#xff08;如电话、邮箱等&#xff09;。个人简介&#xff1a;简要介绍自己的背景和相关经…

数据结构--链式栈

一.链式栈的栈顶在哪里? 二.链栈的结构: typedef struct LSNode{ int data; struct LSNode* next; }LSNode ,*PLStack; //链栈的节点.由于栈顶在第一个数据节点,所以不需要top指针 三.链式栈的实现: //初始化LSNode* p (LSNode*)malloc(sizeof(LSNode));assert(p ! NULL)…

03-JAVA设计模式-享元模式

享元模式 什么是享元模式 享元模式&#xff08;Flyweight Pattern&#xff09;是一种对象结构型设计模式&#xff0c;用于减少创建对象的数量&#xff0c;以减少内存占用和提高系统性能。它通过共享已经存在的对象来避免创建大量相似的对象&#xff0c;从而降低内存消耗。 在…

蓝桥杯真题Day48 倒计时5天 练了几道真题小程序+回溯剪枝应用一个小程序

[蓝桥杯 2023 省 A] 更小的数 题目描述 小蓝有一个长度均为 n 且仅由数字字符 0∼9 组成的字符串&#xff0c;下标从0到 n−1&#xff0c;你可以将其视作是一个具有n位的十进制数字num&#xff0c;小蓝可以从num 中选出一段连续的子串并将子串进行反转&#xff0c;最多反转一次…