Unity热更方案HybridCLR+YooAsset,纯c#开发热更,保姆级教程,从零开始

文章目录:

  • 一、前言
  • 二、创建空工程
  • 三、接入HybridCLR
  • 四、接入YooAsset
  • 五、搭建本地资源服务器Nginx
  • 六、实战
  • 七、最后

一、前言

unity热更有很多方案,各种lua热更,ILRuntime等,这里介绍的是YooAsset+HybridCLR的热更方案,HybridCLR负责更新c#代码,YooAsset负责更新资源。

简单来说,流程就是将c#代码打成dll,然后把dll当做一个资源,用YooAsset热更dll资源之后,动态加载dll程序集,然后执行新逻辑

HybridCLR相比其他代码热更方案而言,纯c#方便开发,更加符合开发者习惯,更新的代码执行效率也更好。

YooAsset热更资源,主要是省去了自己亲自管理ab包,ab包的管理挺繁琐,AssetBundle坑也很多,而且YooAsset有下载器,不用自己手写网络下载,也不用自己记录资源,比对资源列表来判定需要热更什么资源。

这里将从零开始,用一个空工程,展示YooAsset+HybridCLR热更的使用过程。

二、创建空工程

这里使用的版本是unity2022.3.17.f1c1
在这里插入图片描述

三、接入HybridCLR

unity必须添加了Windows Build Support(IL2CPP)或Mac Build Support(IL2CPP)模块
在这里插入图片描述
在这里插入图片描述

安装IDE及相关编译环境
Win
需要安装visual studio 2019或更高版本。安装时至少要包含使用Unity的游戏开发和使用c++的游戏开发组件。
安装git

Mac
要求MacOS版本 >= 12,xcode版本 >= 13,例如xcode 13.4.1, macos 12.4。
安装 git

添加ConsoleToScreen.cs脚本
和热更无关,主要在屏幕上显示log,代码如下:

using System.Collections.Generic;
using UnityEngine;

public class ConsoleToScreen : MonoBehaviour
{
    const int maxLines = 50;
    const int maxLineLength = 120;
    private string _logStr = "";

    private readonly List<string> _lines = new();

    public int fontSize = 15;

    void OnEnable() { Application.logMessageReceived += Log; }
    void OnDisable() { Application.logMessageReceived -= Log; }

    public void Log(string logString, string stackTrace, LogType type)
    {
        foreach (var line in logString.Split('\n'))
        {
            if (line.Length <= maxLineLength)
            {
                _lines.Add(line);
                continue;
            }
            var lineCount = line.Length / maxLineLength + 1;
            for (int i = 0; i < lineCount; i++)
            {
                if ((i + 1) * maxLineLength <= line.Length)
                {
                    _lines.Add(line.Substring(i * maxLineLength, maxLineLength));
                }
                else
                {
                    _lines.Add(line.Substring(i * maxLineLength, line.Length - i * maxLineLength));
                }
            }
        }
        if (_lines.Count > maxLines)
        {
            _lines.RemoveRange(0, _lines.Count - maxLines);
        }
        // _lines.Add(stackTrace);
        _logStr = string.Join("\n", _lines);
    }

    void OnGUI()
    {
        GUI.matrix = Matrix4x4.TRS(Vector3.zero, Quaternion.identity,
            new Vector3(Screen.width / 1200.0f, Screen.height / 800.0f, 1.0f));
        GUI.Label(new Rect(10, 10, 800, 370), _logStr, new GUIStyle { fontSize = 10 });
    }
}

工程初始设置
创建Main场景
将ConsoleToScreen挂载一个创建的空物体上
菜单栏 File/BuildSettings添加Main场景
创建Assets/HotUpdate目录
在这里插入图片描述

创建热更程序集
在HotUpdate目录下 右键 Create/Assembly Definition,创建一个名为HotUpdate的程序集模块
在这里插入图片描述

安装HybridCLR
主菜单中点击Windows/Package Manager
Add package from git URL…
填入:https://gitee.com/focus-creative-games/hybridclr_unity.git
如下:
在这里插入图片描述
在这里插入图片描述

初始化HybridCLR
打开菜单HybridCLR/Installer…, 点击Install按钮进行安装。 耐心等待30s左右,安装完成后会在最后打印 安装成功日志。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

配置HybridCLR
打开菜单 HybridCLR/Settings, 在热更新Assembly Definitions配置项中添加HotUpdate程序集。
在这里插入图片描述
在这里插入图片描述

设置PlayerSettings
打开菜单Edit/Project Setting,在Player选项中,设置如下几个配置:
Scripting Backend 切换为 IL2CPP。
Api Compatability Level 切换为 .Net 4.x(Unity 2019-2020) 或.Net Framework(Unity 2021+)
在这里插入图片描述
在这里插入图片描述

HybridCLR方面的操作告一段落,接下来接入YooAsset

四、接入YooAsset

安装YooAsset
打开菜单Edit/Project Settings/Package Manager
在Package Manager选项找那个,输入如下内容,点击save:

(国际版)
Name: package.openupm.com
URL: https://package.openupm.com
Scope(s): com.tuyoogame.yooasset

(中国版)
Name: package.openupm.cn
URL: https://package.openupm.cn
Scope(s): com.tuyoogame.yooasset
(这个配置好像报错了,我用的上面国际版的地址)
在这里插入图片描述
在这里插入图片描述

打开菜单Windows/Package Manager
Packages选择My Registries,出现了YooAsset,点击Install安装。
在这里插入图片描述
在这里插入图片描述

五、搭建本地资源服务器Nginx

为了模拟热更流程,需要一个服务器作为热更资源的下载,我们可以在本机搭建一个资源服务器。我这里用的是Nginx。
下载地址:https://nginx.org/en/download.html
在这里插入图片描述

随便下一个,下载之后是个zip包,解压之后如下:
在这里插入图片描述

注意:端口冲突时,更改端口:打开文件:conf-nginx.conf,修改第36行的listen,我改的是8084,自己随意
然后运行nginx.exe即可
在这里插入图片描述

现在打开网址http://127.0.0.1:8084如下:
在这里插入图片描述

html文件夹下,就可以放需要热更的资源,比如我准备打包测试的是pc平台,我就在html文件夹下建了个TestProject文件夹,在里面再建个PC文件夹,TestProject是项目名,PC是平台名,用以放接下来要热更的资源
在这里插入图片描述

六、实战

创建热更目录
之前的HotUpdate文件夹是为了放c#代码,用以生成程序集的,接下来创建的目录,是为了热更资源的(包括c#代码的dll资源,dll也是一种资源),创建如下目录:
在这里插入图片描述

准备工作
HotUpdate文件夹下创建InstantiateByAsset.cs,这是会被热更的代码:

using UnityEngine;

public class InstantiateByAsset : MonoBehaviour
{
    void Start()
    {
        Debug.Log("原始代码");
    }
}

在这里插入图片描述

创建一个cube预制体,挂载InstantiateByAsset组件,放入MyAsset/Prefabs中
在这里插入图片描述

收集热更资源

创建Resources文件夹,在Resources文件夹内通过右键创建配置文件(Project窗体内右键 -> Create -> YooAsset -> Create Setting),将配置文件放在Resources文件夹下
在这里插入图片描述

打开菜单YooAsset/AssetBundle Collector,点击ShowPackages,再点击+号,创建packages,再点击ShowPackages,只显示Groups就行,方便操作
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

打开Enable Addressable
添加code和prefab分组,具体设置如下:
在这里插入图片描述
在这里插入图片描述

选项含义如下,其他含义,请看官网
在这里插入图片描述

编写LoadDll.cs
编写代码初始化YooAsset,从资源服加载热更资源,加载热更程序集,将LoadDll.cs挂在Main场景的Main Camera上。

整个代码流程就是先初始化,然后下载热更资源,然后补充元数据,然后开始执行热更代码。
关于补充元数据,通俗的理解,举个例子:

由于Unity IL2CPP打包的代码裁剪,假设在打包时的代码,从来没有用过List泛型,打包时,List的元数据会被裁掉了。到玩家手机的APP上,根本不存在List的程序定义等关键信息。当玩家运行app时,在运行前预编译这个app的代码时,是不存在List的相关定义的,编译完成后,在运行时,热更逻辑中,使用了List,虽然这个热更的dll中有List的程序定义,但是已经过了编译阶段,现在是执行阶段,执行阶段是直接找预编译时的程序定义去进行实例化。所以在动态执行包含有List的代码时,由于编译阶段缺失了List的程序定义相关的元数据,无法对其进行实例化。所以在运行包含了List的热更代码时,先补齐编译阶段缺失的List元数据,之后才能正常运行和实例化热更中的包含了List的代码。

常见的需要补充的元数据如:mscorlib.dll, System.dll, System.Core.dll

详细信息原理请浏览官网

代码如下:

using HybridCLR;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
using YooAsset;

/// <summary>
/// 脚本工作流程:
/// 1.下载资源,用yooAsset资源框架进行下载
///    1.资源文件,ab包
///    2.热更新dll
/// 2.给AOT DLL补充元素据,通过RuntimeApi.LoadMetadataForAOTAssembly
/// 3.通过实例化prefab,运行热更代码
/// </summary>
public class LoadDll : MonoBehaviour
{
    /// <summary>
    /// 资源系统运行模式
    /// </summary>
    public EPlayMode PlayMode = EPlayMode.HostPlayMode;

    private ResourcePackage _defaultPackage;

    void Start()
    {
        StartCoroutine(InitYooAssets(StartGame));
    }

    #region YooAsset初始化

    IEnumerator InitYooAssets(Action onDownloadComplete)
    {
        // 1.初始化资源系统
        YooAssets.Initialize();

        string packageName = "DefaultPackage";
        var package = YooAssets.TryGetPackage(packageName) ?? YooAssets.CreatePackage(packageName);
        YooAssets.SetDefaultPackage(package);
        if (PlayMode == EPlayMode.EditorSimulateMode)
        {
            //编辑器模拟模式
            var initParameters = new EditorSimulateModeParameters { SimulateManifestFilePath = EditorSimulateModeHelper.SimulateBuild(EDefaultBuildPipeline.BuiltinBuildPipeline, "DefaultPackage") };
            yield return package.InitializeAsync(initParameters);
        }
        else if (PlayMode == EPlayMode.HostPlayMode)
        {
            //联机运行模式
            string defaultHostServer = GetHostServerURL();
            string fallbackHostServer = GetHostServerURL();
            Debug.Log(defaultHostServer);
            var initParameters = new HostPlayModeParameters();
            initParameters.BuildinQueryServices = new GameQueryServices();
            // initParameters.DecryptionServices = new GameDecryptionServices();
            // initParameters.DeliveryQueryServices = new DefaultDeliveryQueryServices();
            initParameters.RemoteServices = new RemoteServices(defaultHostServer, fallbackHostServer);
            var initOperation = package.InitializeAsync(initParameters);
            yield return initOperation;

            if (initOperation.Status == EOperationStatus.Succeed)
            {
                Debug.Log("资源包初始化成功!");
            }
            else
            {
                Debug.LogError($"资源包初始化失败:{initOperation.Error}");
            }
        }


        //2.获取资源版本
        var operation = package.UpdatePackageVersionAsync();
        yield return operation;

        if (operation.Status != EOperationStatus.Succeed)
        {
            //更新失败
            Debug.LogError(operation.Error);
            yield break;
        }

        string packageVersion = operation.PackageVersion;
        Debug.Log($"Updated package Version : {packageVersion}");

        //3.更新补丁清单
        // 更新成功后自动保存版本号,作为下次初始化的版本。
        // 也可以通过operation.SavePackageVersion()方法保存。
        var operation2 = package.UpdatePackageManifestAsync(packageVersion);
        yield return operation2;

        if (operation2.Status != EOperationStatus.Succeed)
        {
            //更新失败
            Debug.LogError(operation2.Error);
            yield break;
        }

        //4.下载补丁包
        yield return Download();

        //判断是否下载成功
        var assets = new List<string> { "HotUpdate.dll" }.Concat(AOTMetaAssemblyFiles);
        foreach (var asset in assets)
        {
            var handle = package.LoadAssetAsync<TextAsset>(asset);
            yield return handle;
            var assetObj = handle.AssetObject as TextAsset;
            s_assetDatas[asset] = assetObj;
            Debug.Log($"dll:{asset}   {assetObj == null}");
        }

        _defaultPackage = package;
        onDownloadComplete();
    }
    
    private string GetHostServerURL()
    {
        //模拟下载地址,8084为Nginx里面设置的端口号,项目名,平台名
        return "http://127.0.0.1:8084/TestProject/PC";
    }

    /// <summary>
    /// 远端资源地址查询服务类
    /// </summary>
    private class RemoteServices : IRemoteServices
    {
        private readonly string _defaultHostServer;
        private readonly string _fallbackHostServer;

        public RemoteServices(string defaultHostServer, string fallbackHostServer)
        {
            _defaultHostServer = defaultHostServer;
            _fallbackHostServer = fallbackHostServer;
        }

        string IRemoteServices.GetRemoteMainURL(string fileName)
        {
            return $"{_defaultHostServer}/{fileName}";
        }

        string IRemoteServices.GetRemoteFallbackURL(string fileName)
        {
            return $"{_fallbackHostServer}/{fileName}";
        }
    }

    /// <summary>
    /// 资源文件查询服务类
    /// </summary>
    internal class GameQueryServices : IBuildinQueryServices
    {
        public bool Query(string packageName, string fileName, string fileCRC)
        {
#if UNITY_IPHONE
            throw new Exception("Ios平台需要内置资源");
            return false;
#else
            return false;
#endif
        }
    }

    #endregion

    #region 下载热更资源

    IEnumerator Download()
    {
        int downloadingMaxNum = 10;
        int failedTryAgain = 3;
        var package = YooAssets.GetPackage("DefaultPackage");
        var downloader = package.CreateResourceDownloader(downloadingMaxNum, failedTryAgain);

        //没有需要下载的资源
        if (downloader.TotalDownloadCount == 0)
        {
            yield break;
        }

        //需要下载的文件总数和总大小
        int totalDownloadCount = downloader.TotalDownloadCount;
        long totalDownloadBytes = downloader.TotalDownloadBytes;

        //注册回调方法
        downloader.OnDownloadErrorCallback = OnDownloadErrorFunction;
        downloader.OnDownloadProgressCallback = OnDownloadProgressUpdateFunction;
        downloader.OnDownloadOverCallback = OnDownloadOverFunction;
        downloader.OnStartDownloadFileCallback = OnStartDownloadFileFunction;

        //开启下载
        downloader.BeginDownload();
        yield return downloader;

        //检测下载结果
        if (downloader.Status == EOperationStatus.Succeed)
        {
            //下载成功
            Debug.Log("更新完成");
        }
        else
        {
            //下载失败
            Debug.Log("更新失败");
        }
    }

    /// <summary>
    /// 开始下载
    /// </summary>
    /// <param name="fileName"></param>
    /// <param name="sizeBytes"></param>
    private void OnStartDownloadFileFunction(string fileName, long sizeBytes)
    {
        Debug.Log(string.Format("开始下载:文件名:{0},文件大小:{1}", fileName, sizeBytes));
    }

    /// <summary>
    /// 下载完成
    /// </summary>
    /// <param name="isSucceed"></param>
    private void OnDownloadOverFunction(bool isSucceed)
    {
        Debug.Log("下载" + (isSucceed ? "成功" : "失败"));
    }

    /// <summary>
    /// 更新中
    /// </summary>
    /// <param name="totalDownloadCount"></param>
    /// <param name="currentDownloadCount"></param>
    /// <param name="totalDownloadBytes"></param>
    /// <param name="currentDownloadBytes"></param>
    private void OnDownloadProgressUpdateFunction(int totalDownloadCount, int currentDownloadCount, long totalDownloadBytes, long currentDownloadBytes)
    {
        Debug.Log(string.Format("文件总数:{0},已下载文件数:{1},下载总大小:{2},已下载大小{3}", totalDownloadCount, currentDownloadCount, totalDownloadBytes, currentDownloadBytes));
    }

    /// <summary>
    /// 下载出错
    /// </summary>
    /// <param name="fileName"></param>
    /// <param name="error"></param>
    private void OnDownloadErrorFunction(string fileName, string error)
    {
        Debug.Log(string.Format("下载出错:文件名:{0},错误信息:{1}", fileName, error));
    }

    #endregion

    #region 补充元数据

    //补充元数据dll的列表
    //通过RuntimeApi.LoadMetadataForAOTAssembly()函数来补充AOT泛型的原始元数据
    private static List<string> AOTMetaAssemblyFiles { get; } = new() { "mscorlib.dll", "System.dll", "System.Core.dll", };
    private static Dictionary<string, TextAsset> s_assetDatas = new Dictionary<string, TextAsset>();
    private static Assembly _hotUpdateAss;
    
    public static byte[] ReadBytesFromStreamingAssets(string dllName)
    {
        if (s_assetDatas.ContainsKey(dllName))
        {
            return s_assetDatas[dllName].bytes;
        }

        return Array.Empty<byte>();
    }

    

    /// <summary>
    /// 为aot assembly加载原始metadata, 这个代码放aot或者热更新都行。
    /// 一旦加载后,如果AOT泛型函数对应native实现不存在,则自动替换为解释模式执行
    /// </summary>
    private static void LoadMetadataForAOTAssemblies()
    {
        /// 注意,补充元数据是给AOT dll补充元数据,而不是给热更新dll补充元数据。
        /// 热更新dll不缺元数据,不需要补充,如果调用LoadMetadataForAOTAssembly会返回错误
        HomologousImageMode mode = HomologousImageMode.SuperSet;
        foreach (var aotDllName in AOTMetaAssemblyFiles)
        {
            byte[] dllBytes = ReadBytesFromStreamingAssets(aotDllName);
            // 加载assembly对应的dll,会自动为它hook。一旦aot泛型函数的native函数不存在,用解释器版本代码
            LoadImageErrorCode err = RuntimeApi.LoadMetadataForAOTAssembly(dllBytes, mode);
            Debug.Log($"LoadMetadataForAOTAssembly:{aotDllName}. mode:{mode} ret:{err}");
        }
    }

    #endregion

    #region 运行测试

    void StartGame()
    {
        // 加载AOT dll的元数据
        LoadMetadataForAOTAssemblies();
        // 加载热更dll
#if !UNITY_EDITOR
        _hotUpdateAss = Assembly.Load(ReadBytesFromStreamingAssets("HotUpdate.dll"));
#else
        _hotUpdateAss = System.AppDomain.CurrentDomain.GetAssemblies().First(a => a.GetName().Name == "HotUpdate");
#endif
        Debug.Log("运行热更代码");
        StartCoroutine(Run_InstantiateComponentByAsset());
    }

    IEnumerator Run_InstantiateComponentByAsset()
    {
        // 通过实例化assetbundle中的资源,还原资源上的热更新脚本
        var package = YooAssets.GetPackage("DefaultPackage");
        var handle = package.LoadAssetAsync<GameObject>("Cube");
        yield return handle;
        handle.Completed += Handle_Completed;
    }

    private void Handle_Completed(AssetHandle obj)
    {
        Debug.Log("准备实例化");
        GameObject go = obj.InstantiateSync();
        Debug.Log($"Prefab name is {go.name}");
    }

    #endregion
}
//PS:版本不同可能有一些类名发生变化,请参照现阶段版本自行修改,官网可能更新不及时。

打包阶段
(这里演示的是pc平台)
打开菜单 HybridCLR/Generate/All,耐心等待之后,
回到Assets同级目录,将HybridCLRData/HotUpdateDlls/StandaloneWindows64/HotUpdate.dll复制到Assets/MyAssset/Codes内,并且加上后缀.bytes,这是包含热更逻辑代码的dll
在这里插入图片描述

再将HybridCLRData/AssembliesPostIl2CppStrip/StandaloneWindows64目录下的mscorlib.dll, System.dll, System.Core.dll这三个dll复制到Assets/MyAssset/Codes内,并且加上后缀.bytes,这是包含补充元数据的dll
在这里插入图片描述
在这里插入图片描述

打开菜单YooAsset/AssetBundle Builder,BuildModel选ForceRebuild,全量构建。一般第一次选这个,后面IncrementalBuild热更选增量构建,点击ClickBuild
在这里插入图片描述

构建完之后会自动打开构建后的资源目录,在Asset同级目录下的Bundles/StandaloneWindows64/DefaultPackage/2024-06-27-1194(构建时的版本号)
在这里插入图片描述

把里面的所有东西,放在本地资源服务器Nginx的目录下,之前代码里访问的地址:http://127.0.0.1:8084/TestProject/PC就是这里了,到时候热更就是从这里下载最新的资源
在这里插入图片描述

操作完上述之后,打开菜单 File/Build Settings/Build先把.exe打出来,双击运行,能看到先从http://127.0.0.1:8084/TestProject/PC路径下下载资源包,然后补充元数据,然后实例化了Cube预制体,执行了预制体上的代码,打印了“原始代码”。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

开始热更
终于到了激动人心的热更环节,前面搞了那么多就是为了热更代码和资源。接下来我们把cube预制体尺寸x改为10,将InstantiateByAsset脚本的输出从“原始代码”改为“热更后的代码”。
在这里插入图片描述
在这里插入图片描述

点击菜单 HybridCLR/CompileDll/ActiveBuildTarget,把新的HotUpdate.dll复制替换掉MyAsset里面原来的HotUpdate.dll,记得加后缀.bytes,另外三个mscorlib.dll, System.dll, System.Core.dll也复制替换过去记得加后缀.bytes
在这里插入图片描述
在这里插入图片描述

打开菜单YooAsset/AssetBundle Builder,BuildModel选IncrementalBuild,增量构建,点击ClickBuild,然后自动打开了生成的资源所在的文件夹,把这一堆生成的东西,复制到本地资源服务器Nginx的TestProject/PC目录下,之前的旧资源删掉。
在这里插入图片描述
在这里插入图片描述

然后重新打开之前的exe程序,看到代码和资源都热更了,大功告成!!!
在这里插入图片描述

七、最后

至于其他的更多细节,比如加密解密,资源收集设置等待,可以多多浏览官网以及官网的示例项目。
HybridCLR
YooAsset

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

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

相关文章

60种AI工具用法 学会探索AI的无限可能

外面还在卖的课程&#xff0c;学会探索AI的无限可能&#xff0c;从构建精准的提示词到获取个性化新闻&#xff0c;从快速制作PPT到短视频内容的智能提炼&#xff0c;再到编程、股市分析和视频剪辑&#xff0c;AI工具助您工作学习效率飞跃提升&#xff01; 百度网盘 请输入提取…

Linux多进程和多线程(五)进程间通信-消息队列

多进程(五) 进程间通信 消息队列 ftok()函数创建消息队列 创建消息队列示例 msgctl 函数示例:在上⼀个示例的基础上&#xff0c;加上删除队列的代码 发送消息 示例: 接收消息示例 多进程(五) 进程间通信 消息队列 消息队列是一种进程间通信机制&#xff0c;它允许两个或多个…

单例模式详解:概念与实用技巧

目录 单例模式单例模式结构单例模式适用场景单例模式优缺点练手题目题目描述输入描述输出描述输入示例输出示例提示信息题解 单例模式 单例模式是一种创建型设计模式&#xff0c; 让你能够保证一个类只有一个实例&#xff0c; 并提供一个访问该实例的全局节点。 只有一个实例的…

【深入理解Java虚拟机】判断垃圾-引用计数法及其缺陷

什么是引用计数法 引用计数法用来判断对象是否存活 给对象中添加一个引用计数器&#xff0c;每当有一个地方引用它时&#xff0c;计数器的值加一&#xff1b;当引用失效时&#xff0c;计数器的值就减一&#xff0c;任何时刻计数器为0的对象是不可能在被使用的。&#xff08;存…

c++类模板及应用

文章目录 为什么要有函数模板一般实现举例类模板举例 继承中类模板的使用特殊情况 友元函数模板类和静态成员类模板实践 为什么要有函数模板 项目需求: 实现多个函数用来返回两个数的最大值&#xff0c;要求能支持char类型、int类型、double 一般实现举例 类模板举例 继承中类…

2.2 ROS2话题通信

场景 话题通信是ROS中使用频率最高的一种通信模式&#xff0c;话题通信是基于发布订阅模式的&#xff0c;也即&#xff1a;一个节点发布消息&#xff0c;另一个节点订阅该消息。话题通信的应用场景也极其广泛&#xff0c;比如如下场景&#xff1a; 机器人在执行导航功能&#…

肺炎-X光-图像分类数据集

肺炎-X光-图像分类数据集 数据集&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1bt6tf-jHqgufKqPmCFHbrQ?pwdaj54 提取码&#xff1a;aj54 数据集信息介绍&#xff1a; 文件夹 健康 中的图片数量: 1575 文件夹 新冠肺炎 中的图片数量: 1728 文件夹 普通肺炎 中的…

AI:开发者的超级助手,而非取代者

AI&#xff1a;开发者的超级助手&#xff0c;而非取代者 引言 在这个日新月异的科技时代&#xff0c;人工智能&#xff08;AI&#xff09;已悄然渗透到我们生活的方方面面&#xff0c;尤其是在软件开发领域&#xff0c;它正以一种前所未有的方式改变着我们的工作方式。作为一名…

Redis 中的通用命令(命令的返回值、复杂度、注意事项及操作演示)

Redis 中的通用命令(高频率操作) 文章目录 Redis 中的通用命令(高频率操作)Redis 的数据类型redis-cli 命令Keys 命令Exists 命令Expire 命令Ttl 命令Type命令 Redis 的数据类型 Redis 支持多种数据类型&#xff0c;整体来说&#xff0c;Redis 是一个键值对结构的&#xff0c;…

《数据结构与算法基础 by王卓老师》学习笔记——2.5线性表的链式表示与实现1

1.链式表示 2.链表举例 3.链式存储的相关术语 4.三个讨论题

【软件测试】之自动化测试

&#x1f3c0;&#x1f3c0;&#x1f3c0;来都来了&#xff0c;不妨点个关注&#xff01; &#x1f3a7;&#x1f3a7;&#x1f3a7;博客主页&#xff1a;欢迎各位大佬! 文章目录 什么是自动化测试Selenium介绍什么是SeleniumSelenium的特点工作原理 SeleniumJava环境搭建下载…

数学建模------Matlab数据可视化

目录 1.plot函数 &#xff08;1&#xff09;函数介绍 &#xff08;2&#xff09;参数介绍 &#xff08;3&#xff09;图形美化 &#xff08;4&#xff09;背景更改 &#xff08;5&#xff09;多组绘制 &#xff08;6&#xff09;图形叠加 &#xff08;7&#xff09;添加…

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 英文单词联想(100分) - 三语言AC题解(Python/Java/Cpp)

🍭 大家好这里是清隆学长 ,一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 💻 ACM银牌🥈| 多次AK大厂笔试 | 编程一对一辅导 👏 感谢大家的订阅➕ 和 喜欢💗 📎在线评测链接 https://app5938.acapp.acwing.com.cn/contest/2/problem/OD…

Flume集群部署(手把手部署图文详细版)

前景概要&#xff1a; Kafka消息订阅系统在大数据业务中有着重要运用&#xff0c;尤其在实时业务中&#xff0c;kafka是必不可少的组件之一。 Flume是大数据组件中重要的数据采集工具&#xff0c;我们常利用Flume采集各种数据源的数据供其他组件分析使用。例如在实时业务中&…

大白菜U盘启动工具

大白菜如何u盘启动进winpe装系统大白菜是一款非常实用的U盘启动盘制作工具&#xff0c;可以帮助用户快速地将U盘制作成启动盘&#xff0c;从而方便地进行系统安装、维护和修复等操作。官方网站&#xff1a; 大白菜u盘启动盘制作工具_大白菜u盘装系统_大白菜pe_大白菜官网-首页…

机器人控制系列教程之Stewart平台简介和运动学分析

Stewart平台简介及应用场景 六自由度 Stewart 并联机器人结构简图如下图所示&#xff0c;主要有一个固定平台和一个移动平台以及六个可伸缩的推杆组成&#xff0c;通常情况下&#xff0c;固定平台与底座连接&#xff0c;移动平台在空间具有六个自由度&#xff0c;通过六个推杆…

设置Docker中时区不生效的问题

项目中使用docker-compose&#xff0c;并通过以下方式设置了时区 environment:- SET_CONTAINER_TIMEZONEtrue- CONTAINER_TIMEZONEAsia/Shanghai 但是并没有正确生效&#xff0c;网上有很多博客都在推荐这个做法&#xff0c;另外一种是使用标准环境标量 -TZAsia/Shangehai …

大型网站软件系统架构演进过程

在我们的生活中,通常会使用大型网站系统,比如购物网站淘宝,京东,阿里1688;大型搜索引擎网站百度,社交类的如腾讯旗下的微信,QQ及新浪旗下的微博等,他们通常都有一下特点: 高并发、大流量&#xff1a;这些系统必须能够处理成千上万甚至数百万的并发用户请求&#xff0c;以及持续…

Entity Framework EF Migration 迁移

针对Code First来说关注的只有实体类。当需求变更时只需要添加新的实体类或者在实体类中添加、删除、修改属性即可。但是修改完成之后要如何将修改同步到数据库中&#xff1f; migration 机制就出现了 ●启用Migrations   ●通过Add-Migration添加Migration   ●Update-D…