libevent源码解析:信号事件(二)

文章目录

  • 前言
  • 一、用例
  • 二、基本数据结构介绍
  • 三、源码分析
    • event_base_new
    • evsignal_new
    • event_add
    • event_base_dispatch
  • 总结


前言

libevent中对三类事件进行了封装,io事件、信号事件、定时器事件,libevent源码分析系列文章会分别分析这三类事件,本文分析信号事件。libevent框架将linux中的信号事件转换成io事件进行处理,从而将信号事件和io事件进行了有机的统一。
本文通过简单的例子展现libevent中信号事件的使用,然后通过源码分析libevent中的信号事件实现原理。

一、用例

#include <event.h>
void signal_cb(evutil_socket_t s,short w, void *arg)
{
	printf("====signal_cb======\n");
	exit(0);
}
int main()
{
	event_base *base = event_base_new(); //初始化reactor对象,epoll_create
	event* signal_event = evsignal_new(base, SIGINT, signal_cb, (void *)base); //初始化一个信号事件,默认EV_SIGNAL|EV_PERSIST,信号能够被反复触发
	event_add(signal_event,0); //将事件注册到reactor中,epoll_ctl
	event_base_dispatch(base); //事件主循环,epoll_wait
}

二、基本数据结构介绍

struct event {
	TAILQ_ENTRY(event) ev_active_next; //激活事件队列的节点
	TAILQ_ENTRY(event) ev_next; //注册事件队列的节点
...
	union {
		/* used for io events */
		struct {
			TAILQ_ENTRY(event) ev_io_next;  //io事件节点
			struct timeval ev_timeout;
		} ev_io;

		/* used by signal events */
		struct {
			TAILQ_ENTRY(event) ev_signal_next; //信号事件节点
			short ev_ncalls; //对应信号执行的次数
			/* Allows deletes in callback */
			short *ev_pncalls;
		} ev_signal;
	} _ev;
...
	void (*ev_callback)(evutil_socket_t, short, void *arg); //注册回调函数
	void *ev_arg;
};
struct event_base {
	const struct eventop *evsel; //多路复用io封装的方法

	const struct eventop *evsigsel; //信号事件的方法
	struct evsig_info sig;   //存放信号处理的信息

	/** Number of total events added to this event_base */
	int event_count; //所有事件的个数
	/** Number of total events active in this event_base */
	int event_count_active; //激活事件的个数
...
	struct event_list *activequeues; //激活事件队列
	int nactivequeues;  //激活事件个数
...
	/** Mapping from file descriptors to enabled (added) events */
	struct event_io_map io; //存放io事件的hash表

	/** Mapping from signal numbers to enabled (added) events. */
	struct event_signal_map sigmap; //存放信号事件的hash表

	/** All events that have been enabled (added) in this event_base */
	struct event_list eventqueue; //存放所有事件的链表
...

};

信号事件队列
在这里插入图片描述
信号事件队列:分配空间为32的数组存储所有信号事件,index为注册对应的信号值,相同的信号值用链表串联,当某个信号发生后就能将该信号对应的所有事件添加到激活事件队列中。

struct event_signal_map {
	void **entries; //指向数组的指针
	int nentries; //数组的个数
};
struct evmap_signal {
	struct event_list events; //数组中指针所指向evmap_signal结构体,结构体中为event事件链表
};

io事件队列
在这里插入图片描述

io事件队列:分配空间为最大fd,index为对应fd的值,相同的fd事件用链表进行串联,当fd有对应的事件发生时,就遍历fd对应的链表,将对应的事件加入激活事件队列中。

struct evmap_io {
	struct event_list events; //数组中的指针指向evmap_io结构体
	ev_uint16_t nread;  //记录链表中io事件有几个读事件,如果读写事件都有则新加入io事件就不用调用epoll_ctl
	ev_uint16_t nwrite; //记录链表中io事件有几个写事件
};

激活事件队列
在这里插入图片描述

激活事件队列:分配空间为设置event事件的最大优先级值,index为对应的优先级,index越小越先执行,监听信号事件的fd设置成0。每次有激活事件就按照优先级加入到激活事件队列中,执行对应回调函数时就从小到大遍历index,同时遍历对应的链表。

struct event_list *activequeues;

注册事件队列
注册时间队列:为双向链表将所有的事件串联起来,用于判断是否还有未激活的事件。
在这里插入图片描述
libevent使用的双向链表结构详细情况如下文:
TAILQ链表队列详解

三、源码分析

event_base_new

struct event_base *
event_base_new_with_config(const struct event_config *cfg)
{
	int i;
	struct event _base *base;
...
	TAILQ_INIT(&base->eventqueue);
	base->sig.ev_signal_pair[0] = -1;
	base->sig.ev_signal_pair[1] = -1;
...
	evmap_signal_initmap(&base->sigmap); //初始化信号事件队列
...

	base->evbase = NULL;
	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))  //选择使用的网络模型,epoll/poll/iocp默认epoll
				continue;
			if ((eventops[i]->features & cfg->require_features)
			    != cfg->require_features)  //选择使用水平触发还是边缘触发,默认边缘触发
				continue;
		}
...
		base->evsel = eventops[i]; //找到对应的多路复用方法
		base->evbase = base->evsel->init(base); //调用方法中的初始化
	}
...
	/* allocate a single active event queue */
	if (event_base_priority_init(base, 1) < 0) { //初始化激活事件队列,激活事件队列数组大小为1
		event_base_free(base);
		return NULL;
	}
...
	return (base);
}

base->evbase = base->evsel->init(base);
初始化信号事件

static const struct eventop epollops_changelist = {
	"epoll (with changelist)",
	epoll_init,
	event_changelist_add,
	event_changelist_del,
	epoll_dispatch,
	epoll_dealloc,
	1, /* need reinit */
	EV_FEATURE_ET|EV_FEATURE_O1,
	EVENT_CHANGELIST_FDINFO_SIZE
};
static void *
epoll_init(struct event_base *base)
{
	int epfd;
	struct epollop *epollop;

	/* Initialize the kernel queue.  (The size field is ignored since
	 * 2.6.8.) */
	if ((epfd = epoll_create(32000)) == -1) {
		if (errno != ENOSYS)
			event_warn("epoll_create");
		return (NULL);
	}

	evutil_make_socket_closeonexec(epfd);

	if (!(epollop = mm_calloc(1, sizeof(struct epollop)))) {
		close(epfd);
		return (NULL);
	}

	epollop->epfd = epfd;

	/* Initialize fields */
	epollop->events = mm_calloc(INITIAL_NEVENT, sizeof(struct epoll_event));
	if (epollop->events == NULL) {
		mm_free(epollop);
		close(epfd);
		return (NULL);
	}
	epollop->nevents = INITIAL_NEVENT;
...
	evsig_init(base);//初始化信号事件
	return (epollop);
}

evsig_init
将信号事件转换成io事件

int
evsig_init(struct event_base *base)
{
	if (evutil_socketpair( //初始化管道用于信号通知
		    AF_UNIX, SOCK_STREAM, 0, base->sig.ev_signal_pair) == -1) {
...
		return -1;
	}

	evutil_make_socket_closeonexec(base->sig.ev_signal_pair[0]);
	evutil_make_socket_closeonexec(base->sig.ev_signal_pair[1]);
	base->sig.sh_old = NULL;
	base->sig.sh_old_max = 0;

	evutil_make_socket_nonblocking(base->sig.ev_signal_pair[0]); //管道设置非阻塞
	evutil_make_socket_nonblocking(base->sig.ev_signal_pair[1]);
	event_assign(&base->sig.ev_signal, base, base->sig.ev_signal_pair[1],
		EV_READ | EV_PERSIST, evsig_cb, base); //初始化管道的读事件,设置persist保证能够持续对信号进行处理

	base->sig.ev_signal.ev_flags |= EVLIST_INTERNAL;
	event_priority_set(&base->sig.ev_signal, 0); //该管道事件的优先级设置成0,加入激活事件队列index=0

	base->evsigsel = &evsigops; //注册信号事件的方法

	return 0;
}

evsig_init—>evsig_cb
有信号来时会调用evsig_cb回调函数

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) {
		n = recv(fd, signals, sizeof(signals), 0); //管道的另一端发送的为激活的信号值,这里读取所有激活的信                    	号值到数组中
		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) {
			/* XXX warn? */
			break;
		}
		for (i = 0; i < n; ++i) {  //将信号发生的次数记录ncaught数组中,index为信号值,内容为次数
			ev_uint8_t sig = signals[i];
			if (sig < NSIG)
				ncaught[sig]++; //方便处理几个相同信号的情况,几个相同信号就调用几次callback
		}
	}
	EVBASE_ACQUIRE_LOCK(base, th_base_lock);
	for (i = 0; i < NSIG; ++i) {
		if (ncaught[i])
			evmap_signal_active(base, i, ncaught[i]); //将激活的信号加入到激活事件队列中
	}
	EVBASE_RELEASE_LOCK(base, th_base_lock);
}

evmap_signal_active
加入激活事件队列中

void
evmap_signal_active(struct event_base *base, evutil_socket_t sig, int ncalls)
{
	struct event_signal_map *map = &base->sigmap;
	struct evmap_signal *ctx;
	struct event *ev;

	EVUTIL_ASSERT(sig < map->nentries);
	GET_SIGNAL_SLOT(ctx, map, sig, evmap_signal);//通过信号值在信号事件队列中找到对应的链表

	TAILQ_FOREACH(ev, &ctx->events, ev_signal_next)//遍历链表中的所有事件
		event_active_nolock(ev, EV_SIGNAL, ncalls);//将事件加入激活队列当中
}

event_active_nolock
加入激活队列中

void
event_active_nolock(struct event *ev, int res, short ncalls)
{
	struct event_base *base;
...
	if (ev->ev_events & EV_SIGNAL) {
		ev->ev_ncalls = ncalls;//设置该事件的回调函数会触发几次
		ev->ev_pncalls = NULL;
	}
	event_queue_insert(base, ev, EVLIST_ACTIVE);
}

static void
event_queue_insert(struct event_base *base, struct event *ev, int queue)
{
...
	ev->ev_flags |= queue;
	switch (queue) {
	case EVLIST_INSERTED:
		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);  //信号事件加入到激活事件队列中,这里的优先级和管道的io优先级是不同的
		break;
...
	default:
		event_errx(1, "%s: unknown queue %x", __func__, queue);
	}
}

evsignal_new

初始化信号事件

#define evsignal_new(b, x, cb, arg)				\
	event_new((b), (x), EV_SIGNAL|EV_PERSIST, (cb), (arg)) //EV_PERSIST表示信号能够被反复触发
	
struct event *
event_new(struct event_base *base, evutil_socket_t fd, short events, void (*cb)(evutil_socket_t, short, void *), void *arg)
{
	struct event *ev;
	ev = mm_malloc(sizeof(struct event));
	if (ev == NULL)
		return (NULL);
	if (event_assign(ev, base, fd, events, cb, arg) < 0) { //初始化一个信号事件
		mm_free(ev);
		return (NULL);
	}
	return (ev);
}

int
event_assign(struct event *ev, struct event_base *base, evutil_socket_t fd, short events, void (*callback)(evutil_socket_t, short, void *), void *arg)
{
	if (!base)
		base = current_base;

	_event_debug_assert_not_added(ev);

	ev->ev_base = base;

	ev->ev_callback = callback;
	ev->ev_arg = arg;
	ev->ev_fd = fd;
	ev->ev_events = events;
	ev->ev_res = 0;
	ev->ev_flags = EVLIST_INIT;
	ev->ev_ncalls = 0;
	ev->ev_pncalls = NULL;

	if (events & EV_SIGNAL) { //设置的信号事件不能和read/write并列
		if ((events & (EV_READ|EV_WRITE)) != 0) {
			event_warnx("%s: EV_SIGNAL is not compatible with "
			    "EV_READ or EV_WRITE", __func__);
			return -1;
		}
		ev->ev_closure = EV_CLOSURE_SIGNAL; //设置信号事件的结束标志
	} else {
...
	}
	if (base != NULL) {
		ev->ev_pri = base->nactivequeues / 2; //默认将信号事件放在激活事件队列的中间索引位置
	}
	return 0;
}

event_add

static inline int
event_add_internal(struct event *ev, const struct timeval *tv,
    int tv_is_absolute)
{
	struct event_base *base = ev->ev_base;
	int res = 0;
	int notify = 0;
...
	if ((ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL)) &&
	    !(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE))) {
		if (ev->ev_events & (EV_READ|EV_WRITE))
			res = evmap_io_add(base, ev->ev_fd, ev); //时间加入io事件队列
		else if (ev->ev_events & EV_SIGNAL)
			res = evmap_signal_add(base, (int)ev->ev_fd, ev); //加入信号事件队列
		if (res != -1)
			event_queue_insert(base, ev, EVLIST_INSERTED); //加入注册事件队列
	}
...
	return (res);
}

evmap_signal_add
将信号事件加入信号事件队列中

int
evmap_signal_add(struct event_base *base, int sig, struct event *ev)
{
	const struct eventop *evsel = base->evsigsel;//获得信号事件的处理方法
	struct event_signal_map *map = &base->sigmap;
	struct evmap_signal *ctx = NULL;

	if (sig >= map->nentries) {
		if (evmap_make_space( //存放信号事件队列的数组容量不够分配内存
			map, sig, sizeof(struct evmap_signal *)) == -1)
			return (-1);
	}
	GET_SIGNAL_SLOT_AND_CTOR(ctx, map, sig, evmap_signal, evmap_signal_init,
	    base->evsigsel->fdinfo_len);//在信号事件队列中根据信号值找到对应的链表ctx

	if (TAILQ_EMPTY(&ctx->events)) {
		if (evsel->add(base, ev->ev_fd, 0, EV_SIGNAL, NULL) //调用信号事件方法evsig_add
		    == -1)
			return (-1);
	}

	TAILQ_INSERT_TAIL(&ctx->events, ev, ev_signal_next);//信号事件插入对应的信号事件队列
	return (1);
}

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;
...
	evsig_base = base;
	evsig_base_n_signals_added = ++sig->ev_n_signals_added; //添加信号事件的个数
	evsig_base_fd = base->sig.ev_signal_pair[0]; //管道写端保存成全局的静态变量

	if (_evsig_set_handler(base, (int)evsignal, evsig_handler) == -1) {//设置该信号的捕捉函数
		goto err;
	}
	if (!sig->ev_signal_added) { //用于信号的管道读端没有监听
		if (event_add(&sig->ev_signal, NULL))  //将管道的读端事件加入io事件队列中并epoll监听
			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
设置对应信号的捕捉函数

int
_evsig_set_handler(struct event_base *base,
    int evsignal, void (__cdecl *handler)(int))
{
#ifdef _EVENT_HAVE_SIGACTION
	struct sigaction sa;
#else
	ev_sighandler_t sh;
#endif
	struct evsig_info *sig = &base->sig;
	void *p;

	if (evsignal >= sig->sh_old_max) { //信号值大于用于保存旧信号值存放的数组,则分配空间
		int new_max = evsignal + 1;
		event_debug(("%s: evsignal (%d) >= sh_old_max (%d), resizing",
			    __func__, evsignal, sig->sh_old_max));
		p = mm_realloc(sig->sh_old, new_max * sizeof(*sig->sh_old));
		if (p == NULL) {
			event_warn("realloc");
			return (-1);
		}

		memset((char *)p + sig->sh_old_max * sizeof(*sig->sh_old),
		    0, (new_max - sig->sh_old_max) * sizeof(*sig->sh_old));

		sig->sh_old_max = new_max;
		sig->sh_old = p;
	}

	/* allocate space for previous handler out of dynamic array */
	sig->sh_old[evsignal] = mm_malloc(sizeof *sig->sh_old[evsignal]);//初始化数组的二级指针
	if (sig->sh_old[evsignal] == NULL) {//
		event_warn("malloc");
		return (-1);
	}

	/* save previous handler and setup new handler */
#ifdef _EVENT_HAVE_SIGACTION
	memset(&sa, 0, sizeof(sa));
	sa.sa_handler = handler;
	sa.sa_flags |= SA_RESTART;
	sigfillset(&sa.sa_mask);

	if (sigaction(evsignal, &sa, sig->sh_old[evsignal]) == -1) {//注册信号捕捉函数,并保存原有的信号处理动作到数组中。
		event_warn("sigaction");
		mm_free(sig->sh_old[evsignal]);
		sig->sh_old[evsignal] = NULL;
		return (-1);
	}
#else
	if ((sh = signal(evsignal, handler)) == SIG_ERR) {//作用同上
		event_warn("signal");
		mm_free(sig->sh_old[evsignal]);
		sig->sh_old[evsignal] = NULL;
		return (-1);
	}
	*sig->sh_old[evsignal] = sh;
#endif

	return (0);
}

evsig_handler
将信号值通过管道的一端进行发送

static void __cdecl
evsig_handler(int sig)
{
...
#ifndef _EVENT_HAVE_SIGACTION
	signal(sig, evsig_handler);
#endif
	msg = sig;
	send(evsig_base_fd, (char*)&msg, 1, 0);//将信号写入管道的写端
	errno = save_errno;
...
}

event_base_dispatch

事件主循环

int
event_base_dispatch(struct event_base *event_base)
{
	return (event_base_loop(event_base, 0));
}

int
event_base_loop(struct event_base *base, int flags)
{
	const struct eventop *evsel = base->evsel;
	struct timeval tv;
	struct timeval *tv_p;
	int res, done, retval = 0;
...
	if (base->sig.ev_signal_added && base->sig.ev_n_signals_added)
		evsig_set_base(base);

	done = 0;

	base->event_gotterm = base->event_break = 0;

	while (!done) {
		base->event_continue = 0;
...
		res = evsel->dispatch(base, tv_p);//有信号触发读管道的信号事件,并将对应的信号事件加入到激活事件队列中
		if (res == -1) {
			event_debug(("%s: dispatch returned unsuccessfully.",
				__func__));
			retval = -1;
			goto done;
		}
...
		if (N_ACTIVE_CALLBACKS(base)) {
			int n = event_process_active(base);//处理激活事件队列
			if ((flags & EVLOOP_ONCE)
			    && N_ACTIVE_CALLBACKS(base) == 0
			    && n != 0)
				done = 1;
		} else if (flags & EVLOOP_NONBLOCK)
			done = 1;
	}
	event_debug(("%s: asked to terminate loop.", __func__));

done:
	clear_time_cache(base);
	base->running_loop = 0;

	EVBASE_RELEASE_LOCK(base, th_base_lock);

	return (retval);
}

event_process_active
处理激活队列中的信号事件


static int
event_process_active(struct event_base *base)
{
	struct event_list *activeq = NULL;
	int i, c = 0;

	for (i = 0; i < base->nactivequeues; ++i) {//遍历激活事件队列中的数组获得链表
		if (TAILQ_FIRST(&base->activequeues[i]) != NULL) {
			base->event_running_priority = i;
			activeq = &base->activequeues[i];
			c = event_process_active_single_queue(base, activeq);//处理某一信号的对应的所有信号事件
...
	}
	base->event_running_priority = -1;
	return c;
}

static int
event_process_active_single_queue(struct event_base *base,
    struct event_list *activeq)
{
	struct event *ev;
	int count = 0;
	for (ev = TAILQ_FIRST(activeq); ev; ev = TAILQ_FIRST(activeq)) {
		if (ev->ev_events & EV_PERSIST) //信号事件默认有EV_PERSIST
			event_queue_remove(base, ev, EVLIST_ACTIVE);//信号事件从激活事件队列中删除,该信号能够反复触发
		else
			event_del_internal(ev); //信号事件从注册事件队列、激活事件队列、信号事件队列中删除该信号事件,该信号只触发一次
		switch (ev->ev_closure) {
		case EV_CLOSURE_SIGNAL:
			event_signal_closure(base, ev); //调用信号事件的回调函数
			break;
		case EV_CLOSURE_PERSIST:
			event_persist_closure(base, ev);
			break;
		default:
		case EV_CLOSURE_NONE:
			EVBASE_RELEASE_LOCK(base, th_base_lock);
			(*ev->ev_callback)(
				ev->ev_fd, ev->ev_res, ev->ev_arg);
			break;
		}
...
	return count;
}
static void
event_queue_remove(struct event_base *base, struct event *ev, int queue)
{
	ev->ev_flags &= ~queue;
	switch (queue) {
	case EVLIST_INSERTED:
	...
		break;
	case EVLIST_ACTIVE:
		base->event_count_active--;
		TAILQ_REMOVE(&base->activequeues[ev->ev_pri],
		    ev, ev_active_next);//从激活事件队列中删除信号事件
		break;
	case EVLIST_TIMEOUT:
...
		break;
	default:
		event_errx(1, "%s: unknown queue %x", __func__, queue);
	}
}

static inline void
event_signal_closure(struct event_base *base, struct event *ev)
{
	short ncalls;
	ncalls = ev->ev_ncalls;
	if (ncalls != 0)
		ev->ev_pncalls = &ncalls;

	while (ncalls) { //对应的信号事件触发n次
		ncalls--;
		ev->ev_ncalls = ncalls;
		if (ncalls == 0)
			ev->ev_pncalls = NULL;
		(*ev->ev_callback)(ev->ev_fd, ev->ev_res, ev->ev_arg); //调用信号事件的回调函数
...
	}
}

总结

本文对信号事件的使用和源码进行了分析,但是使用中还有一些注意事项需要后面分析。

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

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

相关文章

【数据结构与算法】深入浅出:单链表的实现和应用

&#x1f331;博客主页&#xff1a;青竹雾色间. &#x1f618;博客制作不易欢迎各位&#x1f44d;点赞⭐收藏➕关注 ✨人生如寄&#xff0c;多忧何为 ✨ 目录 前言 单链表的基本概念 节点 头节点 尾节点 单链表的基本操作 创建单链表 头插法&#xff1a; 尾插法&#…

测试一下 Anthropic 宣称超过 GPT-4 的 Claude 3 Opus

测试一下 Anthropic 宣称超过 GPT-4 的 Claude 3 Opus 0. 引言1. 测试 Claude 3 Opus 0. 引言 今天测试一下 Anthropic 发布的 Claude 3 Opus。 3月4日&#xff0c;Anthropic 宣布推出 Claude 3 型号系列&#xff0c;该系列在广泛的认知任务中树立了新的行业基准。该系列包括…

Koa: 打造高效、灵活的Node.js后端 (介绍与环境部署)

在上一篇文章中&#xff0c;我们了解了Node.js的基础知识&#xff0c;今天我们将进一步学习Node.js 较新的一个轻量级Web框架Koa&#xff0c;一起创建NodeJS后端服务器吧&#xff01; 一、介绍 Koa是一个新生代Node.js Web框架&#xff0c;由Express原团队成员开发&#xff0c…

redis最新版本在Windows系统上的安装

一、说明 这次安装操作主要是根据redis官网说明&#xff0c;一步步安装下来的&#xff0c;英语比较好的同学&#xff0c;可以直接看文章底部的超链接1&#xff0c;跳到官网按步操作即可。 目前redis的最新稳定版本为redis7.2。 二、Windows环境改造 Redis在Windows上不被官方…

Django高级之-cookie-session-token

Django高级之-cookie-session-token 发展史 1、很久很久以前&#xff0c;Web 基本上就是文档的浏览而已&#xff0c; 既然是浏览&#xff0c;作为服务器&#xff0c; 不需要记录谁在某一段时间里都浏览了什么文档&#xff0c;每次请求都是一个新的HTTP协议&#xff0c; 就是请…

pytorch(四、五)用pytorch实现线性回归和逻辑斯蒂回归(分类)

文章目录 线性回归代码过程准备数据设计模型设计构造函数与优化器训练过程训练代码和结果pytorch中的Linear层的底层原理&#xff08;个人喜欢&#xff0c;不用看&#xff09;普通矩阵乘法实现Linear层实现 回调机制 逻辑斯蒂回归模型损失函数代码和结果 线性回归 代码过程 训…

【Python】成功解决TypeError: ‘tuple‘ object does not support item assignment

【Python】成功解决TypeError: ‘tuple’ object does not support item assignment &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程&am…

wps没保存关闭了怎么恢复数据?数据恢复这样做

WPS文件已成为我们不可或缺的一部分。从撰写报告、制作表格到展示演讲&#xff0c;WPS系列软件为我们提供了极大的便利。然而正如任何电子设备都可能遇到的问题一样&#xff0c;WPS文件有时也可能出现损坏的情况&#xff0c;这无疑给我们的工作带来了不小的困扰。 那么当WPS文件…

Manz高压清洗机S11-028GCH-High Quality Cleaner 操作使用说明492页

Manz高压清洗机S11-028GCH-High Quality Cleaner 操作使用说明492页

基于php的用户登录实现(v1版)(持续迭代)

目录 版本说明 数据库连接 登录页面&#xff1a;login.html 登录处理实现&#xff1a;login.php 用户欢迎页面&#xff1a;welcome.php 用户注册页面&#xff1a;register.html 注册执行&#xff1a;DoRegister.php 版本说明 v1实现功能&#xff1a; 数据库连接&#x…

基于UDP实现的网络聊天室

服务器&#xff1a; #include <myhead.h> struct msg {char type;char name[20];char text[1024]; };int main(int argc, const char *argv[]) {if(argc!3){printf("input error\n");printf("./a.out IP地址 端口号\n");return -1;}//1、创建用于通…

美国国家安全局(NSA)和美国政府将Delphi/Object Pascal列为推荐政府机构和企业使用的内存安全编程语言

上周&#xff0c;美国政府发布了《回到构建块&#xff1a;通往安全和可衡量软件的道路》的报告。本报告是美国网络安全战略的一部分&#xff0c;重点关注多个领域&#xff0c;包括内存安全漏洞和质量指标。 许多在线杂志都对这份报告发表了评论&#xff0c;这些杂志强调了对 C…

css clip-path polygon属性实现直角梯形

2024.3.8今天我学习了如何用css实现直角梯形的效果&#xff0c; 效果&#xff1a; 具体实现原理&#xff1a; 一、需要三个div&#xff1a; 外面一个大的div&#xff0c;里面左右两个小的div 我们需要先把第一个div变成直角梯形&#xff1a; 大概是这样&#xff0c;设置好之…

web服务之虚拟主机功能

华子目录 概述基于IP地址的虚拟原理实验 基于不同端口号的虚拟主机原理实验 基于域名的虚拟主机原理域名解析实验 概述 如果每台运行 Linux 系统的服务器上只能运行一个网站&#xff0c;那么人气低、流量小的草根站长就要被迫承担着高昂的服务器租赁费用了&#xff0c;这显然也…

项目申报书引言部分

文献引用方式&#xff1a; 张三 等&#xff0c;2024&#xff1b; Zhang S et al.,2015&#xff1b; &#xff08;中文是中文逗号&#xff0c;英文是英文逗号&#xff09;

【你也能从零基础学会网站开发】Web建站之HTML+CSS入门篇 CSS常用属性

&#x1f680; 个人主页 极客小俊 ✍&#x1f3fb; 作者简介&#xff1a;web开发者、设计师、技术分享 &#x1f40b; 希望大家多多支持, 我们一起学习和进步&#xff01; &#x1f3c5; 欢迎评论 ❤️点赞&#x1f4ac;评论 &#x1f4c2;收藏 &#x1f4c2;加关注 CSS常用属性…

ARM64汇编04 - 条件码

关于分支控制与条件码的作用可以去看 《CSAPP》的第 3.6 节&#xff0c;讲的非常清楚&#xff0c;建议看看&#xff0c;这里就不重复了。 我们直接使用一个例子来简单理解汇编是如何实现分支控制的&#xff1a; #include <stdio.h> #include <stdlib.h> #include…

【MATLAB第98期】基于MATLAB的MonteCarlo蒙特卡罗结合kriging克里金代理模型的全局敏感性分析模型【更新中】

【MATLAB第98期】基于MATLAB的Monte Carlo蒙特卡罗结合kriging克里金代理模型的全局敏感性分析模型【更新中】 PS:因内容涉及较多&#xff0c;所以一时半会更新不完 后期会将相关原理&#xff0c;以及多种功能详细介绍。 麻烦点赞收藏&#xff0c;及时获取更新消息。 引言 在…

Easticsearch性能优化之索引优化

Easticsearch性能优化之索引优化 一、合理的索引设计二、合理的分片和副本三、合理的索引设置 对于性能优化&#xff0c;Elasticsearch&#xff08;以下简称ES&#xff09;的索引优化是提高性能的关键因素之一。合理的设计索引&#xff0c;合理的分片和副本以及合理的缓存设置等…

VSCode报错:/bin/sh: python: command not found

背景 以前都是直接用txt写python&#xff0c;然后直接命令行运行。 这次涉及的代码较多&#xff0c;决定用编译器。 写好的一段python点击运行报错&#xff01; 问题描述 因为我本地安装的是python3&#xff0c;但是vscode用的是另一个路径的python&#xff0c;所以找不到 解…