containerd启动过程

news2024/11/15 7:06:12

github: https://github.com/containerd/containerd

在这里插入图片描述

1. 前言

  • dockerd 是 docker engine 守护进程,dockerd 启动时会启动 containerd 子进程,dockerd 与 containerd 通过 rpc 进行通信
  • ctr 是 containerd 的 cli
  • containerd 通过 shim 操作 runc,runc 真正控制容器生命周期,启动一个容器就会启动一个 shim 进程
  • shim 直接调用 runc 的包函数,shim 与 containerd 之前通过 rpc 通信
  • 真正用户想启动的进程由 runc 的 init 进程启动,即 runc init [args …]
docker     ctr
  |         |
  V         V
dockerd -> containerd ---> shim -> runc -> runc init -> process
                      |-- > shim -> runc -> runc init -> process
                      +-- > shim -> runc -> runc init -> process

containerd 只是一个守护进程,容器的实际运行时由 runC 控制。containerd 主要职责是镜像管理(镜像、元信息等)、容器执行(调用最终运行时组件执行)

在这里插入图片描述

2. 源码编译

需要安装依赖包:btrfs-tools,直接 make 即可生成 ctr containerd containerd-shim binaries 可执行文件

2.1 containerd main 函数

入口目录为 cmd/containerd/main.go 中 main 函数,默认配置文件 /etc/containerd/config.toml,包含三个子命令,configCommand,publishCommand,ociHook

func main() {
	app := command.App()
	if err := app.Run(os.Args); err != nil {
		fmt.Fprintf(os.Stderr, "containerd: %s\n", err)
		os.Exit(1)
	}
}

2.1.1 command App 函数

//  App 函数返回 *cli.App 实例
func App() *cli.App {
	app := cli.NewApp()
	app.Name = "containerd"
	app.Version = version.Version
	app.Usage = usage
	app.Description = `
containerd is a high performance container runtime whose daemon can be started
by using this command. If none of the *config*, *publish*, or *help* commands
are specified, the default action of the **containerd** command is to start the
containerd daemon in the foreground.


A default configuration is used if no TOML configuration is specified or located
at the default file location. The *containerd config* command can be used to
generate the default configuration for containerd. The output of that command
can be used and modified as necessary as a custom configuration.`
// 如果未指定 TOML 配置或位于默认文件位置,则使用默认配置。容器配置命令可用于生成容器的默认配置。该命令的输出可以根据需要作为自定义配置使用和修改
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:  "config,c",
			Usage: "path to the configuration file",
			Value: filepath.Join(defaults.DefaultConfigDir, "config.toml"),
		},
		cli.StringFlag{
			Name:  "log-level,l",
			Usage: "set the logging level [trace, debug, info, warn, error, fatal, panic]",
		},
		cli.StringFlag{
			Name:  "address,a",
			Usage: "address for containerd's GRPC server",
		},
		cli.StringFlag{
			Name:  "root",
			Usage: "containerd root directory",
		},
		cli.StringFlag{
			Name:  "state",
			Usage: "containerd state directory",
		},
	}
	app.Flags = append(app.Flags, serviceFlags()...)
	// configCommand 用于生成配置文件 containerd config default > /etc/containerd/config.toml,publishCommand,ociHook  
	// publishCommand 二进制方式推送数据到containerd
	// ociHook 为 OCI 运行时钩子提供基础,以允许注入参数
       	app.Commands = []cli.Command{
		configCommand,
		publishCommand,
		ociHook,
	}
	//未指定子命令时要执行的操作 
        // 需要“cli.ActionFunc”,但可以接受“func(cli.Context) {}” 
	// 注意:对已弃用的“Action”的支持将在将来的版本中删除
	app.Action = func(context *cli.Context) error {
		var (
			start       = time.Now()
			signals     = make(chan os.Signal, 2048)
			serverC     = make(chan *server.Server, 1)
			ctx, cancel = gocontext.WithCancel(gocontext.Background())
			config      = defaultConfig()
		)

		defer cancel()

 		// 仅当配置存在或用户明确告诉我们加载此路径时,才尝试加载配置。
		configPath := context.GlobalString("config")
		_, err := os.Stat(configPath)
		if !os.IsNotExist(err) || context.GlobalIsSet("config") {
			if err := srvconfig.LoadConfig(configPath, config); err != nil {
				return err
			}
		}

		// 将传入参数应用于配置
		if err := applyFlags(context, config); err != nil {
			return err
		}

		//  确定根目录被创建
		if err := server.CreateTopLevelDirectories(config); err != nil {
			return err
		}

		// Stop if we are registering or unregistering against Windows SCM.
		stop, err := registerUnregisterService(config.Root)
		if err != nil {
			logrus.Fatal(err)
		}
		if stop {
			return nil
		}

		done := handleSignals(ctx, signals, serverC, cancel)
		// start the signal handler as soon as we can to make sure that
		// we don't miss any signals during boot
		signal.Notify(signals, handledSignals...)

		// 清理挂载点
		if err := mount.SetTempMountLocation(filepath.Join(config.Root, "tmpmounts")); err != nil {
			return fmt.Errorf("creating temp mount location: %w", err)
		}
		// unmount all temp mounts on boot for the server
		warnings, err := mount.CleanupTempMounts(0)
		if err != nil {
			log.G(ctx).WithError(err).Error("unmounting temp mounts")
		}
		for _, w := range warnings {
			log.G(ctx).WithError(w).Warn("cleanup temp mount")
		}
		// 配置文件中grpc address 不能为空
		if config.GRPC.Address == "" {
			return fmt.Errorf("grpc address cannot be empty: %w", errdefs.ErrInvalidArgument)
		}
		if config.TTRPC.Address == "" {
			// If TTRPC was not explicitly configured, use defaults based on GRPC.
			config.TTRPC.Address = fmt.Sprintf("%s.ttrpc", config.GRPC.Address)
			config.TTRPC.UID = config.GRPC.UID
			config.TTRPC.GID = config.GRPC.GID
		}
		log.G(ctx).WithFields(logrus.Fields{
			"version":  version.Version,
			"revision": version.Revision,
		}).Info("starting containerd")

		type srvResp struct {
			s   *server.Server
			err error
		}

		// run server initialization in a goroutine so we don't end up blocking important things like SIGTERM handling
		// while the server is initializing.
		// As an example opening the bolt database will block forever if another containerd is already running and containerd
		// will have to be be `kill -9`'ed to recover.
		// 在 goroutine 中运行服务器初始化,这样我们就不会在服务器初始化时阻止重要的事情,例如 SIGTERM 处理。
                // 例如,如果另一个 containerd 已经在运行,则打开 bolt 数据库将永远阻塞,并且 containerd 必须被“kill -9”才能恢复。
		chsrv := make(chan srvResp)
		go func() {
			defer close(chsrv)

			server, err := server.New(ctx, config)
			if err != nil {
				select {
				case chsrv <- srvResp{err: err}:
				case <-ctx.Done():
				}
				return
			}

			// Launch as a Windows Service if necessary
			if err := launchService(server, done); err != nil {
				logrus.Fatal(err)
			}
			select {
			case <-ctx.Done():
				server.Stop()
			case chsrv <- srvResp{s: server}:
			}
		}()

		var server *server.Server
		select {
		case <-ctx.Done():
			return ctx.Err()
		case r := <-chsrv:
			if r.err != nil {
				return r.err
			}
			server = r.s
		}

		// We don't send the server down serverC directly in the goroutine above because we need it lower down.
		select {
		case <-ctx.Done():
			return ctx.Err()
		case serverC <- server:
		}

		if config.Debug.Address != "" {
			var l net.Listener
			if isLocalAddress(config.Debug.Address) {
				if l, err = sys.GetLocalListener(config.Debug.Address, config.Debug.UID, config.Debug.GID); err != nil {
					return fmt.Errorf("failed to get listener for debug endpoint: %w", err)
				}
			} else {
				if l, err = net.Listen("tcp", config.Debug.Address); err != nil {
					return fmt.Errorf("failed to get listener for debug endpoint: %w", err)
				}
			}
			serve(ctx, l, server.ServeDebug)
		}
		if config.Metrics.Address != "" {
			l, err := net.Listen("tcp", config.Metrics.Address)
			if err != nil {
				return fmt.Errorf("failed to get listener for metrics endpoint: %w", err)
			}
			serve(ctx, l, server.ServeMetrics)
		}
		// setup the ttrpc endpoint
		tl, err := sys.GetLocalListener(config.TTRPC.Address, config.TTRPC.UID, config.TTRPC.GID)
		if err != nil {
			return fmt.Errorf("failed to get listener for main ttrpc endpoint: %w", err)
		}
		serve(ctx, tl, server.ServeTTRPC)

		if config.GRPC.TCPAddress != "" {
			l, err := net.Listen("tcp", config.GRPC.TCPAddress)
			if err != nil {
				return fmt.Errorf("failed to get listener for TCP grpc endpoint: %w", err)
			}
			serve(ctx, l, server.ServeTCP)
		}
		// setup the main grpc endpoint
		l, err := sys.GetLocalListener(config.GRPC.Address, config.GRPC.UID, config.GRPC.GID)
		if err != nil {
			return fmt.Errorf("failed to get listener for main endpoint: %w", err)
		}
		serve(ctx, l, server.ServeGRPC)

		if err := notifyReady(ctx); err != nil {
			log.G(ctx).WithError(err).Warn("notify ready failed")
		}

		log.G(ctx).Infof("containerd successfully booted in %fs", time.Since(start).Seconds())
		<-done
		return nil
	}
	return app

2.2 server.New 函数创建以及初始化 containerd server

2.2.1 初始化 contaienrd server,加载 timeout 配置

func New(ctx context.Context, config *srvconfig.Config) (*Server, error) {
	if err := apply(ctx, config); err != nil {
		return nil, err
	}
	for key, sec := range config.Timeouts {
		d, err := time.ParseDuration(sec)
		if err != nil {
			return nil, fmt.Errorf("unable to parse %s into a time duration", sec)
		}
		timeout.Set(key, d)
	}
[timeouts]
  "io.containerd.timeout.shim.cleanup" = "5s"
  "io.containerd.timeout.shim.load" = "5s"
  "io.containerd.timeout.shim.shutdown" = "3s"
  "io.containerd.timeout.task.state" = "2s"

2.2.2 LoadPlugins 加载插件


// LoadPlugins loads all plugins into containerd and generates an ordered graph
// of all plugins.
func LoadPlugins(ctx context.Context, config *srvconfig.Config) ([]*plugin.Registration, error) {
	// load all plugins into containerd
	path := config.PluginDir
	if path == "" {
		path = filepath.Join(config.Root, "plugins")
	}
	if err := plugin.Load(path); err != nil {
		return nil, err
	}

2.2.2.1 注册插件 io.containerd.content.v1

// load additional plugins that don't automatically register themselves
	plugin.Register(&plugin.Registration{
		Type: plugin.ContentPlugin,
		ID:   "content",
		InitFn: func(ic *plugin.InitContext) (interface{}, error) {
			ic.Meta.Exports["root"] = ic.Root
			return local.NewStore(ic.Root)
		},
	})

**,**store 结构体 实现了 Store 接口

路径 containerd/content/local/store.go

// Store combines the methods of content-oriented interfaces into a set that
// are commonly provided by complete implementations.
type Store interface {
    Manager
    Provider
    IngestManager
    Ingester
}

2.2.2.2 注册插件 io.containerd.metadata.v1,使用 bolt 数据库存储

	plugin.Register(&plugin.Registration{
		Type: plugin.MetadataPlugin,
		ID:   "bolt",
		Requires: []plugin.Type{
			plugin.ContentPlugin,
			plugin.SnapshotPlugin,
		},
		Config: &srvconfig.BoltConfig{
			ContentSharingPolicy: srvconfig.SharingPolicyShared,
		},

2.2.2.3 遍历所有插件

类型为 io.containerd.snapshotter.v1,或者 snapshot,实现在 containerd/snapshots/proxy/proxy.go,proxySnapshotter 结构体实现了 Snapshotter 接口

	clients := &proxyClients{}
	for name, pp := range config.ProxyPlugins {
		var (
			t plugin.Type
			f func(*grpc.ClientConn) interface{}

			address = pp.Address
		)

		switch pp.Type {
		case string(plugin.SnapshotPlugin), "snapshot":
			t = plugin.SnapshotPlugin
			ssname := name
			f = func(conn *grpc.ClientConn) interface{} {
				return ssproxy.NewSnapshotter(ssapi.NewSnapshotsClient(conn), ssname)
			}

		case string(plugin.ContentPlugin), "content":
			t = plugin.ContentPlugin
			f = func(conn *grpc.ClientConn) interface{} {
				return csproxy.NewContentStore(csapi.NewContentClient(conn))
			}
		default:
			log.G(ctx).WithField("type", pp.Type).Warn("unknown proxy plugin type")
		}

		plugin.Register(&plugin.Registration{
			Type: t,
			ID:   name,
			InitFn: func(ic *plugin.InitContext) (interface{}, error) {
				ic.Meta.Exports["address"] = address
				conn, err := clients.getClient(address)
				if err != nil {
					return nil, err
				}
				return f(conn), nil
			},
		})

	}
[plugins]

  [plugins."io.containerd.snapshotter.v1.devmapper"]
    root_path = ""
    pool_name = ""
    base_image_size = ""
type Snapshotter interface {
    Stat(ctx context.Context, key string) (Info, error)
    Update(ctx context.Context, info Info, fieldpaths ...string) (Info, error)
    Usage(ctx context.Context, key string) (Usage, error)
    Mounts(ctx context.Context, key string) ([]mount.Mount, error)
    Prepare(ctx context.Context, key string, parent string, opts ...Opt) ([]mount.Mount, error)
    View(ctx context.Context, key string, parent string, opts ...Opt) ([]mount.Mount, error)
    Commit(ctx context.Context, name string, key string, opts ...Opt) error
    Remove(ctx context.Context, key string) error
    Walk(ctx context.Context, fn WalkFunc, filters ...string) error
    Close() error
}

2.2.2.4 Graph 函数返回一个注册有序的插件列表

// Plugins in disableList specified by id will be disabled.
func Graph(filter DisableFilter) (ordered []*Registration) {
	register.RLock()
	defer register.RUnlock()

	for _, r := range register.r {
		if filter(r) {
			r.Disable = true
		}
	}

	added := map[*Registration]bool{}
	for _, r := range register.r {
		if r.Disable {
			continue
		}
		children(r, added, &ordered)
		if !added[r] {
			ordered = append(ordered, r)
			added[r] = true
		}
	}
	return ordered
}

2.2.2.5 启动GRPC Server

func serve(ctx gocontext.Context, l net.Listener, serveFunc func(net.Listener) error) {
	path := l.Addr().String()
	log.G(ctx).WithField("address", path).Info("serving...")
	go func() {
		defer l.Close()
		if err := serveFunc(l); err != nil {
			log.G(ctx).WithError(err).WithField("address", path).Fatal("serve failure")
		}
	}()
}

2.3 获取 GRPC server 配置参数

 [grpc]
  address = "/run/containerd/containerd.sock"
  tcp_address = ""
  tcp_tls_cert = ""
  tcp_tls_key = ""
  uid = 0
  gid = 0
  max_recv_message_size = 16777216
  max_send_message_size = 16777216  
	serverOpts := []grpc.ServerOption{
		grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
			otelgrpc.StreamServerInterceptor(),
			grpc.StreamServerInterceptor(grpc_prometheus.StreamServerInterceptor),
			streamNamespaceInterceptor,
		)),
		grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
			otelgrpc.UnaryServerInterceptor(),
			grpc.UnaryServerInterceptor(grpc_prometheus.UnaryServerInterceptor),
			unaryNamespaceInterceptor,
		)),
	}
	if config.GRPC.MaxRecvMsgSize > 0 {
		serverOpts = append(serverOpts, grpc.MaxRecvMsgSize(config.GRPC.MaxRecvMsgSize))
	}
	if config.GRPC.MaxSendMsgSize > 0 {
		serverOpts = append(serverOpts, grpc.MaxSendMsgSize(config.GRPC.MaxSendMsgSize))
	}

2.4 实例化Server

	var (
		grpcServer = grpc.NewServer(serverOpts...)
		tcpServer  = grpc.NewServer(tcpServerOpts...)

		grpcServices  []grpcService
		tcpServices   []tcpService
		ttrpcServices []ttrpcService

		s = &Server{
			grpcServer:  grpcServer,
			tcpServer:   tcpServer,
			ttrpcServer: ttrpcServer,
			config:      config,
		}
		// TODO: Remove this in 2.0 and let event plugin crease it
		events      = exchange.NewExchange()
		initialized = plugin.NewPluginSet()
		required    = make(map[string]struct{})
	)

2.5 需要开启设置的插件

required_plugins = []
for _, r := range config.RequiredPlugins {
	required[r] = struct{}{}
}

2.6 Init调用各个注册的 InitFn 函数初始化,分别实现了不同的接口

// Init the registered plugin
func (r *Registration) Init(ic *InitContext) *Plugin {
	p, err := r.InitFn(ic)
	return &Plugin{
		Registration: r,
		Config:       ic.Config,
		Meta:         ic.Meta,
		instance:     p,
		err:          err,
	}
}

2.7 对所有 服务分别调用 Register 方法注册(根据是否实现了 Register 接口,各个插件里面的实现的接口 Register)

	// register services after all plugins have been initialized
	for _, service := range grpcServices {
		if err := service.Register(grpcServer); err != nil {
			return nil, err
		}
	}
	for _, service := range ttrpcServices {
		if err := service.RegisterTTRPC(ttrpcServer); err != nil {
			return nil, err
		}
	}
	for _, service := range tcpServices {
		if err := service.RegisterTCP(tcpServer); err != nil {
			return nil, err
		}
	}

3. plugin

// Registration contains information for registering a plugin
type Registration struct {
    Type     Type
    ID       string
    Config   interface{}
    Requires []Type
    InitFn   func(*InitContext) (interface{}, error)
    Disable  bool
}
# ctr plugins ls
TYPE                            ID                    PLATFORMS      STATUS  
io.containerd.content.v1        content               -              ok  
io.containerd.snapshotter.v1    btrfs                 linux/amd64    error   
io.containerd.snapshotter.v1    devmapper             linux/amd64    error   
io.containerd.snapshotter.v1    aufs                  linux/amd64    error   
io.containerd.snapshotter.v1    native                linux/amd64    ok  
io.containerd.snapshotter.v1    overlayfs             linux/amd64    ok  
io.containerd.snapshotter.v1    zfs                   linux/amd64    error   
io.containerd.metadata.v1       bolt                  -              ok  
io.containerd.differ.v1         walking               linux/amd64    ok  
io.containerd.gc.v1             scheduler             -              ok  
io.containerd.service.v1        containers-service    -              ok  
io.containerd.service.v1        content-service       -              ok  
io.containerd.service.v1        diff-service          -              ok  
io.containerd.service.v1        images-service        -              ok  
io.containerd.service.v1        leases-service        -              ok  
io.containerd.service.v1        namespaces-service    -              ok  
io.containerd.service.v1        snapshots-service     -              ok  
io.containerd.runtime.v1        linux                 linux/amd64    ok  
io.containerd.runtime.v2        task                  linux/amd64    ok  
io.containerd.monitor.v1        cgroups               linux/amd64    ok  
io.containerd.service.v1        tasks-service         -              ok  
io.containerd.internal.v1       restart               -              ok  
io.containerd.grpc.v1           containers            -              ok  
io.containerd.grpc.v1           content               -              ok  
io.containerd.grpc.v1           diff                  -              ok  
io.containerd.grpc.v1           events                -              ok  
io.containerd.grpc.v1           healthcheck           -              ok  
io.containerd.grpc.v1           images                -              ok  
io.containerd.grpc.v1           leases                -              ok  
io.containerd.grpc.v1           namespaces            -              ok  
io.containerd.internal.v1       opt                   -              ok  
io.containerd.grpc.v1           snapshots             -              ok  
io.containerd.grpc.v1           tasks                 -              ok  
io.containerd.grpc.v1           version               -              ok  
io.containerd.grpc.v1           cri                   linux/amd64    ok

可以在源码中看到具体实现

4. 以plugin:io.containerd.grpc.v1.containers为例

代码路径: services/containers/service.go,ID 为 containers,提供基础元素的存储

注册

func init() {
	plugin.Register(&plugin.Registration{
		Type: plugin.GRPCPlugin,
		ID:   "containers",
		Requires: []plugin.Type{
			plugin.ServicePlugin,
		},
		InitFn: func(ic *plugin.InitContext) (interface{}, error) {
			plugins, err := ic.GetByType(plugin.ServicePlugin)
			if err != nil {
				return nil, err
			}
			p, ok := plugins[services.ContainersService]
			if !ok {
				return nil, errors.New("containers service not found")
			}
			i, err := p.Instance()
			if err != nil {
				return nil, err
			}
			return &service{local: i.(api.ContainersClient)}, nil
		},
	})
}

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

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

相关文章

throw语句以及throw “error“ 和 throw new Error(“error“)的区别

文章目录什么是 throw 语句&#xff1f;throw 语句后面跟一个对象Error 构造函数对象结论什么是 throw 语句&#xff1f; throw 语句用来抛出一个用户自定义的异常。当前函数的执行将被停止&#xff08;throw 之后的语句将不会执行&#xff09;&#xff0c;并且控制将被传递到调…

AOP的另类用法 (权限校验自定义注解)

&#x1f473;我亲爱的各位大佬们好&#x1f618;&#x1f618;&#x1f618; ♨️本篇文章记录的为 AOP的另类用法 (权限校验&&自定义注解) 相关内容&#xff0c;适合在学Java的小白,帮助新手快速上手,也适合复习中&#xff0c;面试中的大佬&#x1f649;&#x1f649…

磁盘存储和文件系统管理

磁盘存储和文件系统管理1. 磁盘结构1.1设备文件1. 设备类型&#xff1a;2. 磁盘设备的设备文件命名&#xff1a;3. 虚拟磁盘&#xff1a;4. 不同磁盘标识&#xff1a;a-z,aa,ab…5. 同一设备上的不同分区&#xff1a;1,2, ...6. 创建设备文件7. 工具 dd常用选项示例demo8. hexd…

maven的安装配置

目录 1. Maven的安装配置 1.1检测jdk的版本 1.2下载maven 1.3配置maven环境变量 2.认识maven的目录结构 2.1 创建一个文件夹作为项目的根目录 1.创建如下结构的目录 2. 在pom.xml文件中写入如下内容(不用记忆) 3.在mian-->java--》下边创建java文件​编辑 4.cmd下…

XGboost部分细节补充

XGBoost算法原理详解与参数详解 R语言XGBoost参数详解 XGBoost部分细节补充1. XGBoost线性模型的实现2.XGBoost对二分类和多分类的处理方法1. XGBoost线性模型的实现 前面文章中已经详细的介绍了XGBoost基于决策树的实现&#xff0c;今天我们主要介绍XGBoost基于线性模型的实现…

模式识别 —— 第二章 参数估计

模式识别 —— 第二章 参数估计 文章目录模式识别 —— 第二章 参数估计最大似然估计&#xff08;MLE&#xff09;最大后验概率估计&#xff08;MAP&#xff09;贝叶斯估计最大似然估计&#xff08;MLE&#xff09; 在语言上&#xff1a; 似然&#xff08;likelihood&#xf…

【Git】P1 Git 基础

Git 基础Git 基本概念集中式版本控制工具 与 分布式版本控制工具Git 下载与安装Bash 初始设置创建本地仓库Git 三区概念一个简单的提交流程更改文件后再次提交git 实现版本切换查看提交日志设置 git 快捷键版本切换&#xff08;一&#xff09;版本切换&#xff08;二&#xff0…

华为OD机试题,用 Java 解【数据分类】问题

华为Od必看系列 华为OD机试 全流程解析+经验分享,题型分享,防作弊指南)华为od机试,独家整理 已参加机试人员的实战技巧华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典使用说明 参加华为od机试,一定要注意不…

0104路径搜索和单点路径-无向图-数据结构和算法(Java)

文章目录2 单点路径2.1 API2.2 算法实现后记2 单点路径 单点路径。给定一幅图和一个起点s&#xff0c;回答“从s到给定的目的顶点v是否存在一条路径&#xff1f;如果有&#xff0c;找出这条路径。”等类似问题。 2.1 API 单点路径问题在图的处理邻域中十分重要。根据标准设计…

PHP, Python和Java的区别

PHP, Python和Java是广泛使用的编程语言。每种编程语言都有其独特的优点和缺点。在本文中&#xff0c;我们将对这些编程语言进行分析&#xff0c;并探讨它们在不同应用场景中的最佳用途。一、PHPPHP是一种广泛使用的Web编程语言&#xff0c;它可以在服务器上运行&#xff0c;并…

程设 | week2:STL

&#x1f4da;回顾C &#x1f407;struct和class 从功能上说&#xff0c;struct和class几乎没什么区别在不显式声明的情况下&#xff0c;struct成员默认为public&#xff0c;class默认为private和c语言的struct不同&#xff0c;c的struct可以定义成员函数&#xff0c;重载运算…

G6绘制树形图(自定义节点、自定义边、自定义布局)

目录1 设计节点1.1 定义节点和文本1.2 增加节点1.3 自定义节点样式2 树图配置2.1 允许使用自定义dom节点2.2 内置行为自定义边layout布局demo1 设计节点 在 registerNode 中定义所有的节点 G6.registerNode(tree-node, {drawShape: function drawShape(cfg, group) {定义图中…

aws appconfig 理解和使用appconfig对应用程序进行动态配置

参考资料 Automating Feature Release using AWS AppConfig Integration with AWS CodepipelineDeploying application configuration to serverless: Introducing the AWS AppConfig Lambda extensionCreate a pipeline that uses Amazon AppConfig as a deployment provider…

秒懂算法 | 搜索基础

本篇介绍了BFS和DFS的概念、性质、模板代码。 01、搜索简介 搜索,就是查找解空间,它是“暴力法”算法思想的具体实现。 暴力法(Brute force,又译为蛮力法):把所有可能的情况都罗列出来,然后逐一检查,从中找到答案。这种方法简单、直接,不玩花样,利用了计算机强大的…

JavaScript 中的 String 类型 模板字面量定义字符串

ECMAScript 6新增了使用模板字面量定义字符串的能力。与使用单引号或双引号不同&#xff0c;模板字面量保留换行字符&#xff0c;可以跨行定义字符串&#xff1a; let str1 早起的年轻人\n喜欢经常跳步;let str2 早起的年轻人喜欢经常跳步;console.log(str1);// 早起的年轻人…

【游戏逆向】游戏玩家技能冷却分析

技能冷却对于不同的游戏有不同的存放方式,而技能冷却的遍历也大多不会和技能的普通属性放在一起,在《在**明月刀》这款游戏中,技能的冷却判断格外重要,因为技能的连贯性对打怪的效率影响很大。 我们需要找一个冷却相对较长的技能用来进行扫描和过滤,一般选择几十秒即可,…

【本周特惠课程】基于GAN的图像增强理论与实践(涵盖图像降噪、色调映射、去模糊、超分辨、修复等方向)...

前言欢迎大家关注有三AI的视频课程系列&#xff0c;我们的视频课程系列共分为5层境界&#xff0c;内容和学习路线图如下&#xff1a;第1层&#xff1a;掌握学习算法必要的预备知识&#xff0c;包括Python编程&#xff0c;深度学习基础&#xff0c;数据使用&#xff0c;框架使用…

Windows扫描工具RunScanner使用实验(21)

实验目的 掌握利用Runscanner扫描和分析电脑&#xff1b;预备知识 RunScanner是一个完全免费的Windows系统工具,您可以用它轻松地将隐藏在您系统中的autostart程序,spyware,adware,主页劫持,未经认证的驱动揪出来,并可以导入和导出报告以帮助别人或获取帮助.目前它可以…

华为OD机试题,用 Java 解【素数之积】问题

华为Od必看系列 华为OD机试 全流程解析+经验分享,题型分享,防作弊指南)华为od机试,独家整理 已参加机试人员的实战技巧华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典使用说明 参加华为od机试,一定要注意不…

C++中的利器——模板

前文本文主要是讲解一下C中的利器——模板&#xff0c;相信铁子们在学完这一节后&#xff0c;写代码会更加的得心应手&#xff0c;更加的顺畅。一&#xff0c;泛型编程想要学习模板&#xff0c;我们要先了解为什么需要模板&#xff0c;我们可以看看下面这个程序。int add(int&a…