libevent源码解析--evbuffer_chain,evbuffer,bufferevent,bufferevent_private

news2024/11/24 14:34:09

1.概述
前面我们已经分析了eventevent_callbackevent_base及监听套接字处理。
有了event_base我们便可实现事件监控,事件分发处理。
有了监听套接字处理,我们便可实现服务端监听,通过accept得到服务端通信套接字。

一个网络库核心功能由客户端,服务端组成。
我们要实现网络库,除了上述设施,还需通信对象,客户端。
围绕通信对象主要有以下功能:
(1). 创建通信对象并对其初始化。
(2). 通信对象可以用来实现套接字上io事件的管理,io事件的处理。
(3). 借助通信对象提供的接口,我们可以实现发送数据。
(4). 借助通信对象提供的接口设置和获取其属性信息。
(5). 借助通信对象提供的接口关闭连接。
(6). 释放通信对象。
上述功能更多是通信对象通用的能力。

为了借助通信对象实现具体的逻辑功能。我们还需要能够为其指定事件处理函数,收包回调函数。
有了这些回调函数,内部再使得在连接建立,连接断开,连接错误时触发我们的事件处理函数,收取完整数据包时触发我们的收包回调函数,我们便可借助此通信对象实现具体的逻辑处理。

针对客户端,除了要具备一个通信对象的能力,还得具有主动发起连接,获取连接状态的能力。
这里,我们分析libevent中的通信对象与客户端。

2.结构
2.1.evbuffer_chain

struct evbuffer_chain {
	struct evbuffer_chain *next;
	size_t buffer_len;
	ev_misalign_t misalign;
	size_t off;
	unsigned flags;
#define EVBUFFER_REFERENCE	0x0004	
#define EVBUFFER_DANGLING	0x0040
	int refcnt;
	unsigned char *buffer;
};

在这里插入图片描述
我们这里讨论通信对象,libevent中每个服务于套接字的通信对象:
(1). 持有一个发送缓存区用于暂时缓存用户执行send发送的数据,在可写事件处理中会执行异步发送完成数据的实际发送。
(2). 持有一个接收缓存区用于暂时缓存可读实际中执行recv收取的套接字上的数据,在可读回调中提供给上层以便供其处理,处理后再消耗掉。

无论是发送缓存区,还是接收缓存区基本的组成单元是上图所示结构。
可以将上述结构成为尺寸可变内存块,代表一片连续可用内存空间。由管理区域,数据区域两部分组成。
管理区域就是一个evbuffer_chain 类型实例。参考上图其结构各个字段含义如下:
(1). next
我们说上述只是构成缓存区的一个单元,多个这样的单元构成的链式结构组成缓存区。通过next形成链式结构。
(2). buffer_len
数据区域容量。
(3). misalign
无效部分尺寸。
(4). off
有效数据部分尺寸。
(5). flags
标志信息。
(6). buffer
指向数据区域起始位置。

由于上述组成单元要么用于实现发送缓存区,要么用于实现接收缓存区。
我们分别讨论用于发送缓存区下数据区域变迁:
(1). 接收缓存区
假设我们的发送缓存区只含有一个evbuffer_chain 代表的固定尺寸块。
a. 初始时刻–分配了一个容量为sizeof(evbuffer_chain)+1000个固定尺寸块。
在这里插入图片描述
此时buffer_len1000misalign0off0,整个数据区域均可用于接收来自recv获得的套接字数据。
b. 处理可读事件,执行recv向其中放入600字节数据
在这里插入图片描述
此时buffer_len1000misalign0off600,整个数据区域还剩400字节空间可用于继续接收来自recv获得的套接字数据。
c. 执行了上层回调,假设600个字节由一个尺寸为400字节的包,和一个尺寸300字节的包构成。
上层回调执行中对数据区域构成一个完成包的部分会触发包的处理逻辑,处理完毕后会将此部分消耗掉。
在这里插入图片描述
此时buffer_len1000misalign400off200,整个数据区域还剩400字节空间可用于继续接收来自recv获得的套接字数据。
这样我们就分析了作为发送缓存区组成单元时,块内三类区域的变迁过程。

(2). 发送缓存区
a. 初始时刻–分配了一个容量为sizeof(evbuffer_chain)+1000个固定尺寸块。
在这里插入图片描述
此时buffer_len1000misalign0off0,整个数据区域均可用于接收来自send调用中的待发送数据。
b. 用户通过send向发送缓存区写入一个尺寸为600的包
在这里插入图片描述
此时buffer_len1000misalign0off600,整个数据区域还剩400字节空间可用于继续接收来自send提供的应用数据。
c. libevent执行可写事件处理,将发送缓存区内尺寸为400的有效数据写入到了套接字内核缓存区。
在这里插入图片描述
此时buffer_len1000misalign400off200,整个数据区域还剩400字节空间可用于继续接收来自send提供的应用数据。

2.2.evbuffer

struct evbuffer {
	struct evbuffer_chain *first;
	struct evbuffer_chain *last;
	struct evbuffer_chain **last_with_datap;
	size_t total_len;
	size_t max_read;
	size_t n_add_for_cb;
	size_t n_del_for_cb;
#ifndef EVENT__DISABLE_THREAD_SUPPORT
	void *lock;
#endif
	unsigned own_lock : 1;
	unsigned deferred_cbs : 1;
	ev_uint32_t flags;
	struct event_base *cb_queue;
	int refcnt;
	struct event_callback deferred;
	LIST_HEAD(evbuffer_cb_queue, evbuffer_cb_entry) callbacks;
	struct bufferevent *parent;
};

libevent中的每个通信对象持有一个发送缓存区,一个接收缓存区。
每个缓存区用一个evbuffer 实例来描述。
其各个字段含义如下:
(1). first
指向组成缓存区的首个固定尺寸块。
(2). last
指向组成缓存区的末个固定尺寸块。
(3). last_with_datap
libevent中出于灵活性考量,设想了很多由固定尺寸块构成的链式结构场景。
比如发送缓存区下,随着用于陆续执行send,我们分配了10个块构成的链式结构来容纳数据,此后,可写事件处理中,执行异步发送将前3个块内的数据发送出去了,但块并未从链式结构移除,此时我们又希望能快速找到链式结构上首个含有效数据的块。此时需借助last_with_datap
接收缓存区下,随着陆续执行recv,我们分配了10个块构成的链式结构来容纳数据,此后,上层回调处理中,将前3个块内的数据消耗掉了,但块并未从链式结构移除,此时我们又希望能快速找到链式结构上首个含有效数据的块。此时需借助last_with_datap

这个字段是对最后含有效数据的块的前一块的next字段取地址后的结果。
(4). total_len
由于缓存区有多个块组成,所以我们需要一个额外字段记录构成缓存区的所有块内有效数据尺寸之和。
(5). max_read
用于接收缓存区时,用于限制一次recv最多可向缓存区放入的字节尺寸。
(6). n_add_for_cb,n_del_for_cb
libevent支持每次我们向缓存区写入新数据或对缓存区执行消耗动作后,借助event_callback,在event_base的事件循环处理中集中触发一次外部提供的应用层回调。
n_add_for_cb,n_del_for_cb将作用应用层回调的参数。用于告知两次回调间缓存区内数据增加量,减少量。
(7). lock,own_lock
当我们讨论通信对象持有的发送缓存区,接收缓存区时,缓存区对象总是借助隶属的通信对象的可递归互斥锁实现互斥保护。
所以,这里lock将指向隶属的通信对象持有的可递归的互斥锁。own_lock将为0
(8). deferred_cbs,deferred
前面说了,libevent中允许在我们向缓存区放入新数据,从缓存区消耗了数据时,引发指定的上层回调。
引发方式有两种:
a. 一种是在对缓存区操作后立即引发。此时deferred_cbs0deferred不需要设置。
b. 一种是在对缓存区操作后异步引发。此时deferred_cbs1deferred需要提前设置。借助手动分发deferred实现event_base事件循环后续执行这些异步引发的上层回调。

作为套接字发送缓存区使用时,可以借助这类机制实现通信对象的可写监控在发送缓存区存在有效数据时注册到event_base,在发送缓存区无有效数据时从event_base移除。
作为套接字接收缓存区使用时,暂时没发现需使用这个机制的地方。
(9). flags
标志信息。
(10). cb_queue
隶属的通信对象所关联到的event_base
(11). parent
隶属的通信对象
(12). callbacks
用于收集上层回调。

2.3.bufferevent
前面的evbuffer_chainevbuffer用于实现发送缓存区,接收缓存区,而bufferevent则用于实现通信对象。

struct bufferevent {
	struct event_base *ev_base;
	const struct bufferevent_ops *be_ops;
	struct event ev_read;
	struct event ev_write;
	struct evbuffer *input;
	struct evbuffer *output;
	bufferevent_data_cb readcb;
	bufferevent_data_cb writecb;
	bufferevent_event_cb errorcb;
	void *cbarg;
	short enabled;
};

其各个字段含义如下:
(1). ev_base
此通信对象所关联到的event_base。每个event_base会独占一个线程执行事件循环。通信对象的事件监控,事件处理,异步回调均放在关联event_base的事件循环中进行。
(2). be_ops
包含为通信对象提供支持的一组操作集合。

struct bufferevent_ops {
	// 名称
	const char *type;
	// bufferevent 可视为通信对象基础类型,一般作为更高层次类型的字段。
	// 这里表示作为某类型字段存在时,bufferevent字段距离依附类型实例起始地址的偏移量。
	off_t mem_offset;
	// 通过此方法向关联event_base注册指定类型事件
	int (*enable)(struct bufferevent *, short);
	// 通过此方法向关联event_base取消注册指定类型事件
	int (*disable)(struct bufferevent *, short);
	// 在通信对象释放阶段1执行此操作--异步释放发起
	void (*unlink)(struct bufferevent *);
	// 在通信对象释放阶段2执行此操作--异步回调中
	void (*destruct)(struct bufferevent *);
	// 允许通过此方法来控制通信对象,比如获取或设置其属性
	int (*ctrl)(struct bufferevent *, enum bufferevent_ctrl_op, union bufferevent_ctrl_data *);
};

(3). ev_read
针对服务于套接字的通信对象,这个event用于代表可读事件对应的event
前面介绍event时,可知event是外部向event_base注册事件监控的载体。依附其的event_callback则提供了如何对事件执行处理的回调函数。
(4). ev_write
针对服务于套接字的通信对象,这个event用于代表可写事件对应的event
(5). input
代表了隶属于通信对象的接收缓存区。
(6). output
代表了隶属于通信对象的发送缓存区。
(7). readcb
前面讨论通信对象时候说了,通信对象需要在收取到套接字上数据后触发上层回调。以便应用层对已经接收数据进行反向序列化,获得数据包,对数据包进行逻辑处理,以便实现网络逻辑功能。
这个readcb就是上层提供的收包回调。
(8). writecb
libevent允许提供此回调,以便发送缓存区有效数据不足时,给上层一个通知处理时机。一般而言,不需要提供此回调。我们可直接通过通信对象的send接口实现数据发送。
(9). errorcb
允许用户提供此回调。在异步连接建立成功,连接建立失败,连接断开,产生错误事件时均触发此回调。以便应用层可针对性进行事件处理。
(10). cbarg
允许提供自定义数据。每次触发readcb,writecb,errorcb回调时会原样提供此自定义数据。
为了保证回调期间自定义数据有效性,提供者需保证,先释放通信对象,再释放自定义数据。
通信对象异步释放下,只有在异步回调之后释放自定义数据才被认为是安全的。
(11). enabled
用于表示通信对象上此时支持的事件类型。目前只能是EV_READ,EV_WRITE
只有一个事件类型先被支持,才有可能被注册到关联的event_base

2.4.bufferevent_private

struct bufferevent_private {
	struct bufferevent bev;
	unsigned own_lock : 1;
	unsigned readcb_pending : 1;
	unsigned writecb_pending : 1;
	short eventcb_pending;
	int errno_pending;
	struct event_callback deferred;
	enum bufferevent_options options;
	
	bufferevent_suspend_flags read_suspended;
	bufferevent_suspend_flags write_suspended;
	
	unsigned connecting : 1;
	unsigned connection_refused : 1;
	
	int refcnt;
	void *lock;
	ev_ssize_t max_single_read;
	ev_ssize_t max_single_write;
	union {
		struct sockaddr_in6 in6;
		struct sockaddr_in in;
	} conn_address;
};

前面说了bufferevent一般看成通信对象基础类型。
bufferevent_private 则构成了libevent实现套接字通信的通信对象完全体。
其各个字段含义:
(1). bev
依附于其的bufferevent对象,用于实现通信对象基础功能。
(2). own_lock
表示此对象是否拥有互斥锁。通信对象可自己拥有,也可采用隶属的更高层对象的互斥锁。
对我们分析的服务于套接字通信的bufferevent_private,其拥有互斥锁。故own_lock1
(3). readcb_pendingwritecb_pendingeventcb_pendingerrno_pending
bufferevent中允许使用者提供readcbwritecberrorcb三类回调。
在通信对象内部需要触发上述三类回调的场景,就我们分析的服务于套接字通信的bufferevent_private 而言,总是在需要时,直接触发回调即可。

libevent出于灵活及可扩展考量,还是提供了另外一种异步延迟触发回调的方式。
要使用异步延迟触发回调必须:
a. bufferevent_privateoptions包含BEV_OPT_DEFER_CALLBACKS。表示在对象层面支持这种行为。
b. libevent内部需触发应用回调处,必须执行触发函数时通过参数的options包含BEV_OPT_DEFER_CALLBACKS,来表示希望以异步延迟方式触发回调。

readcb_pending,writecb_pending ,eventcb_pending为延迟异步回调机制提供支持。来在引发阶段记录下那些异步回调被引发了。
错误类型事件回调异步引发时,需要在回调参数提供套接字错误码,errno_pending在引发阶段记录了错误码信息。
(4). deferred
异步回调机制使用时,deferred为其提供支持。
引发异步回调,即手动分发此event_callback对象到event_base,以便event_base事件循环中后续执行其处理函数。
其处理函数中再依据readcb_pending,writecb_pending ,eventcb_pending的设置,决定引发何种类型的回调。
(5). read_suspended
通信对象提供了临时禁止从套接字继续读取新数据及产生新的可读事件的机制。
当读取被禁止时,read_suspended里包含了禁止的原因信息。当这些原因解除后,就可解除禁止。
(6). write_suspended
通信对象提供了临时禁止向套接字内核发送缓存区继续写入新数据及产生新的可写事件的机制。
当写入被禁止时,write_suspended里包含了禁止的原因信息。当这些原因解除后,就可解除禁止。
(7). connecting
当我们通过connect接口发出连接请求,但连接过程尚未结束期间。connecting 将为1
(8). connection_refused
异步连接中收到对端拒绝提示时被设置为1。这样异步连接可写处理中将知道对端拒绝我们的连接请求。
(9). lock
指向持有的互斥锁对象。
(10). max_single_read,max_single_write
用于一次readwrite系统调用最大可操作数据尺寸进行限制。
(11). conn_address
这里是通信对象所连接的另一端的地址信息。

2.5.服务于套接字通信的bufferevent使用的be_ops

const struct bufferevent_ops bufferevent_ops_socket = {
	// 名称
	"socket",
	// bufferevent字段在依附的bufferevent_private中距离实例起始地址偏移量
	evutil_offsetof(struct bufferevent_private, bev),
	// 用于实现向关联的event_base注册指定类型event
	be_socket_enable,
	// 用于实现向关联的event_base取消注册指定类型event
	be_socket_disable,
	// 通信对象释放阶段1操作--异步释放发起时
	NULL, /* unlink */
	// 通信对象释放阶段2操作--如关闭关联的套接字
	be_socket_destruct,
	// 用于设置或获取通信对象的属性
	be_socket_ctrl,
};

3.功能
3.1.作为客户端的通信对象使用流程
3.1.1.创建通信对象
此步骤执行的关键步骤为:
(1). 为客户端分配一个bufferevent_private实例对象.
(2). 为此实例对象执行初始化.初始化参考上述对其各个字段含义的分析.
初始化过程指的注意的是:
a. 会为bufferevent_private动态分配一个evbuffer用于其发送缓存区,动态分配一个evbuffer用于其接收缓存区.
b. 会初始化其ev_readev_write两个event.这两个event的标志为EV_READ|EV_PERSIST|EV_FINALIZE.表示分别服务于可读事件,可写事件.且是持久的.EV_FINALIZE使得默认下执行event_del不会阻塞.
c.初始化过程为服务于发送缓存区的outbuf添加了一个回调对象.用于在发送缓存区内容从无变有时,检测若可写事件是支持的且未被禁止时,自动向关联的event_base添加可写event注册.
d. 对象初始化是enabled字段设置的是EV_WRITE.因为客户端异步连接依赖可写事件监控来完成异步连接结束处理.

3.1.2.设置通信对象上层回调
客户端要实现事件处理,收包处理,发送缓存区有效内容不足时处理,需设置好相应的上层回调函数,及回调时所用的自定义参数.

3.1.3.向关联event_base注册事件监控
为了使得关联的event_base可以在其事件循环里帮我们监控套接字上的事件,及在事件产生时分发对应的event以便后续执行依附其的event_callback上的回调处理.我们需通过接口让通信对象向关联event_base注册指定类型的event.对客户端,一般同时需要注册可读,可写类型的event.但如果此阶段bufferevent_private所关联的套接字为-1,注册时会忽略.

3.1.4.发起连接
我们可以通过接口使得bufferevent发起到某个地址对象的连接.其执行过程如下:
(1). 确定连接基于的套接字.
若关联的fd此时为-1,则会创建一个新的非阻塞套接字.
若关联的fd并非-1,我们应保证此套接字并未连接.
(2). 通过套接字发出连接请求.
a. 若connect返回值非负值,表示连接已经建立.
b. 若connect返回负值,但errnoEINPROGRESS,可认为连接请求正常发出,但尚未获得结果.
c. 若connect返回负值,但errnoEINPROGRESS,可算作失败.
(3). 针对上述,立即失败的场景,直接返回-1结束.
(4). 这里需要建立新的套接字和通信对象的关联.
所谓建立关联就是,将通信对象的两个event分别从关联的event_base取消注册.再重新用新的fd去初始化两个event,并按enabled字段去向event_base重新建立关联的过程.
(5). 设置connecting,表示通信对象此时处于连接中,并返回.表示连接已经正常发起.

注意的是:
a. 连接立即成功时,由于注册了对可写事件的监控.此时也会触发可写事件处理.可在可写处理中完成连接建立动作.
b. 连接正常发起时,无论后续失败还是成功,在结果达到时均会产生可写事件.在可写事件处理中分别处理异步成功,异步失败的动作.

libevent连接发起有两处看着是有问题的:
a. 连接立即成功时,没保证一定注册可写事件监控是不对的,此时再手动引发一次上层设置的可写回调也是不对的.
b. connect返回EINTR算作连接已经正常发起也是不对的.此时应该再次connect,直到返回值表示连接成功,连接失败,连接进行中才行.

3.1.5.断开连接
libevent中通信对象没提供主动断开的接口,只提供了通信对象释放的接口.
释放通信对象前会从关联的event_base移除此通信对象上所有关联到其的eventevent_callback,并通过event_base提供的异步释放机制在异步回调中执行实际的释放操作.

3.1.6.主动发送数据
参考上述关于evbuffer的描述.
向其写入新数据简要描述为:
(1). 若最后一个持有有效数据块内尚可放入,先将数据放入此块.
(2). 若数据还有剩余,分配新块,剩余数据放入新快.新快插入链式结构.
(3). 更新数据增量,立即触发一次发送缓存区上挂着的回调.此回调用于在需要时自动向关联event_base注册通信对象可写事件.

3.1.7.实现收取数据包处理
每次处理可读事件收到新的数据后会自动触发一次用户提供的收包回调.
在此回调里面应该,分析现有收取内容是否构成一个完整包.
若是,则应取出新包,反序列化后,处理包的逻辑.将包尺寸从缓存区对象上消耗掉.
若剩余部分,不足一个包应结束回调(后续再次可读并读取新的内容时会再次触发回调).

3.1.8.实现事件处理
事件处理一般划分为两类:
(1). 连接建立
此时作相应的连接建立处理即可.
(2). 超时或错误
针对此类情形一般直接释放连接对象即可.

3.1.11.释放通信对象
释放通信对象前会从关联的event_base移除此通信对象上所有关联到其的eventevent_callback,并通过event_base提供的异步释放机制在异步回调中执行实际的释放操作.

3.2.通信对象的io事件处理
3.2.1.实现可读事件处理
可简要描述为:
(1). 先计算本次执行一次recv最多可收取的数据量.
(2). 分析接收缓存区最后一块是否存在足够空间来完成数据接收,若存在在最后一块上作数据接收.
(3). 若不存在,分配一个新的块.基于新的块完成数据接收.新块插入链式结构.
(4). 若接收出错,则立即引发事件处理.若接收成功,则立即引发收包回调.

3.2.2.实现可写事件处理
可简要描述为:
(1). 若连接中收到可写事件,此时需进一步判断是属于异步连接成功,还是异步连接失败.
异步连接失败时,需向关联event_base移除可写,可读event,并立即触发事件处理.结束.
异步连接成功时,会立即触发事件处理.并判断若此时enabled不含EV_WRITE则向关联event_base取消可写event
(2). 计算本次允许向write写入的数据量.
(3). 会对发送缓存区各个块进行规整处理,规整的目的是得到一块连续的包含指定尺寸的待发送区域.规整过程可能涉及块间数据转移,产生新块,释放块等.
(4). 写入过程遭遇错误,会立即引发上层事件回调.
(5). 写入成功,会判断发送缓存区有效数据若此时为,则会从关联event_base移除可写event
(6). 写入成功,也会立即引发一个关于写入的回调.不过一般而言,通信对象没必要需要这样的回调.

3.2.作为服务端被动连接的通信对象使用流程
服务端的通信对象完全可参考客户端的通信对象使用.唯一的区别只是,不用再针对通信对象发起连接.直接基于accept得到的套接字产生新的通信对线下.设置其应用层回调.使能其可写,可读事件后,即可正常使用这样的对象收取包,发送包,处理其事件.

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

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

相关文章

Linux---信号

前言 到饭点了,我点了一份外卖,然后又开了一把网游,这个时候,我在打游戏的过程中,我始终记得外卖小哥会随时给我打电话,通知我我去取外卖,这个时候游戏还没有结束。我在打游戏的过程中需要把外…

【Boost】:阶段性测试和阶段性代码合集(五)

阶段性测试和阶段性代码合集 一.编写测试程序-server.cc二.一些问题三.完整源代码 在这里添加了一些打印信息,方便我们观察,由于比较分散就不一一列举,可以看下面的完整源代码。 一.编写测试程序-server.cc 1.原版 只是简单的测试&#xff0…

多输入多输出 | Matlab实现PSO-LSTM粒子群优化长短期记忆神经网络多输入多输出预测

多输入多输出 | Matlab实现PSO-LSTM粒子群优化长短期记忆神经网络多输入多输出预测 目录 多输入多输出 | Matlab实现PSO-LSTM粒子群优化长短期记忆神经网络多输入多输出预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 Matlab实现PSO-LSTM粒子群优化长短期记忆神经网络…

Java Arrays 的相关操作数组排序

Java Arrays 的相关操作数组排序 package com.zhong.arrays;import java.math.BigDecimal; import java.util.Arrays; import java.util.Comparator;public class ArraysDemo {public static void main(String[] args) {int[] arr {10, 20, 40, 30, 90, 60, 10, 30, 50};// A…

ReactNative实现文本渐变

我们直接上图&#xff0c;可以看到上面文本的效果&#xff0c;使用SVG实现 1.首先还是要引入react-native-svg库 2.使用该库下面的LinearGradient和Text 好&#xff0c;话不多说&#xff0c;我们看具体代码 <Svg width{422} height{30} viewBox{0 0 422 30}><Defs&…

以真机促创新!玻色量子联合中国运筹学会共商量子生态新模式

2024年1月29日&#xff0c;中国运筹学会算法软件与应用分会的一行领导莅临北京玻色量子科技有限公司&#xff08;以下简称“玻色量子”&#xff09;&#xff0c;参观了玻色量子及光量子计算机真机“天工量子大脑”、自建的十万颗粒洁净度光量子信息技术实验室&#x1f517;&…

React 浮层组件实现思路

React 浮层组件&#xff08;也称为弹出组件或弹窗组件&#xff09;通常是指在用户界面上浮动显示的组件&#xff0c;它们脱离常规的文档流&#xff0c;并且可以在用户进行某些操作时出现在页面的最上层。React 浮层组件可以用于创建模态框&#xff08;Modal&#xff09;、下拉菜…

【TCP/IP】用户访问一个购物网站时TCP/IP五层参考模型中每一层的功能

当用户访问一个购物网站时&#xff0c;网络上的每一层都会涉及不同的协议&#xff0c;具体网络模型如下图所示。 以下是每个网络层及其相关的协议示例&#xff1a; 物理层&#xff1a;负责将比特流传输到物理媒介上&#xff0c;例如电缆或无线信号。所以在物理层&#xff0c;可…

解决hive表新增的字段查询为空null问题

Hive分区表新增字段&#xff0c;查询时数据为NULL的解决方案 由于业务拓展&#xff0c;需要往hive分区表新增新的字段&#xff0c;hive版本为2点多。 于是利用 alter table table_name add columns (col_name string )新增字段&#xff0c;然后向已存在分区中插入数据&#x…

centos间文件传输

scp /home/vagrant/minio zx192.168.56.34:/home/zx /home/vagrant/minio 是你要传输的文件而且是当前机器登录用户有权限操作的文件 zx是目标机器的用户192.168.56.34是目标机器的地址 /home/zx是要传到这个文件夹下 要确保zx有/home/zx这个文件夹的操作权限 本质就是ssh文…

黑豹程序员-ElementPlus选择图标器

ElementPlus组件提供了很多图标svg 如何在你的系统中&#xff0c;用户可以使用呢&#xff1f; 这就是图标器&#xff0c;去调用ElementPlus的icon组件库&#xff0c;展示到页面&#xff0c;用户选择&#xff0c;返回选择的组件名称。 效果 代码 <template><el-inpu…

【C语言】static关键字的使用

目录 一、静态本地变量 1.1 静态本地变量的定义 1.2 静态本地变量和非静态本地变量的区别 二、静态函数 2.1 静态函数的定义 2.2 静态函数与非静态函数的区别 三、静态全局变量 3.1 静态全局变量的定义 3.2 静态全局变量和非静态全局变量的区别 四、静态结构体变量 …

挑战杯 python+opencv+深度学习实现二维码识别

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; pythonopencv深度学习实现二维码识别 &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&#xff1a;3分工作量&#xff1a;3分创新点&#xff1a;3分 该项目较为新颖&…

两次NAT

两次NAT即Twice NAT&#xff0c;指源IP和目的IP同时转换&#xff0c;该技术应用于内部网络主机地址与外部网络上主机地址重叠的情况。 如图所示&#xff0c;两次NAT转换的过程如下: 内网Host A要访问地址重叠的外部网络Host B&#xff0c;Host A向位于外部网络的DNS服务器发送…

图文并茂讲解Travelling Salesman

题目 思路 一道lca板子题&#xff0c;不会的同学可以先康康 详解最近公共祖先(LCA)-CSDN博客 我们可以发现&#xff0c;商人是从1开始&#xff0c;旅行到第一个城镇&#xff0c;再到第二个&#xff0c;第三个…… 那么我们只需要求出1~第一个城镇的距离&#xff0c;第一个城…

爱上算法:每日算法(24-2月4号)

&#x1f31f;坚持每日刷算法&#xff0c;&#x1f603;将其变为习惯&#x1f91b;让我们一起坚持吧&#x1f4aa; 文章目录 [232. 用栈实现队列](https://leetcode.cn/problems/implement-queue-using-stacks/)思路CodeJavaC 复杂度 [225. 用队列实现栈](https://leetcode.cn/…

引入echarts环形图及显示后端数据

实现效果&#xff1a; 1.下载echarts 并引用 npm install echarts --save 或 pnpm install -S echarts 项目中引用&#xff1a; 在项目main.ts中 import * as echarts from "echarts"; //引入echarts 3.页面中使用 <div id"main" class&quo…

Maven配置笔记

1、下载Maven 在Maven的官网即可下载&#xff0c;点击访问Apache Maven。 2、配置环境变量 chcp 65001 echo off set mvnhomeE:\apache-maven-3.8.4 rem LPY echo. echo ************************************************************ echo * …

有水印的照片怎么删除水印?这几个照片去水印的方法教给你

如今社会发展迅猛&#xff0c;越来越多人用手机、相机等设备拍照和录视频&#xff0c;记录生活点滴。然而&#xff0c;这些照片和视频很多时候会因为水印而影响欣赏体验。水印不仅破坏了画面的美观&#xff0c;还可能遮挡了重要的内容。那么&#xff0c;有水印的照片怎么删除水…

私有化部署跳一跳

目录 效果 安装 1.安装httpd 2.下载跳一跳 3.启动httpd 使用 效果 安装 1.安装httpd yum -y install httpd systemctl enable httpd 2.下载跳一跳 cd /var/www/html/ git clone https://gitee.com/WangZhe168_admin/jump.git 3.启动httpd systemctl start httpd 使…