Go源码--context包

简介

Context 是go语言比较重要的且也是比较复杂的一个结构体,Context主要有两种功能:

  • 取消信号:包括直接取消(涉及的结构体:cancelCtx ; 涉及函数:WithCancel)和携带截止日期的取消(涉及结构体:timeCtx,cancelCtx;涉及函数:WithTimeout/WithDeadline/WithDeadlineCause);一般用来控制主从协程,当主协程执行取消操作时,当然希望从携程也执行取消操作(或者其他设定的操作),这样主协程可以对从协程的生命周期有控制权。
  • 传递消息:包括跨API边界和进程间传递(涉及结构体:valueCtx;涉及函数:WithValue);主从协程可以共享数据,这样在Context形成的带回溯的树状结构中,通过回溯任何协程可以根据key获取其他协程注册在valueCtx(继承了context)结构体中的value,从而做到在整个主从协程中共享数据。

由于上述涉及的结构体入参 都包含Context 返回值也包含Context,所以继承自Context的结构体 都可以是入参,这样上述函数就可以组合使用。WithValue一般和/WithCancel/WithTimeout/WithDeadline/WithDeadlineCause搭配使用,这样既可以控制从协程生命周期,又可以传递数据。这几个结构体的组合可以产生各种应用场景。我后续会介绍这种组合。下面我们先梳理下几种重要的接口和结构体吧。

重要结构体

Context接口

Context接口是梦开始的地方,拥有最崇高的地位。我们来看下其结构体

type Context interface {
// Deadline 返回代表此上下文完成工作时应取消的时间。
	Deadline() (deadline time.Time, ok bool)
	Done() <-chan struct{}  // 返回 chan类型 如果返回的不是关闭的 则阻塞 否则 返回关闭的chan 不阻塞
	Err() error  // 返回Done不阻塞的原因 如果是 Canceled的则 返回cancel的原因 所以 这个参数不为空 不意味着是运行出错了
	Value(key any) any   // 返回 key对应的value,可以递归调用。用于查找整个context树上跟key匹配的value
}
emptyCtx 结构体

emptyCtx 采用空值实现了 Context的四个函数

type emptyCtx struct{}
backgroundCtx 结构体
type backgroundCtx struct{ emptyCtx }

其采用组合的方式来包含emptyCtx,从而可以调用其函数值,也就是说实现了Context这个结构体,这就是结构体嵌入的优势。todoCtx结构体 跟backgroundCtx 结构一样。其主要用来作为树形结构的根节点,所以其实现的context函数都是空实现。

cancelCtx 结构体

介绍 这个结构体前我们先来看下 canceler 接口

type canceler interface {
	cancel(removeFromParent bool, err, cause error) // 取消
	Done() <-chan struct{}
}

canceler 的实现是 *cancelCtx和 *timerCtx,主要为context类型结构体进行功能补充,可以进行取消操作。

cancelCtx 结构体如下:

// 可以被取消,当被取消,也会取消任何实现了canceler结构体的孩子Context
type cancelCtx struct {
	Context

	mu       sync.Mutex            // 保护下面的属性
	done     atomic.Value          // done存了chan struct{},被懒惰创建(使用时才创建)
	children map[canceler]struct{} // 实现了 canceler接口的孩子map,key有用,value没用。主要用来组成树形结构,因为key是cancler类型的
								    // 所以能作为key(除了根节点的树节点)的只有cancelCtx,timeCtx和嵌入这两个任何一个的结构体;
	err      error                 // 
	cause    error                 // 
}

cancelCtx主要用来主协程控制从协程的生命周期,主协程调用cance()函数后其chan关闭,其子协程也会执行 Done()(关闭的chan不再阻塞)函数取消阻塞来执行后续逻辑。这里分两种情况,一种是只有一个With函数,子协程不产生新的context,也就是大家经常用的,第二种情况是在子协程使用With函数来产生孩子节点,这时children就会发挥作用,会形成带回溯的树形结构。下文会有详细讲解。
跟cancelCtx直接相关的函数主要是 WithCancel。

timerCtx 结构体

timerCtx结构体有定时器和截止时间两个参数。它嵌入了一个cancelCtx结构体来实现Done和Err方法。通过停止计时器(到时间了)然后委托给cancelCtx.cancel来实现cancel,也就是说timerCtx的cancel是通过调用cancelCtx的cancel方法来实现的。我们来看下其结构体:

type timerCtx struct {
	cancelCtx // 内嵌取消context
	timer *time.Timer  // 定时器 用来定时取消操作

	deadline time.Time // 返回取消的时间戳
}

可以看到 由于内嵌 取消context其本身可以代表cancelCtx类型,可以断言成功。(这个特性可以将两者组合使用)
跟timerCtx直接相关的函数有 WithTimeout/WithDeadline/WithDeadlineCause三个。要再次强调下 只要是context的实现结构体都可以作为With系列函数的入参,所以不同context变体的组合可以实现各种复杂的主从控制和信息传递操作。以上两个结构体介绍的都是主从控制方面的,现在我们来看下传递参数相关的结构体。

valueCtx 结构体

其结构体如下:

type valueCtx struct {
	Context
	key, val any
}

这个结构体带来一个key-value对,其为key实现了Vaule函数,主要用来在树形结构中查找key对应的value。其他的方法调用都委托给了嵌入的Context。这样 通过 valueCtx和timerCtx或者cancelCtx的组合就能即传递参数又控制子协程。当然vauleCtx也可以嵌入其他Context来实现参数的传递,例如我们常用的 go http包。
我们来简要绘制一个图来看下如上结构体之间的关系。
在这里插入图片描述
简要介绍了结构体 接下来我们来介绍几种重要的函数,函数都是名如其人。

WithCancel

其英文解释如下:
// WithCancel returns a copy of parent with a new Done channel. The returned context’s Done channel is closed when the returned cancel function is called or when the parent context’s Done channel is closed, whichever happens first.Canceling this context releases resources associated with it, so code should call cancel as soon as the operations running in this Context complete.
翻译成中文是:
WithCancel返回带有Done 通道的上下文的副本,其实就是新建一个cancelCtx将其放入children key中。当返回的取消函数调用或者当父上下文通道已经关闭时,返回的上下文通道也会关闭。看哪个先发生吧。
其代码如下:

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
	c := withCancel(parent)  // 创造新cancelCtx 如果入参 ctx是cancelCtx类型 就将 新的cancelCtx放入其children key中
	return c, func() { c.cancel(true, Canceled, nil) } // 返回新 cancelCtx和 取消函数 
}

其使用例子如下:


func TestWithCancel(t *testing.T) {
	var wg sync.WaitGroup
	ctx, concel := context.WithCancel(context.Background())
	wg.Add(1)
	go doSomthing(ctx, &wg)
	time.Sleep(3 * time.Second)
	concel()
	wg.Wait()
}

func doSomthing(ctx context.Context, wg *sync.WaitGroup) {
	defer wg.Done()
 	for {
		select {
		case <-ctx.Done():
			time.Sleep(2 * time.Second)

			fmt.Println("playing")
			return
		default:
			time.Sleep(1 * time.Second)
			fmt.Println("I am working")

		}

	}

}

其输出如下:

I am working
I am working
playing

我们可以看到 当还没有执行cancel时 doSoomthing 中 ctx.Done() 返回的chan是阻塞未关闭状态,当调用cancel()函数时,chan关闭,因为doSomthing()中入参是 context是一个接口,接口是入参,就会携带本身类型和其实现的结构体的指针。所以 ctx中的 chan就是唯一的,当主协程调用cancel关闭chan时,子协程ctx.Done函数 返回关闭的结构体也就不再阻塞。还记得我在介绍cancelCtx结构体时,最后一段的描述吗,这时候因为就一个With函数不会产生新的 cancelCtx。
接下来我们来深入剖析下WithCancel源码。
WithCancel 的withCancel函数源码如下:

func withCancel(parent Context) *cancelCtx {
	if parent == nil {
		panic("cannot create context from nil parent")
	}
	// 创建一个新的 结构体 作为 符合条件的父节点 child; 树结构
	c := &cancelCtx{}

	// 如果符合某条件 将 子节点插入树中
	c.propagateCancel(parent, c)
	return c
}

其中 propagateCancel函数源码如下:

// propagateCancel 负责在父上下文被取消时,使子上下文也被取消。它设置了 cancelCtx 的父上下文(可以理解成树上的父子节点,制定一个节点的父节点和子节点)。
func (c *cancelCtx) propagateCancel(parent Context, child canceler) {
	c.Context = parent // 回溯 指向父节点 删除本子节点用 方便GC 和 通过Value方法回溯

	// 父节点 主要是context.Background() 节点 没有必要取消 它是根节点(设想下 链表的根节点是不是都是空的,上文中TestWithCancel函数 执行到 
	// 这边就返回了 因为parent 是 context.Background() 是根节点 且 其Done是空实现)
	done := parent.Done()  
	if done == nil {
		return // parent is never canceled
	}
    // 
	// 父节点已经取消 后续就不用再执行了 因为 按照 context 的设计理念 父节点取消 肯定是想子节点也取消
	select {
	case <-done:
		// parent is already canceled
		child.cancel(false, parent.Err(), Cause(parent))
		return
	default:
	}

	// 将 parent Context 类型的 转换成 cancelCtx类型 然后构建 树形传递链(一夫多子 一子一父) map可能共享 所以需要枷锁
	if p, ok := parentCancelCtx(parent); ok {
		// parent is a *cancelCtx, or derives from one.
		p.mu.Lock()
		if p.err != nil {
			// parent has already been canceled
			child.cancel(false, p.err, p.cause)
		} else {
			if p.children == nil {
				p.children = make(map[canceler]struct{})
			}
			p.children[child] = struct{}{}
		}
		p.mu.Unlock()
		return
	}

	// 略过
}

可以看到 上面有对孩子节点赋值的操作这是什么情况下发生的呢,接下来我们来看一个例子,是cancelCtx的另一个有传播树形链的使用场景。代码如下:



func doSomthing2(ctx context.Context, wg *sync.WaitGroup) {
	defer wg.Done()
	for {
		select {
		case <-ctx.Done():
			fmt.Println("playing2")
			return
		default:
			time.Sleep(1 * time.Second)
			fmt.Println("I am working2")

		}

	}

}

func doSomthing(ctx context.Context, wg *sync.WaitGroup) {
	defer wg.Done()
	ctx1, cancel1:= context.WithCancel(ctx)  // ctx 当入参 产生新的 ctx1 这时 ctx下文是 ctx1 上文是 Backgroud产生的空context
	defer cancel1() 
	go doSomthing2(ctx1, wg)

	for {
		select {
		case <-ctx.Done():
 			fmt.Println("playing")
			return
		default:
			time.Sleep(1 * time.Second)
			fmt.Println("I am working")

		}

	}

}

func TestWithCancel(t *testing.T) {
	var wg sync.WaitGroup
	ctx, concel := context.WithCancel(context.Background())
	wg.Add(2)
	go doSomthing(ctx, &wg)
	time.Sleep(3 * time.Second)
	concel()
	wg.Wait()
}

运行结果是:

I am working2
I am working
I am working
I am working2
I am working
playing
I am working2
playing2

可以看到 这时候 就形成了树形结构 我们来看下 形成的树:

在这里插入图片描述
如果 在子协程中 和子子协程中 运用With函数 进行嵌套最终可形成一颗很大的树,如图:
在这里插入图片描述
这时候 就形成比较复杂的主从控制方案,如果valueCtx再嵌套上cancelCtx,就可以根据key在树上寻找value(因为指针是双向的,可以根据value函数递归查找,只能往上找不能往下),这样可以做到参数的向下传递。但我们一般常用的是主协程一个With函数,从协程就引用主协程的ctx 也不嵌套,就全局唯一一个ctx,如同第一个例子。
其结构图如下:
在这里插入图片描述

WithValue

WithValue 基于 valueCtx结构体,主要用来父context给子context传递数据,也就是说子context可以获得其父context的内容 反之则不行。
一个例子:

func doSomthing(ctx context.Context, wg *sync.WaitGroup) {
	defer wg.Done()
	for {
		select {
		case <-ctx.Done():
			fmt.Println("playing")
			return
		default:
			time.Sleep(1 * time.Second)
			fmt.Println("I am working")
			fmt.Println(ctx.Value("key"))
		}

	}

}

func TestWithCancel(t *testing.T) {
	var wg sync.WaitGroup
	ctx:=context.WithValue(context.Background(),"key","value")
	ctx, concel := context.WithCancel(ctx)
	wg.Add(1)
	go doSomthing(ctx, &wg)
	time.Sleep(3 * time.Second)
	concel()
	wg.Wait()
}

运行结果如下:

I am working
value
I am working
value
I am working
value
playing

可以看到 主协程传递的是 key value 可以在子协程通过 Value(key)函数来获取value。通过valuetx嵌入cancelCtx可以得到一颗树形结构,如上图cancelCtx形成的多节点树形结构,由于valueCtx 可以嵌入 cancelCtx所以 可以加入其树形链条中。

其源码如下:

func WithValue(parent Context, key, val any) Context {
	if parent == nil {
		panic("cannot create context from nil parent")
	}
	if key == nil {
		panic("nil key")
	}
	if !reflectlite.TypeOf(key).Comparable() {
		panic("key is not comparable")
	}
	return &valueCtx{parent, key, val}
}

源码比较简单,valueCtx本身一般不会用来传播,一般都是嵌入其他的结构体 像cancelCtx,timerCtx 和其他的context,主要用来传递参数。子可以看所有的父参数,父不能看子,因为查找是递归查找。
valueCtx的主要功能是传递参数和查找参数,传递用的是 key,value。查找用的是Value函数 我们来看下Value函数。


func (c *valueCtx) Value(key any) any {
	if c.key == key {
		return c.val
	}
	return value(c.Context, key)
}

// 用于在整个context链条(带回溯的树结构)上 寻找 匹配的值 是一个公共方法
func value(c Context, key any) any {
	for {
		switch ctx := c.(type) {
		case *valueCtx:
			if key == ctx.key {
				return ctx.val // 匹配上key 返回
			}
			c = ctx.Context
		case *cancelCtx:
			if key == &cancelCtxKey {
				return c
			}
			c = ctx.Context
		case withoutCancelCtx:
			if key == &cancelCtxKey {
				// This implements Cause(ctx) == nil
				// when ctx is created using WithoutCancel.
				return nil
			}
			c = ctx.c
		case *timerCtx:
			if key == &cancelCtxKey {
				return &ctx.cancelCtx
			}
			c = ctx.Context
		case backgroundCtx, todoCtx:
			return nil
		default:
			return c.Value(key) // 回溯 递归查找
		}
	}
}

通过 上述代码 我们可以看到 其之所以可以共享参数 主要是由于有递归查找的原因 所以 子可以查找父传递的参数 但是反之不行。
另一个用处是在http包内 传递参数,感兴趣的可以查阅相关资料。
人为取消是不是有时候不爽啊,那就加入定时功能吧,所以 下面的几个函数本质上就是在cancelCtx功能的扩展,我们看其结构体timerCtx就包含了cancelCtx,只不过在其基础上加了时间维度。下面来看看涉及的几个结构体。

WithDeadlineCause

WithDeadlineCause 是time相关功能的基础函数,WithDeadline和 WithTimeout都是基于这个函数。我们来看下其源码:


// 定时取消操作
func WithDeadlineCause(parent Context, d time.Time, cause error) (Context, CancelFunc) {
	if parent == nil {
		panic("cannot create context from nil parent")
	}
	
	// 获取父context的取消时间,如果比传入时间早 就执行WithCancel取消逻辑
	if cur, ok := parent.Deadline(); ok && cur.Before(d) {
		// The current deadline is already sooner than the new one.
		return WithCancel(parent)
	}
	
	// 否则 就新建一个新timerCtx 并把取消时间赋给deadline
	c := &timerCtx{
		deadline: d,
	}
	// 构建 context树形结构 将c, parent父子化
	c.cancelCtx.propagateCancel(parent, c)
	// 开始定时 直到达到取消时间 执行取消操作
	dur := time.Until(d)
	if dur <= 0 {
		c.cancel(true, DeadlineExceeded, cause) // deadline has already passed
		return c, func() { c.cancel(false, Canceled, nil) }
	}
	c.mu.Lock()
	defer c.mu.Unlock()

	// dur时间过后 开始执行取消
	if c.err == nil {
		c.timer = time.AfterFunc(dur, func() {
			c.cancel(true, DeadlineExceeded, cause)
		})
	}
	// todo
	return c, func() { c.cancel(true, Canceled, nil) }
}

这个结构体不怎么使用 我们一般使用它两个变种。如下

WithDeadline

变种自WithDeadlineCause
源码如下:

func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
	return WithDeadlineCause(parent, d, nil)
}

只不过去掉了取消原因 因为是定时取消 原因不用说了
我们看一个例子

func doWork(ctx context.Context,wg * sync.WaitGroup) {
	defer wg.Done()
	select {
	case <-time.After(3 * time.Second):
		// 模拟操作需要3秒完成
		fmt.Println("操作完成")
	case <-ctx.Done():
		// 当上下文取消时会进入这个分支
		fmt.Println("操作被取消:", ctx.Err())
	}
}
func TestWithDeadline(t *testing.T) {
	// 设置截止时间为当前时间后2秒
	deadline := time.Now().Add(2 * time.Second)
	ctx, cancel := context.WithDeadline(context.Background(), deadline)
	defer cancel() // 确保在主函数返回前取消上下文
	// 模拟操作

	var wg sync.WaitGroup
	wg.Add(1)
	go doWork(ctx,&wg)
	wg.Wait()
}

运行结果如下:

操作被取消: context deadline exceeded

比较简单 我们来看最后一个

WithTimeout

其源码如下:

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
	return WithDeadline(parent, time.Now().Add(timeout)) 
}

我们看到这个函数 只是 TestWithDeadline一个特例,入参是一个时间戳,只不过后续需要转换成当前时间往后时间戳刻度作为定时时间。
所以上面的例子稍微修改下就可以使用

func doWork(ctx context.Context,wg * sync.WaitGroup) {
	defer wg.Done()
	select {
	case <-time.After(3 * time.Second):
		// 模拟操作需要3秒完成
		fmt.Println("操作完成")
	case <-ctx.Done():
		// 当上下文取消时会进入这个分支
		fmt.Println("操作被取消:", ctx.Err())
	}
}
func TestWithTimeout(t *testing.T) {
	// 设置截止时间为当前时间后2秒
 	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel() // 确保在主函数返回前取消上下文
	// 模拟操作

	var wg sync.WaitGroup
	wg.Add(1)
	go doWork(ctx,&wg)
	wg.Wait()
}

总结

如此 Context的所有变体和相关函数都介绍完毕了。我们来总结下。

  1. Context接口是内核,cancelCtx内嵌了Context并实现了其部分函数,来执行一些取消操作,而timerCtx内嵌了 cancelCtx从而可以定时取消。
  2. valueCtx内嵌了Context,其本身还有 键值对参数,从而可以用来传递参数。其之所以不内嵌cancelCtx主要是为了提供一个基础组合结构体,用户就可以内嵌各种context变体来传递参数,使用范围更广,而不只是限制在cancel变体。
  3. With函数入参和返回值都有Context所以各种变体可以进行组合。例如cancelCtx和valueCtx组合既可以控制从协程又可以向从协程传递参数。
  4. With函数只有在子协程中也执行,才能构建Context树,否则With参数传递Contest就是全局唯一一个ctx,因为With函数Context入参是接口,所以实际上传递的是Context类型和其实现结构体的指针,所以单纯传递Context不会产生值复制。

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

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

相关文章

【Java安装】windows10+JDK21+IDEA

文章目录 一、JDK安装1. 下载完成后按照自己需要的位置安装2. 配置环境变量2.1 JAVA_HOME变量2.2 PATH配置 3. 验证4. helloworld 二、IDEA安装三、IDEA-HelloWorld 一、JDK安装 JDK安装链接 1. 下载完成后按照自己需要的位置安装 2. 配置环境变量 2.1 JAVA_HOME变量 安装…

JVM原理(三):JVM对象回收判定机制与回收算法

如何判断一个对象是否存活(即是否还分配在堆中)&#xff0c;那看他是否还在用。 1. 引用计数算法 这是一种判断方式&#xff0c;相应的方法就是&#xff1a;如果一个对象被引用&#xff0c;那将被引用的对象中的一个计数器加一&#xff0c;引用失效就减一。在任何时刻引用计数…

QT实现GIF动图显示(小白版,可直接copy使用)

需要你自己提前设置好动图的位置&#xff0c;本例中存放于"/Users/PLA/PLA/PLA.gif widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QMovie> #include <QLabel>class Widget : public QWidget {Q_OBJECTpublic:explicit Wid…

mssql查询历史执行过的语句日志

SELECT deqs.creation_time,dest.text AS [SQL Text],deqs.execution_count,deqs.total_elapsed_time,deqs.total_worker_time FROM sys.dm_exec_query_stats AS deqs CROSS APPLY sys.dm_exec_sql_text(deqs.sql_handle) AS dest--where dest.text like %这个是我的条件&#…

基于Springboot的智慧养老中心管理系统

文章目录 项目介绍主要功能截图:部分代码展示设计总结项目获取方式🍅 作者主页:超级无敌暴龙战士塔塔开 🍅 简介:Java领域优质创作者🏆、 简历模板、学习资料、面试题库【关注我,都给你】 🍅文末获取源码联系🍅 项目介绍 基于Springboot的智慧养老中心管理系统,…

全景图三维3D模型VR全景上传展示H5开发

全景图三维3D模型VR全景上传展示H5开发 3D互动体验平台的核心功能概览 兼容广泛格式&#xff1a;支持OBJ、FBX、GLTF等主流及前沿3D模型格式的无缝上传与展示&#xff0c;确保创意无界。 动态交互探索&#xff1a;用户可自由旋转、缩放、平移模型&#xff0c;深度挖掘每一处…

默安逐日实验室:XDP的应用实践

1. 网络数据包是如何进入进计算机的 众所周知&#xff0c;网络数据包通常需要在TCP/IP协议栈中进行处理&#xff0c;但网络数据包并不直接进入TCP/IP协议栈&#xff1b;相反&#xff0c;他们直接进入网络接口。因此&#xff0c;在数据包进入 TCP/IP 堆栈之前&#xff0c;它们已…

HTML如何在图片上添加文字

HTML如何在图片上添加文字 当我们开发一个页面&#xff0c;插入图片时&#xff0c;需要有一组文字对图片进行描述。那么HTML中如何在图片上添加文字呢&#xff1f;这篇文章告诉你。 先让我们来看下效果图&#xff1a; 句子“这是一张夜空图片”被放置在了图片的左下角。 那么…

谷粒商城学习-10-docker安装mysql

文章目录 一&#xff0c;拉取MySQL镜像1&#xff0c;搜索MySQL的Docker镜像2&#xff0c;拉取MySQL镜像3&#xff0c;查看已经拉取的镜像 二&#xff0c;创建、启动MySQL容器1&#xff0c;使用docker run创建启动容器2&#xff0c;使用docker ps查看运行状态的容器3&#xff0c…

属性描述符初探——Vue实现数据劫持的基础

目录 属性描述符——Vue实现数据劫持的基础 一、属性描述符是什么&#xff1f; ​编辑 1.1、属性描述符示例 1.2、用属性描述符定义属性及获取对象的属性描述符 1.3、带有读取器和设置器的属性描述符 二、使用属性描述符的情景 2.1、封装和数据隐藏 使用getter和setter…

论文写作全攻略:Kimi辅助下的高效学术写作技巧

学境思源&#xff0c;一键生成论文初稿&#xff1a; AcademicIdeas - 学境思源AI论文写作 完成论文写作是一个多阶段的过程&#xff0c;涉及到不同的任务和技能。以下是按不同分类总结的向Kimi提问的prompt&#xff0c;以帮助你在论文写作过程中取得成功&#xff1a; 1. 选题与…

使用kali Linux启动盘轻松破解Windows电脑密码

破解分析文章仅限用于学习和研究目的&#xff1b;不得将上述内容用于商业或者非法用途&#xff0c;否则&#xff0c;一切后果请用户自负。谢谢&#xff01;&#xff01; 效果展示&#xff1a; 使用kali Linux可以轻松破解Windows用户及密码 准备阶段&#xff1a; &#xff08…

RedHat9 | kickstart无人值守批量安装

一、知识补充 kickstart Kickstart是一种用于Linux系统安装的自动化工具&#xff0c;它通过一个名为ks.cfg的配置文件来定义Linux安装过程中的各种参数和设置。 kickstart的工作原理 Kickstart的工作原理是通过记录典型的安装过程中所需人工干预填写的各种参数&#xff0c;…

配置基于用户认证的虚拟主机

添加账号abc [rootlocalhost conf.d]# htpasswd -c /etc/httpd/zhanghao abc New password: Re-type new password: Adding password for user abc添加账号tom [rootlocalhost conf.d]# htpasswd /etc/httpd/zhanghao tom New password: Re-type new password: Adding pa…

QT中QDomDocument读写XML文件

一、XML文件 <?xml version"1.0" encoding"UTF-8"?> <Begin><Type name"zhangsan"><sex>boy</sex><school>Chengdu</school><age>18</age><special>handsome</special>&l…

平安养老险蚌埠中心支公司开展“78奋力前行”健步走活动

7月4日&#xff0c;平安养老保险股份有限公司&#xff08;以下简称“平安养老险”&#xff09;蚌埠中心支公司组织员工在张公山公园开展“78奋力前行”健步走活动&#xff0c;传递保险行业的正能量&#xff0c;展现平安养老险的活力与风采。 平安养老险蚌埠中心支公司员工身着…

【瑞吉外卖 | day02】登录功能完善+员工信息的增改查

文章目录 瑞吉外卖 — day021. 完善登录功能1.1 问题分析1.2 实现 2. 新增员工功能2.1 分析2.2 程序执行过程2.3 程序执行中的异常处理 3. 员工信息分页查询3.1 程序执行过程 4. 启用/禁用员工账号4.1 需求分析4.2 程序执行过程4.3 代码修复 5. 编辑员工信息5.1 需求分析5.2 程…

基于DMAIC降低气缸体水套芯磕碰伤率

在制造业的激烈竞争中&#xff0c;产品质量的提升一直是企业追求的目标。气缸体作为汽车发动机的核心部件&#xff0c;其生产过程中的质量控制尤为重要。今天&#xff0c;深圳天行健企业管理咨询公司就来分享一下如何运用DMAIC&#xff08;定义、测量、分析、改进、控制&#x…

Logstash安装插件失败的问题

Logstash安装插件失败的问题 安装 logstash-output-jdbc 失败 报错为&#xff1a; Unable to download data from https://rubygems.org - Net::OpenTimeout: Failed to open TCP connection to rubygems.org:443 (execution expired) (https://rubygems.org/latest_specs.4.…

[GHCTF 2024 新生赛]UP+——入土为安的第一天

注意&#xff1a;这道题需要脱壳&#xff0c;而且需要改特征值&#xff0c;详细请看 [LitCTF 2024]hello_upx——入土为安的第一天-CSDN博客 脱完壳发现有256这个特殊的数&#xff0c;是rc4类型的题&#xff0c;最后有一个异或 a "9F041CEFA92386B6F56F27B96155FD42&qu…