从一万英尺外看libevent(源码刨析)

news2024/11/19 1:28:06

从一万英尺外看libevent

img
温馨提示:阅读时间大概二十分钟

前言

Libevent是用于编写高速可移植非阻塞IO应用的库,其设计目标是:

  • 可移植性:使用libevent编写的程序应该可以在libevent支持的所有平台上工作。即使没有好的方式进行非阻塞IO,libevent也应该支持一般的方式,让程序可以在受限的环境中运行
  • 速度:libevent尝试使用每个平台上最高速的非阻塞IO实现,并且不引入太多的额外开销。
  • 可扩展性:libevent被设计为程序即使需要上万个活动套接字的时候也可以良好工作。
  • 方便:无论何时,最自然的使用libevent编写程序的方式应该是稳定的、可移植的。(Libevent should compile on Linux, *BSD, Mac OS X, Solaris, Windows, and more.)

libevent由下列组件构成:

  • evutil:用于抽象不同平台网络实现差异的通用功能。

  • event和event_base:libevent的核心,为各种平台特定的、基于事件的非阻塞IO后端提供抽象API,让程序可以知道套接字何时已经准备好,可以读或者写,并且处理基本的超时功能,检测OS信号。

  • bufferevent:为libevent基于事件的核心提供使用更方便的封装。除了通知程序套接字已经准备好读写之外,还让程序可以请求缓冲的读写操作,可以知道何时IO已经真正发生。(bufferevent接口有多个后端,可以采用系统能够提供的更快的非阻塞IO方式,如Windows中的IOCP。)

  • evbuffer:在bufferevent层之下实现了缓冲功能,并且提供了方便有效的访问函数。

  • evhttp:一个简单的HTTP客户端/服务器实现。

  • evdns:一个简单的DNS客户端/服务器实现。

  • evrpc:一个简单的RPC实现。


  • libevent API提供了一种机制,可以在文件描述符上发生特定事件或达到超时后执行回调函数。此外,libevent还支持由于信号或常规超时而产生的回调。

  • Libevent旨在取代事件驱动网络服务器中的事件循环。应用程序只需要调用event_dispatch(),然后动态地添加或删除事件,而不必更改事件循环。

  • Libevent还为缓冲网络IO提供了一个复杂的框架,支持套接字、过滤器、速率限制、SSL、零拷贝文件传输和IOCP。Libevent支持几种有用的协议,包括DNS、HTTP和一个最小的RPC框架。

在这里插入图片描述

创建libevent时,默认安装下列库:

  • libevent_core:所有核心的事件和缓冲功能,包含了所有的event_base、evbuffer、bufferevent和工具函数。

  • libevent_extra:定义了程序可能需要,也可能不需要的协议特定功能,包括HTTP、DNS和RPC。

  • libevent:这个库因为历史原因而存在,它包含libevent_core和libevent_extra的内容。不应该使用这个库,未来版本的libevent可能去掉这个库。

某些平台上可能安装下列库:

  • libevent_openssl:这个库为使用bufferevent和OpenSSL进行加密的通信提供支持。它独立于libevent_core,这样程序使用libevent时就不需要链接到OpenSSL,除非是进行加密通信。
  • ibevent_pthreads:添加基于pthread可移植线程库的线程和锁定实现。它独立于libevent_core,这样程序使用libevent时就不需要链接到pthread,除非是以多线程方式使用libevent

头文件

libevent公用头文件都安装在event2目录中,分为三类:

  • API头文件:定义libevent公用接口。这类头文件没有特定后缀。
  • 兼容头文件:为已废弃的函数提供兼容的头部包含定义。不应该使用这类头文件,除非是在移植使用较老版本libevent的程序时。
  • 结构头文件:这类头文件以相对不稳定的布局定义各种结构体。这些结构体中的一些是为了提供快速访问而暴露;一些是因为历史原因而暴露。直接依赖这类头文件中的任何结构体都会破坏程序对其他版本libevent的二进制兼容性,有时候是以非常难以调试的方式出现。这类头文件具有后缀“_struct.h”。

如何使用libevent

在这里插入图片描述

​ 使用libevent函数之前需要分配一个或者多个``event_base结构体,每个event_base`持有一个事件集合,可以检测哪个事件是激活(可读写)的。

​ 如果设置event_base使用锁,则可以安全地在多个线程中访问它。然而,其事件循环只能运行在一个线程中。如果需要用多个线程检测IO,则需要为每个线程使用一个event_base


每个event_base都有一种用于检测哪种事件已经就绪的“方法”.

  • select
  • poll
  • epoll
  • kqueue
  • devpool
  • evport
  • win32

​ 用户可以用环境变量禁止某些特定的后端。比如说,要禁止kqueue后端,可以设置EVENT_NOKQUEUE环境变量。如果要用编程的方法禁止后端,看关于event_config_avoid_method()的说明。

建立默认的event_base

event_base_new()函数分配并且返回一个新的具有默认设置的event_base。函数会检测环境变量,返回一个到event_base的指针。如果发生错误,则返回NULL。选择各种方法时,函数会选择OS支持的最快方法。

event_base_new(void)

event_base_new()函数声明在<event2/event.h>

struct event_base * event_base_new(void)
{
	struct event_base *base = NULL;
    //配置config
	struct event_config *cfg = event_config_new();
	if (cfg) {
		base = event_base_new_with_config(cfg);
		event_config_free(cfg);
	}
	return base;
}

创建复杂的event_base()

​ 要对取得什么类型的event_base有更多的控制,就需要使用event_config。event_config是一个容纳event_base配置信息的不透明结构体。需要event_base时,将event_config传递给event_base_new_with_config()。

event_config_new()
#define INT_MAX __INT_MAX__
//在 C/C++ 中用于表示 int 类型所能表示的最大值。它是一个编译器常量,值为 2147483647(0x7ffffffff)
 

struct event_config * event_config_new(void)
{
	struct event_config *cfg = mm_calloc(1, sizeof(*cfg));

	if (cfg == NULL)
		return (NULL);

	TAILQ_INIT(&cfg->entries);
	cfg->max_dispatch_interval.tv_sec = -1;
	cfg->max_dispatch_callbacks = INT_MAX;
	cfg->limit_callbacks_after_prio = 1;

	return (cfg);
}

/*
 * Tail queue functions.
 * 尾队列的头结点初始化为空队列。
 */
#define	TAILQ_INIT(head) do {						\
	(head)->tqh_first = NULL;					\
	(head)->tqh_last = &(head)->tqh_first;				\
} while (/*CONSTCOND*/0)

要使用这些函数分配event_base,先调用event_config_new()分配一个event_config。然后,对event_config调用其它函数,设置所需要的event_base特征。最后,调用event_base_new_with_config()获取新的event_base。完成工作后,使用event_config_free()释放event_config


event_base_new_with_config

这个函数在new_event_base被调用,传入的参数是event_config,根据cfg的内容来配置event_base

struct event_base * event_base_new_with_config(const struct event_config *cfg)
{
	int i;
	struct event_base *base;
	int should_check_environment;

#ifndef EVENT__DISABLE_DEBUG_MODE
		event_debug_mode_too_late = 1;
#endif

	if ((base = mm_calloc(1, sizeof(struct event_base))) == NULL) { //安全分配内存
		event_warn("%s: calloc", __func__);
		return NULL;
	}

	if (cfg)
		base->flags = cfg->flags;

	should_check_environment =
	    !(cfg && (cfg->flags & EVENT_BASE_FLAG_IGNORE_ENV));

	{
		struct timeval tmp;
		int precise_time =
		    cfg && (cfg->flags & EVENT_BASE_FLAG_PRECISE_TIMER);
		int flags;
		if (should_check_environment && !precise_time) {
			precise_time = evutil_getenv_("EVENT_PRECISE_TIMER") != NULL;
			if (precise_time) {
				base->flags |= EVENT_BASE_FLAG_PRECISE_TIMER;
			}
		}
		flags = precise_time ? EV_MONOT_PRECISE : 0;
		evutil_configure_monotonic_time_(&base->monotonic_timer, flags);

		gettime(base, &tmp);
	}

	min_heap_ctor_(&base->timeheap);

	base->sig.ev_signal_pair[0] = -1;
	base->sig.ev_signal_pair[1] = -1;
	base->th_notify_fd[0] = -1;
	base->th_notify_fd[1] = -1;

	TAILQ_INIT(&base->active_later_queue);

	evmap_io_initmap_(&base->io);
	evmap_signal_initmap_(&base->sigmap);
	event_changelist_init_(&base->changelist);

	base->evbase = NULL;

	if (cfg) {
		memcpy(&base->max_dispatch_time,
		    &cfg->max_dispatch_interval, sizeof(struct timeval));
		base->limit_callbacks_after_prio =
		    cfg->limit_callbacks_after_prio;
	} else {
		base->max_dispatch_time.tv_sec = -1;
		base->limit_callbacks_after_prio = 1;
	}
	if (cfg && cfg->max_dispatch_callbacks >= 0) {
		base->max_dispatch_callbacks = cfg->max_dispatch_callbacks;
	} else {
		base->max_dispatch_callbacks = INT_MAX;
	}
	if (base->max_dispatch_callbacks == INT_MAX &&
	    base->max_dispatch_time.tv_sec == -1)
		base->limit_callbacks_after_prio = INT_MAX;

	for (i = 0; eventops[i] && !base->evbase; i++) {
		if (cfg != NULL) {
			/* determine if this backend should be avoided */
			if (event_config_is_avoided_method(cfg,
				eventops[i]->name))
				continue;
			if ((eventops[i]->features & cfg->require_features)
			    != cfg->require_features)
				continue;
		}

		/* also obey the environment variables */
		if (should_check_environment &&
		    event_is_method_disabled(eventops[i]->name))
			continue;

		base->evsel = eventops[i];

		base->evbase = base->evsel->init(base);
	}

	if (base->evbase == NULL) {
		event_warnx("%s: no event mechanism available",
		    __func__);
		base->evsel = NULL;
		event_base_free(base);
		return NULL;
	}

	if (evutil_getenv_("EVENT_SHOW_METHOD"))
		event_msgx("libevent using: %s", base->evsel->name);

	/* allocate a single active event queue */
	if (event_base_priority_init(base, 1) < 0) {
		event_base_free(base);
		return NULL;
	}

	/* prepare for threading */

#if !defined(EVENT__DISABLE_THREAD_SUPPORT) && !defined(EVENT__DISABLE_DEBUG_MODE)
	event_debug_created_threadable_ctx_ = 1;
#endif

#ifndef EVENT__DISABLE_THREAD_SUPPORT
	if (EVTHREAD_LOCKING_ENABLED() &&
	    (!cfg || !(cfg->flags & EVENT_BASE_FLAG_NOLOCK))) {
		int r;
		EVTHREAD_ALLOC_LOCK(base->th_base_lock, 0);
		EVTHREAD_ALLOC_COND(base->current_event_cond);
		r = evthread_make_base_notifiable(base);
		if (r<0) {
			event_warnx("%s: Unable to make base notifiable.", __func__);
			event_base_free(base);
			return NULL;
		}
	}
#endif

#ifdef _WIN32
	if (cfg && (cfg->flags & EVENT_BASE_FLAG_STARTUP_IOCP))
		event_base_start_iocp_(base, cfg->n_cpus_hint);
#endif

	return (base);
}



event_config_free
//用来释放config
void event_config_free(struct event_config *cfg)
{
	struct event_config_entry *entry;

	while ((entry = TAILQ_FIRST(&cfg->entries)) != NULL) {
		TAILQ_REMOVE(&cfg->entries, entry, next);
		event_config_entry_free(entry);
	}
	mm_free(cfg);
}


event_config_avoid_method()
int event_config_avoid_method(struct event_config *cfg, const char *method)
{
	struct event_config_entry *entry = mm_malloc(sizeof(*entry));
	if (entry == NULL)
		return (-1);

	if ((entry->avoid_method = mm_strdup(method)) == NULL) {
		mm_free(entry);
		return (-1);
	}

	TAILQ_INSERT_TAIL(&cfg->entries, entry, next);

	return (0);
}

​ 调用event_config_avoid_method()可以通过名字让libevent避免使用特定的可用后端。调用event_config_require_feature()让libevent不使用不能提供所有指定特征的后端。调用event_config_set_flag()让libevent在创建event_base时设置一个或者多个将在下面介绍的运行时标志。


event_config_require_feature()
int event_config_require_features(struct event_config *cfg,int features)
{
	if (!cfg)
		return (-1);
	cfg->require_features = features;
	return (0);
}
  • EV_FEATURE_ET:要求支持边沿触发的后端

  • EV_FEATURE_O1:要求添加、删除单个事件,或者确定哪个事件激活的操作是O(1)复杂度的后端

  • EV_FEATURE_FDS:要求支持任意文件描述符,而不仅仅是套接字的后端


event_config_set_flag
int event_config_set_flag(struct event_config *cfg, int flag)
{
	if (!cfg)
		return -1;
	cfg->flags |= flag;
	return 0;
}
  • EVENT_BASE_FLAG_NOLOCK:不要为event_base分配锁。设置这个选项可以为event_base节省一点用于锁定和解锁的时间,但是让在多个线程中访问event_base成为不安全的。

  • EVENT_BASE_FLAG_IGNORE_ENV:选择使用的后端时,不要检测EVENT_*环境变量。使用这个标志需要三思:这会让用户更难调试你的程序与libevent的交互。

  • EVENT_BASE_FLAG_STARTUP_IOCP:仅用于Windows,让libevent在启动时就启用任何必需的IOCP分发逻辑,而不是按需启用。

  • EVENT_BASE_FLAG_NO_CACHE_TIME:不是在事件循环每次准备执行超时回调时检测当前时间,而是在每次超时回调后进行检测。注意:这会消耗更多的CPU时间。

  • EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST:告诉libevent,如果决定使用epoll后端,可以安全地使用更快的基于changelist的后端。epoll-changelist后端可以在后端的分发函数调用之间,同样的fd多次修改其状态的情况下,避免不必要的系统调用。但是如果传递任何使用dup()或者其变体克隆的fd给libevent,epoll-changelist后端会触发一个内核bug,导致不正确的结果。在不使用epoll后端的情况下,这个标志是没有效果的。也可以通过设置EVENT_EPOLL_USE_CHANGELIST环境变量来打开epoll-changelist选项。

上述操作event_config的函数都在成功时返回0,失败时返回-1。

注意

​ 设置event_config,请求OS不能提供的后端是很容易的。比如说,对于libevent 2.0.1-alpha,在Windows中是没有O(1)后端的;在Linux中也没有同时提供EV_FEATURE_FDS和EV_FEATURE_O1特征的后端。如果创建了libevent不能满足的配置,event_base_new_with_config()会返回NULL。

event_config_set_num_cpus_hint()

int event_config_set_num_cpus_hint(struct event_config *cfg, int cpus)
{
	if (!cfg)
		return (-1);
	cfg->n_cpus_hint = cpus;
	return (0);
}

​ 这个函数当前仅在Windows上使用IOCP时有用,虽然将来可能在其他平台上有用。这个函数告诉event_config在生成多线程event_base的时候,应该试图使用给定数目的CPU。注意这仅仅是一个提示:event_base使用的CPU可能比你选择的要少。

这些函数和类型在<event2/event.h>中声明。

EVENT_BASE_FLAG_IGNORE_ENV标志首次出现在2.0.2-alpha版本。event_config_set_num_cpus_hint()函数是2.0.7-rc版本新引入的。检查event_base的后端方法

event_get_supported_methods
const char ** event_get_supported_methods(void)
{
	static const char **methods = NULL;
	const struct eventop **method;
	const char **tmp;
	int i = 0, k;

	/* count all methods */
	for (method = &eventops[0]; *method != NULL; ++method) {
		++i;
	}

	/* allocate one more than we need for the NULL pointer */
	tmp = mm_calloc((i + 1), sizeof(char *));
	if (tmp == NULL)
		return (NULL);

	/* populate the array with the supported methods */
	for (k = 0, i = 0; eventops[k] != NULL; ++k) {
		tmp[i++] = eventops[k]->name;
	}
	tmp[i] = NULL;

	if (methods != NULL)
		mm_free((char**)methods);

	methods = tmp;

	return (methods);
}

event_get_supported_methods()函数返回一个指针,指向libevent支持的方法名字数组。这个数组的最后一个元素是NULL。

注意

这个函数返回libevent被编译以支持的方法列表。然而libevent运行的时候,操作系统可能不能支持所有方法。比如说,可能OS X版本中的kqueue的bug太多,无法使用。

event_base_get_method
const char * event_base_get_method(const struct event_base *base)
{
	EVUTIL_ASSERT(base);//检查给定的条件 cond 是否满足
	return (base->evsel->name);
}
#define EVUTIL_ASSERT(cond)						\
	do {								\
		if (EVUTIL_UNLIKELY(!(cond))) {				\
			event_errx(EVENT_ERR_ABORT_,			\
			    "%s:%d: Assertion %s failed in %s",		\
			    __FILE__,__LINE__,#cond,__func__);		\
			/* In case a user-supplied handler tries to */	\
			/* return control to us, log and abort here. */	\
			(void)fprintf(stderr,				\
			    "%s:%d: Assertion %s failed in %s",		\
			    __FILE__,__LINE__,#cond,__func__);		\
			abort();					\
		}							\
	} while (0)
void event_errx(int eval, const char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	event_logv_(EVENT_LOG_ERR, NULL, fmt, ap);
	va_end(ap);
	event_exit(eval);
}

释放event_base

​ 使用完event_base之后,使用event_base_free()进行释放。

event_base_free()
void
event_base_free(struct event_base *base)
{
	event_base_free_(base, 1);
}

参数 base 是指向需要释放的 event_base 结构的指针,run_finalizers 是一个标志,用于指示是否需要运行终结器。

static void
event_base_free_(struct event_base *base, int run_finalizers)
{
	int i, n_deleted=0;
	struct event *ev;
	/* XXXX grab the lock? If there is contention when one thread frees
	 * the base, then the contending thread will be very sad soon. */
	/* 这段注释表明,在一个线程释放资源(通常指的是一个共享的基础对象或结构)时,可能需要获取一个锁(lock)来防止其他线程同时	访问该资源。如果不加锁,而另一个线程尝试访问同一个资源,就会发生竞争(contention)。竞争会导致程序不正确的行为,甚至崩	溃。*/
    
    
	/* event_base_free(NULL) is how to free the current_base if we
	 * made it with event_init and forgot to hold a reference to it. */
   	/*这段注释解释了如何释放当前的基础对象。如果您使用 event_init 函数创建了一个基础对象,但忘记了保存对它的引用,那么可以通		过调用 event_base_free(NULL) 来释放它。这表明 event_base_free 函数允许一个 NULL 参数,这种情况下,它将释放当前	   上下文中的基础对象。*/
	if (base == NULL && current_base)
		base = current_base;
	/* Don't actually free NULL. */
	if (base == NULL) {
		event_warnx("%s: no base to free", __func__);
		return;
	}
	/* XXX(niels) - check for internal events first */

#ifdef _WIN32
	event_base_stop_iocp_(base);
#endif

	/* threading fds if we have them */
	if (base->th_notify_fd[0] != -1) {
		event_del(&base->th_notify);
		EVUTIL_CLOSESOCKET(base->th_notify_fd[0]);
		if (base->th_notify_fd[1] != -1)
			EVUTIL_CLOSESOCKET(base->th_notify_fd[1]);
		base->th_notify_fd[0] = -1;
		base->th_notify_fd[1] = -1;
		event_debug_unassign(&base->th_notify);
	}

	/* Delete all non-internal events. */
	evmap_delete_all_(base);

	while ((ev = min_heap_top_(&base->timeheap)) != NULL) {
		event_del(ev);
		++n_deleted;
	}
	for (i = 0; i < base->n_common_timeouts; ++i) {
		struct common_timeout_list *ctl =
		    base->common_timeout_queues[i];
		event_del(&ctl->timeout_event); /* Internal; doesn't count */
		event_debug_unassign(&ctl->timeout_event);
		for (ev = TAILQ_FIRST(&ctl->events); ev; ) {
			struct event *next = TAILQ_NEXT(ev,
			    ev_timeout_pos.ev_next_with_common_timeout);
			if (!(ev->ev_flags & EVLIST_INTERNAL)) {
				event_del(ev);
				++n_deleted;
			}
			ev = next;
		}
		mm_free(ctl);
	}
	if (base->common_timeout_queues)
		mm_free(base->common_timeout_queues);

	for (;;) {
		/* For finalizers we can register yet another finalizer out from
		 * finalizer, and iff finalizer will be in active_later_queue we can
		 * add finalizer to activequeues, and we will have events in
		 * activequeues after this function returns, which is not what we want
		 * (we even have an assertion for this).
		 *
		 * A simple case is bufferevent with underlying (i.e. filters).
		 */
		int i = event_base_free_queues_(base, run_finalizers);
		event_debug(("%s: %d events freed", __func__, i));
		if (!i) {
			break;
		}
		n_deleted += i;
	}

	if (n_deleted)
		event_debug(("%s: %d events were still set in base",
			__func__, n_deleted));

	while (LIST_FIRST(&base->once_events)) {
		struct event_once *eonce = LIST_FIRST(&base->once_events);
		LIST_REMOVE(eonce, next_once);
		mm_free(eonce);
	}

	if (base->evsel != NULL && base->evsel->dealloc != NULL)
		base->evsel->dealloc(base);

	for (i = 0; i < base->nactivequeues; ++i)
		EVUTIL_ASSERT(TAILQ_EMPTY(&base->activequeues[i]));

	EVUTIL_ASSERT(min_heap_empty_(&base->timeheap));
	min_heap_dtor_(&base->timeheap);

	mm_free(base->activequeues);

	evmap_io_clear_(&base->io);
	evmap_signal_clear_(&base->sigmap);
	event_changelist_freemem_(&base->changelist);

	EVTHREAD_FREE_LOCK(base->th_base_lock, 0);
	EVTHREAD_FREE_COND(base->current_event_cond);

	/* If we're freeing current_base, there won't be a current_base. */
	if (base == current_base)
		current_base = NULL;
	mm_free(base);
}

主要代码逻辑
/* Global state; deprecated */
//全局的event_base current_base(event_global_current_base)
EVENT2_EXPORT_SYMBOL
struct event_base *event_global_current_base_ = NULL;
#define current_base event_global_current_base_

  1. 处理 NULL 基础对象: 如果 baseNULL 并且 current_base 存在,那么将 base 设置为 current_base

    if (base == NULL && current_base)
        base = current_base;d
    if (base == NULL) {
        event_warnx("%s: no base to free", __func__);
        return;
    }
    
  2. 停止 Windows 特定的 IOCP 处理(仅在 Windows 平台上有效):

    #ifdef _WIN32
    event_base_stop_iocp_(base);//此处不做讨论
    #endif
    
  3. 处理线程通知文件描述符: 删除线程通知事件并关闭文件描述符。

    if (base->th_notify_fd[0] != -1) {
        event_del(&base->th_notify);
        EVUTIL_CLOSESOCKET(base->th_notify_fd[0]);
        if (base->th_notify_fd[1] != -1)
            EVUTIL_CLOSESOCKET(base->th_notify_fd[1]);
        base->th_notify_fd[0] = -1;
        base->th_notify_fd[1] = -1;
        event_debug_unassign(&base->th_notify);
    }
    
  4. 删除所有非内部事件: 删除时间堆中的所有事件,并释放公共超时队列中的事件。

    evmap_delete_all_(base);
    while ((ev = min_heap_top_(&base->timeheap)) != NULL) {
        event_del(ev);
        ++n_deleted;
    }
    for (i = 0; i < base->n_common_timeouts; ++i) {
        struct common_timeout_list *ctl = base->common_timeout_queues[i];
        event_del(&ctl->timeout_event); // Internal; doesn't count
        event_debug_unassign(&ctl->timeout_event);
        for (ev = TAILQ_FIRST(&ctl->events); ev; ) {
            struct event *next = TAILQ_NEXT(ev, ev_timeout_pos.ev_next_with_common_timeout);
            if (!(ev->ev_flags & EVLIST_INTERNAL)) {
                event_del(ev);
                ++n_deleted;
            }
            ev = next;
        }
        mm_free(ctl);
    }
    if (base->common_timeout_queues)
        mm_free(base->common_timeout_queues);
    
  5. 释放一次性事件: 释放 once_events 列表中的事件。

    while (LIST_FIRST(&base->once_events)) {
        struct event_once *eonce = LIST_FIRST(&base->once_events);
        LIST_REMOVE(eonce, next_once);
        mm_free(eonce);
    }
    
  6. 释放选择器和锁: 释放事件选择器和相关的锁与条件变量。

    if (base->evsel != NULL && base->evsel->dealloc != NULL)
        base->evsel->dealloc(base);
    
    for (i = 0; i < base->nactivequeues; ++i)
        EVUTIL_ASSERT(TAILQ_EMPTY(&base->activequeues[i]));
    
    EVUTIL_ASSERT(min_heap_empty_(&base->timeheap));
    min_heap_dtor_(&base->timeheap);
    
    mm_free(base->activequeues);
    
    evmap_io_clear_(&base->io);
    evmap_signal_clear_(&base->sigmap);
    event_changelist_freemem_(&base->changelist);
    
    EVTHREAD_FREE_LOCK(base->th_base_lock, 0);
    EVTHREAD_FREE_COND(base->current_event_cond);
    
  7. 释放基础对象: 如果正在释放 current_base,则将其设置为 NULL,并最终释放 base

    if (base == current_base)
        current_base = NULL;
    mm_free(base);
    
总结

这个函数通过清理和释放所有相关资源和内存来销毁一个 event_base 对象。注释中提到的竞争条件提醒开发者在多线程环境中小心处理共享资源,确保程序的稳定性和正确性。

设置event_base的优先级

​ libevent支持为事件设置多个优先级。然而,event_base默认只支持单个优先级。可以调用event_base_priority_init()设置event_base的优先级数目。

int event_base_priority_init(struct event_base *base, int npriorities)

​ 成功时这个函数返回0,失败时返回-1。base是要修改的event_base,n_priorities是要支持的优先级数目,这个数目至少是1。每个新的事件可用的优先级将从0(最高)到n_priorities-1(最低)。

​ 常量EVENT_MAX_PRIORITIES表示n_priorities的上限。调用这个函数时为n_priorities给出更大的值是错误的。

注意

必须在任何事件激活之前调用这个函数,最好在创建event_base后立刻调用。

示例

关于示例,请看event_priority_set的文档·。

默认情况下,与event_base相关联的事件将被初始化为具有优先级n_priorities / 2event_base_priority_init()函数定义在<event2/event.h>中,从libevent 1.0版就可用了。

*这个宏用于获取event_base*的锁(如果有support)

/** Lock an event_base, if it is set up for locking.  Acquires the lock
    in the base structure whose field is named 'lockvar'. */
#define EVBASE_ACQUIRE_LOCK(base, lockvar) do {				\
		EVLOCK_LOCK((base)->lockvar, 0);			\
	} while (0)

#define N_ACTIVE_CALLBACKS(base)					\
	((base)->event_count_active) //最大事件数量

/** Largest number of priorities that Libevent can support. */
#define EVENT_MAX_PRIORITIES 256
/**

 Set the number of different event priorities

 By default Libevent schedules all active events with the same priority.

 However, some time it is desirable to process some events with a higher

 priority than others.  For that reason, Libevent supports strict priority

 queues.  Active events with a lower priority are always processed before

 events with a higher priority.

 The number of different priorities can be set initially with the

 event_base_priority_init() function.  This function should be called

 before the first call to event_base_dispatch().  The

 event_priority_set() function can be used to assign a priority to an

 event.  By default, Libevent assigns the middle priority to all events

 unless their priority is explicitly set.

 Note that urgent-priority events can starve less-urgent events: after

 running all urgent-priority callbacks, Libevent checks for more urgent

 events again, before running less-urgent events.  Less-urgent events

 will not have their callbacks run until there are no events more urgent

 than them that want to be active.

 @param eb the event_base structure returned by event_base_new()

 @param npriorities the maximum number of priorities

 @return 0 if successful, or -1 if an error occurred

 @see event_priority_set()

 */
int event_base_priority_init(struct event_base *base, int npriorities)
{
	int i, r;
	r = -1; //用于指示返回值的状态	
	//在多线程环境中,需要获取锁以确保对 base 的修改是线程安全的。
	EVBASE_ACQUIRE_LOCK(base, th_base_lock);
    //展开类似 EVLOCK_LOCK((base)->th_base_lock, 0);

	//参数和状态检查
	if (N_ACTIVE_CALLBACKS(base) || npriorities < 1
	    || npriorities >= EVENT_MAX_PRIORITIES)
		goto err;
	//检查是否需要重新分配队列	
	if (npriorities == base->nactivequeues)
		goto ok;
	
	if (base->nactivequeues) {
		mm_free(base->activequeues);
		base->nactivequeues = 0;
	}

	/* Allocate our priority queues */
    //分配新的优先级队列
	base->activequeues = (struct evcallback_list *)
	  mm_calloc(npriorities, sizeof(struct evcallback_list));
	if (base->activequeues == NULL) {
		event_warn("%s: calloc", __func__);
		goto err;
	}
	base->nactivequeues = npriorities;

	for (i = 0; i < base->nactivequeues; ++i) {
		TAILQ_INIT(&base->activequeues[i]);
	}

ok:
	r = 0;
err:
	EVBASE_RELEASE_LOCK(base, th_base_lock);
	return (r);
}
总结

这个函数的主要目的是为事件基础结构 base 初始化或重新初始化优先级队列。它通过以下步骤实现:

  1. 获取锁以确保线程安全。
  2. 检查输入参数和当前状态。
  3. 如果需要,释放现有的优先级队列。
  4. 分配并初始化新的优先级队列。
  5. 设置返回状态并释放锁。

通过这些步骤,确保 event_base 可以正确管理不同优先级的事件。

libevent的内存分配与管理

内存分配的相关宏定义

#define mm_malloc(sz) 			event_mm_malloc_(sz)
#define mm_calloc(count, size) 	event_mm_calloc_((count), (size))
#define mm_strdup(s) 			event_mm_strdup_(s)
#define mm_realloc(p, sz) 		event_mm_realloc_((p), (sz))
#define mm_free(p) 				event_mm_free_(p)
//#ifdef EVENT__DISABLE_MM_REPLACEMENT 下
#define mm_malloc(sz) malloc(sz)
#define mm_calloc(n, sz) calloc((n), (sz))
#define mm_strdup(s) strdup(s)
#define mm_realloc(p, sz) realloc((p), (sz))
#define mm_free(p) free(p)

static void *(*mm_malloc_fn_)(size_t sz) = NULL;
static void *(*mm_realloc_fn_)(void *p, size_t sz) = NULL;
static void (*mm_free_fn_)(void *p) = NULL;

mm_realloc

void *  event_mm_realloc_(void *ptr, size_t sz)
{
	if (mm_realloc_fn_)
		return mm_realloc_fn_(ptr, sz);
	else
		return realloc(ptr, sz);
}

mm_strdup

char * event_mm_strdup_(const char *str)
{
	if (!str) {
		errno = EINVAL;
		return NULL;
	}

	if (mm_malloc_fn_) {
		size_t ln = strlen(str);
		void *p = NULL;
		if (ln == EV_SIZE_MAX)
			goto error;
		p = mm_malloc_fn_(ln+1);
		if (p)
			return memcpy(p, str, ln+1);
	} else
        //不同平台下的函数不一样
#ifdef _WIN32
		return _strdup(str);
#else
		return strdup(str);
#endif

error:
	errno = ENOMEM;
	return NULL;
}
 

mm_malloc

void * event_mm_malloc_(size_t sz)
{
	if (sz == 0)
		return NULL;

	if (mm_malloc_fn_)
		return mm_malloc_fn_(sz);
	else
		return malloc(sz);
}

mm_free()

#define mm_free(p) event_mm_free_(p)

static void (*mm_free_fn_)(void *p) = NULL;//指向释放内存的函数
void event_mm_free_(void *ptr)
{
	if (mm_free_fn_)
		mm_free_fn_(ptr);
	else
		free(ptr);
}

mm_calloc()

 

#ifndef EVENT__DISABLE_MM_REPLACEMENT
#define mm_malloc(sz) event_mm_malloc_(sz)
#define mm_calloc(count, size) event_mm_calloc_((count), (size))
#define mm_strdup(s) event_mm_strdup_(s)
#define mm_realloc(p, sz) event_mm_realloc_((p), (sz))
#define mm_free(p) event_mm_free_(p)
#else
#define mm_malloc(sz) malloc(sz)
#define mm_calloc(n, sz) calloc((n), (sz))
#define mm_strdup(s) strdup(s)
#define mm_realloc(p, sz) realloc((p), (sz))
#define mm_free(p) free(p)
#endif //EVENT__DISABLE_MM_REPLACEMENT

static void *(*mm_malloc_fn_)(size_t sz) = NULL;//定义一个接受参数为seize_t类型的函数指针mm_malloc_fn_
void * event_mm_calloc_(size_t count, size_t size)
{
	if (count == 0 || size == 0)
		return NULL;

	if (mm_malloc_fn_) { 
        //非空
		size_t sz = count * size;
		void *p = NULL;
		if (count > EV_SIZE_MAX / size)
			goto error;
		p = mm_malloc_fn_(sz);
		if (p)
			return memset(p, 0, sz);
	} else {
        //为空
		void *p = calloc(count, size);
#ifdef _WIN32
		/* Windows calloc doesn't reliably set ENOMEM */
		if (p == NULL)
			goto error;
#endif
		return p;
	}

error:
	errno = ENOMEM;
	return NULL;
}

libevent的一些重要的结构体

event_config_entry结构体

libevent 中的事件配置项。

通过使用 event_config_entry 结构体,可以在 libevent 中定义和管理多个事件配置项,并按照链表的方式进行链接和访问。每个配置项都包含一个要避免使用的网络通信方法。

struct event_config_entry {
	TAILQ_ENTRY(event_config_entry) next;

	const char *avoid_method;//要避免使用的网络通信方法。
};
//通过使用 TAILQ_ENTRY 宏,可以为指定的数据类型创建一个双向链表的入口和出口结构体,方便在链表中进行插入、删除和遍历等操作。
#define	_TAILQ_ENTRY(type, qual)					\
struct {								\
	qual type *tqe_next;		/* next element */		\
	qual type *qual *tqe_prev;	/* address of previous next element */\
}
#define TAILQ_ENTRY(type)	_TAILQ_ENTRY(struct type,)

qual 用于指定链表结构体成员的修饰符(它可以是 constvolatile 或其他限定符。)

  • tqe_next:指向链表中下一个元素的指针。
  • tqe_prev:指向链表中上一个元素的指针的地址。这里使用了一个指向指针的指针,即二级指针,用于在删除元素时修改前一个元素的 tqe_next 指针。

event_base结构体

struct event_base {
	//一个指向特定于后端数据的指针,用于描述这个event_base端。
	const struct eventop *evsel;
	//一个指向特定于后端数据的指针,用于指向底层事件驱动后端的实现。
	void *evbase;

	/** List of changes to tell backend about at next dispatch.  Only used
	 * by the O(1) backends. */
    
    //一个结构体,用于描述在下次调度时需要通知后端更改的事件
	struct event_changelist changelist;
	
	/** Function pointers used to describe the backend that this event_base
	 * uses for signals */
    
    //一个指向特定于信号处理后端数据的指针,用于描述后端event_base*用于信号处理。
	const struct eventop *evsigsel;
    
	/** Data to implement the common signal handler code. */
    //一个结构体,用于实现信号处理通用代码的数据。
	struct evsig_info sig;

	/** Number of virtual events */
    //用于表示当前虚拟事件数量。
	int virtual_event_count;
    
	/** Maximum number of virtual events active */
    //用于表示最大虚拟事件数量。
	int virtual_event_count_max;
    
	/** Number of total events added to this event_base */
    //用于表示已添加到event_base的事件数量。
	int event_count;
    
	/** Maximum number of total events added to this event_base */
    //用于表示最大已添加到event_base的事件数量。
	int event_count_max;
    
	/** Number of total events active in this event_base */
    //用于表示当前活动事件数量。
	int event_count_active;
    
	/** Maximum number of total events active in this event_base */
    //用于表示最大活动事件数量。
	int event_count_active_max;

	/** Set if we should terminate the loop once we're done processing
	 * events. */
    //用于表示是否应该在处理完事件后终止循环。
	int event_gotterm;
    
	/** Set if we should terminate the loop immediately */
	
    //用于表示是否应该立即终止循环。
	int event_break;
    
	/** Set if we should start a new instance of the loop immediately. */
    //用于表示是否应该在处理完事件后继续执行循环。
	int event_continue;

	/** The currently running priority of events */
    //用于表示当前正在运行的事件优先级。
	int event_running_priority;

	/** Set if we're running the event_base_loop function, to prevent
	 * reentrant invocation. */
    //用于表示是否正在运行event_base_loop函数,以防止重入调用。
	int running_loop;

	/** Set to the number of deferred_cbs we've made 'active' in the
	 * loop.  This is a hack to prevent starvation; it would be smarter
	 * to just use event_config_set_max_dispatch_interval's max_callbacks
	 * feature */
    //用于表示已 deferred_cbs 数量
	int n_deferreds_queued;

	/* Active event management. */
	/** An array of nactivequeues queues for active event_callbacks (ones
	 * that have triggered, and whose callbacks need to be called).  Low
	 * priority numbers are more important, and stall higher ones.
	 */
    //用于存储活动事件队列。
	struct evcallback_list *activequeues;
	/** The length of the activequeues array */
	int nactivequeues;//用于表示活动事件队列的数量。
	/** A list of event_callbacks that should become active the next time
	 * we process events, but not this time. */
    //于存储应该在下次处理事件时激活的事件。
	struct evcallback_list active_later_queue;

	/* common timeout logic */

	/** An array of common_timeout_list* for all of the common timeout
	 * values we know. */
    //用于存储所有已知的时间outs。
	struct common_timeout_list **common_timeout_queues;

    /** The number of entries used in common_timeout_queues */
    //用于表示已使用的时间outs数量。
	int n_common_timeouts;
    
	/** The total size of common_timeout_queues. */
    //用于表示已分配的时间outs数量。
	int n_common_timeouts_allocated;

	/** Mapping from file descriptors to enabled (added) events */
    //用于存储文件描述符到已添加事件的映射。
	struct event_io_map io;

	/** Mapping from signal numbers to enabled (added) events. */
    //用于存储信号编号到已添加事件的映射。
	struct event_signal_map sigmap;

	/** Priority queue of events with timeouts. */
    //1个优先队列,用于存储具有超时的事件。
	struct min_heap timeheap;

	/** Stored timeval: used to avoid calling gettimeofday/clock_gettime
	 * too often. */
    //用于存储当前时间,以避免频繁调用gettimeofday/clock_gettime。
	struct timeval tv_cache;
	
    //,用于实现单调时钟。
	struct evutil_monotonic_timer monotonic_timer;

	/** Difference between internal time (maybe from clock_gettime) and
	 * gettimeofday. */
    //用于存储内部时间与gettimeofday之间的差异。
	struct timeval tv_clock_diff;
    
	/** Second in which we last updated tv_clock_diff, in monotonic time. */
    //用于存储上次更新tv_clock_diff时的单调时间。
	time_t last_updated_clock_diff;

#ifndef EVENT__DISABLE_THREAD_SUPPORT
	/* threading support */
	/** The thread currently running the event_loop for this base */
	unsigned long th_owner_id;
	/** A lock to prevent conflicting accesses to this event_base */
	void *th_base_lock;
	/** A condition that gets signalled when we're done processing an
	 * event with waiters on it. */
	void *current_event_cond;
	/** Number of threads blocking on current_event_cond. */
	int current_event_waiters;
#endif
	/** The event whose callback is executing right now */
	struct event_callback *current_event;

#ifdef _WIN32
	/** IOCP support structure, if IOCP is enabled. */
    /**:一个指向`event_iocp_port`结构体的指针,如果在Windows平台上使用IOCP(I/O Completion Port)作为事件驱动后端,则使用此字段。
*/
	struct event_iocp_port *iocp;
#endif

	/** Flags that this base was configured with */
    //`flags`:一个枚举类型,用于表示`event_base`的配置标志。
    
	enum event_base_config_flag flags;
	
    //,用于表示最大调度时间。
	struct timeval max_dispatch_time;
    
	int max_dispatch_callbacks;//用于表示最大调度回调数量。
	int limit_callbacks_after_prio;//用于表示在达到特定优先级后限制回调数量。

	/* Notify main thread to wake up break, etc. */
	/** True if the base already has a pending notify, and we don't need
	 * to add any more. */
	int is_notify_pending;//用于表示`event_base`是否已经有一个待处理的通知。
	/** A socketpair used by some th_notify functions to wake up the main
	 * thread. */
    
    //用于free base
	evutil_socket_t th_notify_fd[2];//一个套接字对,用于在另一个线程中唤醒主线程。
	/** An event used by some th_notify functions to wake up the main
	 * thread. */
    
	struct event th_notify;//用于在主线程中唤醒其他线程。

	/** A function used to wake up the main thread from another thread. */
    /**`th_notify`:一个`event`结构体,用于在主线程中唤醒其他线程。*/
	int (*th_notify_fn)(struct event_base *base);

	/** Saved seed for weak random number generator. Some backends use
	 * this to produce fairness among sockets. Protected by th_base_lock. */
    //一个`evutil_weakrand_state`结构体,用于存储弱随机数生成器的种子。
	struct evutil_weakrand_state weakrand_seed;

	/** List of event_onces that have not yet fired. */
    /**`once_events`:一个`LIST_HEAD`结构体,用于存储尚未触发的事件。*/
	LIST_HEAD(once_event_list, event_once) once_events;

};

eventop结构体

/** 用于定义给定事件基础结构的后端的结构体。 */

struct eventop {
    /** 后端的名称。 */
    const char *name;

    /** 初始化函数,用于设置事件基础结构以使用该后端。它应该创建一个新的结构体,
     * 保存运行该后端所需的任何信息,并将其返回。返回的指针将由event_init存储在
     * event_base.evbase字段中。如果初始化失败,该函数应返回NULL。 */
    void *(*init)(struct event_base *);

    /** 启用给定文件描述符或信号的读写事件。'events'参数表示我们要启用的事件类型,
     * 可能是EV_READ、EV_WRITE、EV_SIGNAL和EV_ET的组合。'old'参数表示之前在该
     * 文件描述符上启用的事件。'fdinfo'参数是与文件描述符相关联的结构体,在evmap
     * 中管理;其大小由下面的fdinfo_len字段定义。第一次添加文件描述符时,
     * 它将被设置为0。该函数应在成功时返回0,在错误时返回-1。 */
    int (*add)(struct event_base *, evutil_socket_t fd, short old, short events, void *fdinfo);

    /** 类似于'add'函数,但'events'参数表示我们要禁用的事件类型。 */
    int (*del)(struct event_base *, evutil_socket_t fd, short old, short events, void *fdinfo);

    /** 实现事件循环的核心功能。它需要检查哪些已添加的事件已准备就绪,并为每个活动事件
     * 调用event_active函数(通常通过event_io_active等方式)。该函数应在成功时返回0,
     * 在错误时返回-1。 */
    int (*dispatch)(struct event_base *, struct timeval *);

    /** 用于清理和释放事件基础结构中的数据的函数。 */
    void (*dealloc)(struct event_base *);

    /** 标志:如果我们在fork之后需要重新初始化事件基础结构,则设置此标志。 */
    int need_reinit;

    /** 支持的事件方法特性的位数组。 */
    enum event_method_feature features;

    /** 每个具有一个或多个活动事件的文件描述符应记录的额外信息的长度。
     * 此信息作为每个文件描述符的evmap条目的一部分记录,并作为参数传递给上述的
     * 'add'和'del'函数。 */
    size_t fdinfo_len;
};

event_config结构体

/** Internal structure: describes the configuration we want for an event_base
 * that we're about to allocate. */
struct event_config {
    于定义一个双向链表的头部。它将被用作存储event_config_entry类型的结构体的链表。
	TAILQ_HEAD(event_configq, event_config_entry) entries;
    
    //用于提示事件配置所需的CPU数量。
	int n_cpus_hint;
    //指定最大分派间隔的时间值。
	struct timeval max_dispatch_interval;
    //指定在一次循环中最大的分派回调数量。
	int max_dispatch_callbacks;
    //用于限制在特定优先级之后的回调数量。
	int limit_callbacks_after_prio;
    //指定要求的事件方法特性。
    //event_config_require_featureEV_FEATURE_ET:要求支持边沿触发的后端等
	enum event_method_feature require_features;
    //指定事件基础配置的标志。
	enum event_base_config_flag flags;//event_config_set_flag 设置具体可用参数见上文
};
#define TAILQ_HEAD(name, type)			\
struct name {					\
	struct type *tqh_first;			\
	struct type **tqh_last;			\
}

timeval结构体

/* A time value that is accurate to the nearest

  microsecond but also has a range of years.  */

struct timeval

{

 __time_t tv_sec;   /* Seconds.  */

 __suseconds_t tv_usec;  /* Microseconds.  */

};

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

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

相关文章

多业态、多品牌企业,如何实现积分通积通兑?(附大会员方案)

2021年&#xff0c;龙湖升级珑珠为全业态通用积分&#xff0c;招商荟深度接入招商蛇口大会员体系建设&#xff1b;2022年&#xff0c;华润置地大会员“万象星”正式上线&#xff1b;2023年&#xff0c;“蒙牛生活家会员中心”全新上线…… 越来越多地产、零售等行业的集团品牌…

【学习】如何利用Python技术进行软件测试相关工作

Python是一种广泛使用的高级编程语言&#xff0c;它因其简洁的语法、强大的库支持和跨平台特性而受到开发者的喜爱。在软件测试领域&#xff0c;Python同样发挥着重要作用&#xff0c;它可以帮助测试人员编写自动化测试脚本、进行接口测试、性能测试、以及处理测试数据等。以下…

迅为RK3588开发板支持LVDS信号,标准 HDMI信号,IMIPI信号

性能强--iTOP-3588开发板采用瑞芯微RK3588处理器&#xff0c;是全新一代ALoT高端应用芯片&#xff0c;采用8nm LP制程&#xff0c;搭载八核64位CPU&#xff0c;四核Cortex-A76和四核Cortex-A55架构&#xff0c;主频高达2.4GHZ&#xff0c;8GB内存&#xff0c;32GB EMMC。 四核心…

2024第十三届中国PMO大会主持人介绍

全国PMO专业人士年度盛会 由PMO评论主办的2024第十三届中国PMO大会邀请了到十几位知名企业的PMO和项目管理专家来担任大会主持人。大会将于6月29-30日在北京举办&#xff0c;敬请关注&#xff01; 主持人介绍 肖杨&#xff0c;国际知名组织级项目管理专家&#xff0c;微薄之力…

[深度学习] 自编码器Autoencoder

自编码器&#xff08;Autoencoder&#xff09;是一种无监督学习算法&#xff0c;主要用于数据的降维、特征提取和数据重建。自编码器由两个主要部分组成&#xff1a;编码器&#xff08;Encoder&#xff09;和解码器&#xff08;Decoder&#xff09;。其基本思想是将输入数据映射…

软考《信息系统运行管理员》-1.2信息系统运维

1.2信息系统运维 传统运维模式&#xff08;软件&#xff09; 泛化&#xff1a;软件交付后围绕其所做的任何工作纠错&#xff1a;软件运行中错误的发现和改正适应&#xff1a;为适应环境做出的改变用户支持&#xff1a;为软件用户提供的支持 新的不同视角下的运维 “管理”的…

【八股系列】Vue中的<keep-alive>组件:深入解析与实践指南

&#x1f389; 博客主页&#xff1a;【剑九 六千里-CSDN博客】 &#x1f3a8; 上一篇文章&#xff1a;【探索响应式布局的奥秘&#xff1a;关键技术与实战代码示例】 &#x1f3a0; 系列专栏&#xff1a;【面试题-八股系列】 &#x1f496; 感谢大家点赞&#x1f44d;收藏⭐评论…

三、用户中心项目笔记----后端多环境实战+原始部署

后端多环境主要是修改&#xff1a; 依赖的环境地址 数据库地址 缓存地址 消息队列地址 项目端口号 服务器配置 后端怎么去区分不同的环境&#xff1f; 我们后端的SpringBoot项目&#xff0c;通过application.yml添加不同后缀来区分配置文件 application.yml就是公共的配置&a…

NeRF从入门到放弃6:两种OpenCV去畸变模型

针孔相机和鱼眼相机的去畸变模型是不一样的。 针孔相机的畸变参数有12个&#xff0c;k1~k6是径向畸变参数&#xff0c;p1 p2是切向畸变&#xff0c;s1s4&#xff1b;而鱼眼相机是等距模型&#xff0c;畸变参数只有4个k1k4。 针孔相机 畸变分为径向畸变和切向畸变。 把相机平…

链式结构二叉树练习

一.二叉树的前序遍历 想要输出所给值&#xff0c;就要先用数组将数据存储起来&#xff0c;所以这里我们单独创建一个前序遍历函数&#xff0c;将所要数据前序遍历并放入数组&#xff0c;代码如下&#xff1a; void preOrder(struct TreeNode* root, int* a, int* pi)//前序遍历…

新鲜出炉的信息化一机两用方案

在信息化日益发展的今天&#xff0c;网络安全问题愈发凸显其重要性。尤其是在政府和企事业单位中&#xff0c;如何在保证业务流畅和工作效率的同时&#xff0c;确保信息高安全性&#xff0c;成为了一个亟待解决的问题。而“一机两用”政策&#xff0c;正是针对这一需求而提出的…

如何理解:业务架构、应用架构、数据架构、技术架构与系统和复杂度

关于系统的理解 1.1 系统的概述 随着人类社会的发展&#xff0c;人们面对越来越多的规模巨大、关系复杂、参数众多地复杂问题&#xff0c;这些问题的复杂度已经远远超出人类的理解能力&#xff0c;系统论就是为了分析和解决这些问题而生。我们平时接触的计算机系统包括软件系统…

C语言 | 文件操作(下)【必收藏】

文件操作&#xff08;下&#xff09; 5、文件的顺序读写5.1 顺序读写函数介绍5.1.1 fputc与fgetc5.1.2 fputs与fgets5.1.3 fprintf与fscanf5.1.4 fread与fwrite 5.2 对比一组函数 6. 文件的随机读写6.1 fseek6.2 ftell6.3 rewind 7. 文件读取结束的判定7.1 被错误使用的feof 8.…

Python爬虫实战:利用代理IP批量下载哔哩哔哩美女视频

文章 目录 1.前言2.爬取目标3.准备工作3.1 环境安装3.2 代理免费获取 四、爬虫实战分析4.1 翻页分析4.2 获取视频跳转链接4.3 下载视频4.4 视频音频合并4.5 完整源码 五、总结 1.前言 粉丝们&#xff08;lsp&#xff09;期待已久的Python批量下载哔哩哔哩美女视频教程它终于来…

Java中File文件和IO流

File文件和IO流 概述 FIle是java.io.下面的包&#xff0c;用于代表当前操作系统的文件 可以获文件信息&#xff0c;判断文件类型&#xff0c;创建删除文件夹 注意&#xff1a;File只能对文件本身进行操作&#xff0c;不能读写文件里面存储的数据 …

Docker三分钟部署ElasticSearch平替MeiliSearch轻量级搜索引擎

&#x1f469;&#x1f3fd;‍&#x1f4bb;个人主页&#xff1a;阿木木AEcru (更多精彩内容可进入主页观看) &#x1f525; 系列专栏&#xff1a;《Docker容器化部署系列》 《Java每日面筋》 &#x1f4b9;每一次技术突破&#xff0c;都是对自我能力的挑战和超越。 目录 一、 …

基于web的摩托车销售系统的设计与实现-计算机毕业设计源码031706

摘 要 信息化社会内需要与之针对性的信息获取途径&#xff0c;但是途径的扩展基本上为人们所努力的方向&#xff0c;由于站在的角度存在偏差&#xff0c;人们经常能够获得不同类型信息&#xff0c;这也是技术最为难以攻克的课题。针对摩托车销售系统等问题&#xff0c;对摩托车…

手把手教你使用kimi创建流程图【实践篇】

学境思源&#xff0c;一键生成论文初稿&#xff1a; AcademicIdeas - 学境思源AI论文写作 引言 在昨日的文章中&#xff0c;我们介绍了如何使用Kimi生成论文中的流程图。今天&#xff0c;我们将更进一步&#xff0c;通过实践案例来展示Kimi在生成流程图方面的应用。这不仅将加…

火绒被骂惨,良心居然也翻车?剩下3款软件还被误认为外国人开发

万万没想到&#xff0c;公认的国产良心软件“火绒”&#xff0c;居然也翻车&#xff0c;很多网友对其大失所望&#xff0c;甚至忍不住吐槽让他不要砸了自己的招牌。 事情的起因是这样的&#xff0c;火绒推出应用商店&#xff0c;并于正式公测&#xff0c;这是要逐渐走向全家桶的…

淘宝店铺商家订单API-接入ERP,多平台订单同步的利器

淘宝开放平台给商家们提供了丰富的API&#xff0c;以方便大家扩展业务流程。但是需要调用这些API&#xff0c;商家们要提交资质审核&#xff0c;审核条件也是很严格的。第三方数据公司的存在可以为大家解决这个问题。 custom-自定义API操作 请求参数 请求参数&#xff1a;ap…