UE4 C++联网RPC教程笔记(一)(第1~4集)

news2024/10/7 10:20:20

UE4 C++联网RPC教程笔记(一)(第1~4集)

  • 前言
  • 1. 教程介绍与资源
  • 2. 自定义 Debug 功能
  • 3. Actor 的复制
  • 4. 联网状态判断

前言

本系列笔记将会对梁迪老师的《UE4C++联网RPC框架开发吃鸡》教程进行个人的知识点梳理与总结,此课程也像全反射零耦合框架的课程那样,已经超过报名截止时间了,无法通过正常方法观看。

笔者依旧是采取神奇的方法,通过手机浏览器(不同浏览器的效果有差别,有的会直接要求你登录,遇到这样的就换一个;还有可能点开网页会发现没有播放按钮,遇到这样的就换一个网页)搜索该课程后可以在课程预览界面观看,也可以在目录进行跳转,不过没有字幕。建议是在 PC 端的手机模拟器观看。

本课程集数不多,可以通过目录跳转看完,就不需要复制一串数字到 URL 来切换集数了。

笔者用的引擎版本是 4.26.2,老师推荐的引擎版本是 4.20,不同的版本可能在代码上有所区别,笔者会通过注释标明。

本系列文章不允许转载。

本系列笔记可供读者学习后用于复习回顾或参考代码来解决一些敲错了代码导致的 Bug。并且笔者只会贴出对应集数修改的代码内容,已经有了的部分代码基本都不会贴出来,以免笔记篇幅过长。

1. 教程介绍与资源

此处列出本课程需要翻阅的网址:虚幻文档关于 RPC 的讲解 >>【】

RPC 的全称是 Remote Procedure Calls 远程过程调用。

本课程篇幅较短,分两步走:1. RPC 基础 2. 分别用蓝图和 C++ 实现监听服务器。

2. 自定义 Debug 功能

打开 UE4,创建一个新的 C++ 第三人称游戏项目,需带有初学者内容包,命名为 RPCCourse

如果学过梁迪老师另一个课程《UE4全反射零耦合框架开发坦克游戏》的读者可能会有印象,因为这个自定义 Debug 功能也在那个课程里面实现了,学过的读者可自行决定是否再看一遍。

创建以下 C++ 类:

创建一个 Object,命名为 RPCHelper,路径为默认。

要实现自定义 Debug 功能,我们需要用到单例模式

RPCHelper.h

#include "CoreMinimal.h"
// 引入头文件
#include "Engine/GameEngine.h"

class RPCCOURSE_API DDRecord
{
private:
	// 自身单例
	static TSharedPtr<DDRecord> RecordInst;
	
	// 最终输出的字符串
	FString RecordInfo;
	// 显示时长
	float ShowTime;
	// 显示的颜色
	FColor ShowColor;

public:

	// 构造和析构函数不写内容,并且由于可能会被大量调用所以写成内联函数
	inline DDRecord() {}
	~DDRecord() {}

	static TSharedPtr<DDRecord> Get();
	
	// 初始化显示时长和颜色
	inline void InitParam(float InTime, FColor InColor)
	{
		ShowTime = InTime;
		ShowColor = InColor;
	}

	// 实际依赖引擎自带输出逻辑
	inline void Output()
	{
		if (GEngine)
			GEngine->AddOnScreenDebugMessage(-1, ShowTime, ShowColor, RecordInfo);
		// 清空最终输出字符串
		RecordInfo.Empty();
	}

	// 移位操作符重写,将传入的各种类型数据都转换成 FString 然后加入最终输出字符串
	inline DDRecord &operator<<(FName Info) { RecordInfo.Append(Info.ToString()); return *this; }
	inline DDRecord &operator<<(FText Info) { RecordInfo.Append(Info.ToString()); return *this; }
	inline DDRecord &operator<<(const char* Info) { RecordInfo += Info; return *this; }
	inline DDRecord &operator<<(const char Info) { RecordInfo.AppendChar(Info); return *this; }
	inline DDRecord &operator<<(int32 Info) { RecordInfo.Append(FString::FromInt(Info)); return *this; }
	inline DDRecord &operator<<(float Info) { RecordInfo.Append(FString::SanitizeFloat(Info)); return *this; }
	inline DDRecord &operator<<(double Info) { RecordInfo.Append(FString::SanitizeFloat(Info)); return *this; }
	inline DDRecord &operator<<(bool Info) { RecordInfo.Append(Info ? FString("true") : FString("false")); return *this; }
	inline DDRecord &operator<<(FVector2D Info) { RecordInfo.Append(Info.ToString()); return *this; }
	inline DDRecord &operator<<(FVector Info) { RecordInfo.Append(Info.ToString()); return *this; }
	inline DDRecord &operator<<(FRotator Info) { RecordInfo.Append(Info.ToString()); return *this; }
	inline DDRecord &operator<<(FQuat Info) { RecordInfo.Append(Info.ToString()); return *this; }
	inline DDRecord &operator<<(FTransform Info) { RecordInfo.Append(Info.ToString()); return *this; }
	inline DDRecord &operator<<(FMatrix Info) { RecordInfo.Append(Info.ToString()); return *this; }
	inline DDRecord &operator<<(FColor Info) { RecordInfo.Append(Info.ToString()); return *this; }
	inline DDRecord &operator<<(FLinearColor Info) { RecordInfo.Append(Info.ToString()); return *this; }

	// 在遇到 DDRecord 对象时(即下文的 Endl)输出,即调用 Output()
	inline void operator<<(DDRecord& Record) { Record.Output(); }
};

namespace DDH
{
	FORCEINLINE DDRecord& Debug(float InTime = 3000.f, FColor InColor = FColor::Yellow)
	{
		DDRecord::Get()->InitParam(InTime, InColor);
		return *DDRecord::Get();
	}

	FORCEINLINE DDRecord& Endl()
	{
		return *DDRecord::Get();
	}
}

RPCHelper.cpp

TSharedPtr<DDRecord> DDRecord::RecordInst = NULL;

TSharedPtr<DDRecord> DDRecord::Get()
{
	if (!RecordInst.IsValid())
		RecordInst = MakeShareable(new DDRecord());
	return RecordInst;
}

接下来到第三人称项目自带的这个 RPCCourseCharacter,重写它的 BeginPlay() 方法来测试一下我们的自定义 Debug 功能。

RPCCourseCharacter.h

protected:

	void BeginPlay() override;

RPCCourseCharacter.cpp

// 引入头文件
#include "RPCHelper.h"

void ARPCCourseCharacter::BeginPlay()
{
	Super::BeginPlay();

	// 输出 Debug
	DDH::Debug(20.f, FColor::Red) << "Hello UE4 " << 123 << 0.888 << FVector(30, 40, 50) << FColor::Red << DDH::Endl();
}

编译后运行游戏,左上角输出红色的 Debug 语句,20 秒后消失。

在这里插入图片描述
继续改进下,让 Debug 支持更多输出方式,比如输出到 Output Log 控制台里,并且有日志记录、警告、报错模式。

RPCHelper.h

class RPCCOURSE_API DDRecord
{
public:

	// 状态模式,0:Debug,1:Log,2:Warning,3:Error
	uint8 PatternID;

public:

	inline void Output()
	{
		switch (PatternID) {
		case 0:
			{
				if (GEngine)
					GEngine->AddOnScreenDebugMessage(-1, ShowTime, ShowColor, RecordInfo);
			}
			break;
		case 1:
			{
				UE_LOG(LogTemp, Log, TEXT("%s"), *RecordInfo);
			}
			break;
		case 2:
			{
				UE_LOG(LogTemp, Warning, TEXT("%s"), *RecordInfo);
			}
			break;
		case 3:
			{
				UE_LOG(LogTemp, Error, TEXT("%s"), *RecordInfo);
			}
			break;
		}
		
		RecordInfo.Empty();
	}
};

namespace DDH
{
	FORCEINLINE DDRecord& Debug(float InTime = 3000.f, FColor InColor = FColor::Yellow)
	{
		DDRecord::Get()->PatternID = 0;		// 初始化
		DDRecord::Get()->InitParam(InTime, InColor);
		return *DDRecord::Get();
	}

	// 只改变输出颜色,不管显示时间
	FORCEINLINE DDRecord& Debug(FColor InColor)
	{
		return Debug(3000.f, InColor);
	}

	FORCEINLINE DDRecord& Log()
	{
		DDRecord::Get()->PatternID = 1;
		return *DDRecord::Get();
	}

	FORCEINLINE DDRecord& Warning()
	{
		DDRecord::Get()->PatternID = 2;
		return *DDRecord::Get();
	}

	FORCEINLINE DDRecord& Error()
	{
		DDRecord::Get()->PatternID = 3;
		return *DDRecord::Get();
	}
}

最后测试一下日志记录模式。

RPCCourseCharacter.cpp

void ARPCCourseCharacter::BeginPlay()
{
	Super::BeginPlay();

	DDH::Log() << "Hello UE4 " << 123 << 0.888 << FVector(30, 40, 50) << FColor::Red << DDH::Endl();
}

编译后,打开 Window -> Develop Tools -> Output Log,运行游戏,可以看到日志输出了 Debug 语句。

在这里插入图片描述

最后将 BeginPlay() 里的 Debug 语句删除掉。

3. Actor 的复制

以下知识点内容截取自梁迪老师准备的 RPC 联网文档:

(1)bool 变量 bNetLoadOnClient
这个变量是给一开始就放置在场景中的对象使用的。
如果bNetLoadOnClient 设置为 true,当客户端连接上服务端时,客户端也会存在这个对象。
如果 bNetLoadOnClient 设置为 false,当客户端连接上服务端时,客户端不会存在这个对象。
SetReplicates 无论是否为 true 都不会影响这个变量的作用。(关于 SetReplicates 下面会讲解)

在默认路径下新建 3 个 C++ 的 Actor 类,分别命名为 RPCActorCubeReplicateCubeNoReplicate

我们先用 RPCActor 来测试 bNetLoadOnClient

RPCActor.cpp

ARPCActor::ARPCActor()
{


	bNetLoadOnClient = false;	// 设置为 不网络同步到客户端
}

来到角色类,在 BeginPlay() 里输出场上 RPCActor 实例的数量。

RPCCourseCharacter.cpp

// 引入头文件
#include "Kismet/GameplayStatics.h"
#include "RPCActor.h"

void ARPCCourseCharacter::BeginPlay()
{
	Super::BeginPlay();

	// 寻找场景中的 RPCActor
	TArray<AActor*> ActArray;
	UGameplayStatics::GetAllActorsOfClass(GetWorld(), ARPCActor::StaticClass(), ActArray);
	DDH::Debug() << "RPCActor Num --> " << ActArray.Num() << DDH::Endl();
}

编译后,将 C++ 类的 RPCActor 拖进场景,随后作以下设置(对于 4.26 版本会多出第 2 步)。运行后结果如图所示。

在这里插入图片描述
输出场景内 RPCActor 的数量为 1 的语句是服务端发出来的,输出数量为 0 的语句则是客户端发出来的。至于为何会分别输出了两遍,是因为在编辑器中,调用引擎自带的屏幕输出方法,会使语句在每个端都输出一次。

同时因为 RPCActor 设置了 bNetLoadOnClientfalse,所以 RPCActor 只存在于服务端。

RPCActor.cpp

ARPCActor::ARPCActor()
{

	// 重新设置为 网络同步到客户端
	bNetLoadOnClient = true;
}

编译后,将原本场景里的 RPCActor 删除,重新放置一个 RPCActor。运行游戏,结果如图所示:

在这里插入图片描述
这时服务端和客户端的场景里都存在这个 RPCActor 的实例。

(2)SetReplicates(bool)
调用 SetReplicates(true) 设置 Actor 可以复制。
调用 SetReplicates(false) 设置 Actor 不可以复制。

当在服务端 Spawn 可复制的 Actor 时,客户端会生成。
当在客户端 Spawn 可复制的 Actor 时,其他端不会生成。

测试下 SetReplicates(bool)bNetLoadOnClient 共同作用是什么样的效果。

RPCActor.cpp

ARPCActor::ARPCActor()
{

	// bNetLoadOnClient 设置为 true 时,如果该对象是一开始就在场景中的对象,
	// 客户端连接到服务端时该对象也会存在,与 SetReplicates 是否为 true 没有关系
	SetReplicates(true);
	bNetLoadOnClient = false;
}

编译后,将原本场景里的 RPCActor 删除,重新放置一个 RPCActor。运行游戏,结果如图所示:

在这里插入图片描述
又变成了客户端没有 RPCActor,服务端有。说明确实 bNetLoadOnClient 的优先级比 SetReplicates(bool) 更高。

接下来单独测试一下 SetReplicates(bool)。我们给 CubeReplicate 和 CubeNoReplicate 添加一些组件方便观察,前者设置可复制,后者设置不可复制。

CubeReplicate.h

protected:

	UStaticMeshComponent* CubeMesh;

CubeReplicate.cpp

 // 引入头文件
#include "Components/StaticMeshComponent.h"
#include "UObject/ConstructorHelpers.h"
#include "RPCHelper.h"

ACubeReplicate::ACubeReplicate()
{
	PrimaryActorTick.bCanEverTick = true;

	// 设置复制
	SetReplicates(true);

	RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("RootScene"));

	CubeMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("CubeMesh"));
	CubeMesh->SetupAttachment(RootComponent);

	// 附加模型
	ConstructorHelpers::FObjectFinder<UStaticMesh> StaticCubeMesh(TEXT("StaticMesh'/Game/StarterContent/Shapes/Shape_Cylinder.Shape_Cylinder'"));
	CubeMesh->SetStaticMesh(StaticCubeMesh.Object);
}

CubeNoReplicate.h

protected:

	UStaticMeshComponent* CubeMesh;

CubeNoReplicate.cpp

 // 引入头文件
#include "Components/StaticMeshComponent.h"
#include "UObject/ConstructorHelpers.h"
#include "RPCHelper.h"

ACubeNoReplicate::ACubeNoReplicate()
{
	PrimaryActorTick.bCanEverTick = true;

	// 设置不复制
	SetReplicates(false);

	RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("RootScene"));

	CubeMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("CubeMesh"));
	CubeMesh->SetupAttachment(RootComponent);

	// 附加模型
	ConstructorHelpers::FObjectFinder<UStaticMesh> StaticCubeMesh(TEXT("StaticMesh'/Game/StarterContent/Shapes/Shape_WideCapsule.Shape_WideCapsule'"));
	CubeMesh->SetStaticMesh(StaticCubeMesh.Object);
}

来到 RPCActor,让前面的两个 Cube 只在服务端生成。

RPCActor.cpp

// 引入头文件
#include "CubeReplicate.h"
#include "CubeNoReplicate.h"
#include "RPCHelper.h"

ARPCActor::ARPCActor()
{

	//SetReplicates(true);
	bNetLoadOnClient = true;	// 设置为 网络同步到客户端
}

void ARPCActor::BeginPlay()
{
	Super::BeginPlay();
	
	// 判断是不是服务端
	if (GetWorld()->IsServer()) {
		GetWorld()->SpawnActor<ACubeReplicate>(ACubeReplicate::StaticClass(), GetActorLocation() + FVector::RightVector * 300.f, FQuat::Identity.Rotator());

		GetWorld()->SpawnActor<ACubeNoReplicate>(ACubeNoReplicate::StaticClass(), GetActorLocation() - FVector::RightVector * 300.f, FQuat::Identity.Rotator());
	}
}

编译后,将场景内的 RPCActor 删除。

新建一个 Blueprint 文件夹,在里面创建一个基于 RPCActor 的蓝图,命名为 RPCActor_BP,然后将其拖进场景内。

运行游戏,得到效果如图。可以看到服务端出现了 CubeReplicate 和 CubeNoReplicate 的实例,但是客户端没有 CubeNoReplicate 的实例。说明 SetReplicates(bool) 生效了。

在这里插入图片描述
再试一下只在客户端生成,只需要在判断条件前面加个 ! 取反就可以了。

RPCActor.cpp

void ARPCActor::BeginPlay()
{
	Super::BeginPlay();
	
	// 判断是不是客户端
	if (!GetWorld()->IsServer()) {
		GetWorld()->SpawnActor<ACubeReplicate>(ACubeReplicate::StaticClass(), GetActorLocation() + FVector::RightVector * 300.f, FQuat::Identity.Rotator());

		GetWorld()->SpawnActor<ACubeNoReplicate>(ACubeNoReplicate::StaticClass(), GetActorLocation() - FVector::RightVector * 300.f, FQuat::Identity.Rotator());
	}
}

编译后再次运行,可以看到这回服务端场景内没有生成两个 Actor 的实例,而客户端出现了 CubeReplicate 和 CubeNoReplicate 的实例。说明客户端内生成对象时,即便这个对象是可复制的,它也不会生成到服务端。

在这里插入图片描述

最后将 RPCCourseCharacter.cpp 的 BeginPlay() 方法里的 Debug 语句注释掉。

4. 联网状态判断

本节课需要看的官方参考文档:网络概述 >>【】

以下知识点内容截取自梁迪老师准备的 RPC 联网文档:

(1)AActor 的 HasAuthority(),返回 true 是说明 Actor 是该端创建的角色。

在关卡蓝图或者是 GameMode 以及默认放在场景中的 Actor 等对象使用这个函数可以用来判断是否是服务端,因为关卡蓝图和 GameMode 与默认放在场景中的对象可以看做是由服务端生成的。

不推荐用 HasAuthority() 来判断当前端是不是服务端。梁迪老师推荐的用法是用来它来做下面这个 Actor 的角色判断。

(2)Actor 的角色判断
AActor 里的 ENetRole Role 枚举是用来识别角色的 Actor 的身份的。ENetRole 的几个值:

ROLE_None:该 Actor 在网络游戏中无角色,不会复制。
ROLE_SimulatedProxy:这个 Actor 是其他客户端在本机客户端的一个模拟代理
ROLE_AutonomousProxy:这个 Actor 是本机客户端的自己控制的角色
ROLE_Authority:这个 Actor 是服务器上的 Actor
ROLE_MAX:官方没有解释,笔者个人猜测应该是代表该枚举的最大枚举值。

(3)是否是服务端判断,不推荐使用 HasAuthority() 来判断
推荐使用 GetWorld()->IsServer() 或者 GetNetMode() 判断

(4)端的判断
使用 GetNetMode() 函数可以获取端的属性 ENetMode,分类如下:

NM_Standalone:单独端,单机游戏
NM_DedicatedServer:专用服务器
NM_ListenServer:监听服务器
NM_Client:客户端
NM_MAX:官方没有解释,笔者个人猜测应该是代表该枚举的最大枚举值。

接下来我们打算测试一下 IsServer()HasAuthority() 在 “判断当前端是不是服务端” 的需求上表现如何。

在复制 Cube 和不复制 Cube 的 BeginPlay() 函数输出一下调用上面两个方法后返回的结果。

CubeReplicate.cpp

void ACubeReplicate::BeginPlay()
{
	Super::BeginPlay();

	DDH::Debug() << "IsServer --> " << GetWorld()->IsServer() << " ; HasAuthority() --> " << HasAuthority() << "  ACubeReplicate BeginPlay" << DDH::Endl();
}

CubeNoReplicate.cpp

void ACubeNoReplicate::BeginPlay()
{
	Super::BeginPlay();

	DDH::Debug() << "IsServer --> " << GetWorld()->IsServer() << " ; HasAuthority() --> " << HasAuthority() << "  ACubeNoReplicate BeginPlay" << DDH::Endl();	
}

接上一节课结尾的代码,此时两个 Cube 的生成逻辑是在客户端上运行的,所以只有在客户端才会生成这两个 Cube,服务端不会生成。

编译后运行,可以看到左上角客户端输出的两句 Debug 信息,IsServer() 返回的结果是 false,符合预期;而 HasAuthority() 返回的结果是 true,说明这个方法不一定能判断当前端是服务端还是客户端。

在这里插入图片描述
重新调整下,让两个 Cube 在服务端生成。

RPCActor.cpp

void ARPCActor::BeginPlay()
{
	Super::BeginPlay();
	
	// 将判断表达式的 ! 去掉
	if (GetWorld()->IsServer()) {
		GetWorld()->SpawnActor<ACubeReplicate>(ACubeReplicate::StaticClass(), GetActorLocation() + FVector::RightVector * 300.f, FQuat::Identity.Rotator());

		GetWorld()->SpawnActor<ACubeNoReplicate>(ACubeNoReplicate::StaticClass(), GetActorLocation() - FVector::RightVector * 300.f, FQuat::Identity.Rotator());
	}
}

编译后运行,下面和中间的 Debug 语句是服务端打印的,上面的 Debug 语句是客户端打印的。此时 HasAuthority() 确实在服务端则输出了 true,在客户端输出了 false。两次测试结果相比之下还是 IsServer() 更适合用于判断当前端的性质。

在这里插入图片描述
随后将 CubeReplicate 和 CubeNoReplicate 的 BeginPlay() 内的 Debug 语句注释掉。

接下来我们测试一下 AActor 的 GetNetMode() 方法,用于获取当前端类型 ENetMode 的值。

RPCActor.h

protected:

	// 获取端类型的枚举后以文本形式输出
	void EchoNetMode();

RPCActor.cpp

void ARPCActor::BeginPlay()
{
	

	// 测试完毕后记得注释掉
	EchoNetMode();
}

void ARPCActor::EchoNetMode()
{
	ENetMode NetMode = GetNetMode();
	switch (NetMode)
	{
	case NM_Standalone:
		DDH::Debug() << "NM_Standalone" << DDH::Endl();
		break;
	case NM_DedicatedServer:
		DDH::Debug() << "NM_DedicatedServer" << DDH::Endl();
		break;
	case NM_ListenServer:
		DDH::Debug() << "NM_ListenServer" << DDH::Endl();
		break;
	case NM_Client:
		DDH::Debug() << "NM_Client" << DDH::Endl();
		break;
	case NM_MAX:
		DDH::Debug() << "NM_MAX" << DDH::Endl();
		break;
	}
}

编译后运行游戏,很明显 Client 是客户端输出的,ListenServer 是服务端输出的。

在这里插入图片描述
如果将运行模式调整如下后运行游戏,则会显示 NM_Standalone。

在这里插入图片描述

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

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

相关文章

软考29-上午题-排序

一、排序的基本概念 1-1、稳定性 稳定性指的是相同的数据所在的位置经过排序后是否发生变化。若是排序后&#xff0c;次序不变&#xff0c;则是稳定的。 1-2、归位 每一趟排序能确定一个元素的最终位置。 1-3、内部排序 排序记录全部存放在内存中进行排序的过程。 1-4、外部…

白话微机:6.解释RTOS以及一些考研面试问题

一. 前言&#xff08;总结世界观&#xff09; 很久很久以前&#xff0c;有这样一个世界&#xff0c;这个世界有着现实世界一样的元素&#xff1a;那里的人又有一个别的名字叫做“数据”&#xff0c;人有0有1&#xff1b;人们也有住房&#xff0c;这些住房在这个世界叫做“存储器…

C++-opencv的imread、imshow、waitkey、namedWindow

在C中使用OpenCV时&#xff0c;imread和imshow是两个非常基础且常用的函数&#xff0c;用于读取图像和显示图像。以下是这两个函数的简要说明和如何一起使用它们的示例。 imread函数 imread用于从指定的文件路径读取图像。它将图像读入为cv::Mat对象&#xff0c;这是OpenCV中…

单机环境搭建Redis伪集群

1、Redis版本 [rootwsdhla ~]# redis-server -v Redis server v6.2.6 sha00000000:0 mallocjemalloc-5.1.0 bits64 buildbf23dac15dfc00fa[rootwsdhla ~]# redis-cli -v redis-cli 6.2.62、创建节点目录 创建6个节点目录&#xff0c;分别复制一份redis.conf并编辑&#xff1a…

关于Sora的一些紧迫问题...

OpenAI Sora 概述 OpenAI最新的创新&#xff0c;Sora&#xff0c;在人工智能领域开辟了新的天地。Sora是一个文本到视频的扩散模型&#xff0c;可以将文本描述转化为逼真的视频内容。它解决了一个重大的技术挑战&#xff0c;即在视频中保持主体的一致性&#xff0c;即使它们暂…

anomalib1.0学习纪实-续2:三个文件夹

为了读懂程序&#xff0c;有三个最重要的文件夹&#xff0c;如下图&#xff1a; 正好对应四个类&#xff0c;如下图&#xff1a; 四个类的来源如下图所示&#xff1a; 注意&#xff0c;MVTec是个大类&#xff0c;里面用到了这里的第四个类MVTecDataset&#xff0c;代码如下。…

Windows制作Ubuntu的U盘启动盘

概要&#xff1a; 本篇演示在Windows10中制作Ubuntu22.04的U盘启动盘 一、下载Ubuntu22.04的iso文件 在浏览器中输入https://ubuntu.com去Ubuntu官网下载Ubuntu22.04的iso文件 二、下载Ultraiso 在浏览器中输入https://www.ultraiso.com进入ultraiso官网 点击FREE TRIAL&a…

设计模式复习

单例模式 确保一个类最多只有一个实例&#xff0c;并提供一个全局访问点。 &#xff08;某个类的对象有且仅有一个&#xff0c;单例的对象充当的是全局变量的角色&#xff0c;为什么在C里面不直接使用全局变量&#xff0c;而是使用单例来代替全局变量&#xff0c;因为如果直接…

07 按键控制 LED

按键简介 按键开关是一种电子开关&#xff0c;属于电子元器件类。常见的按键开关有两种&#xff0c;第一种是轻触式按键开关&#xff08;简称轻触开关&#xff09;&#xff0c;使用时以向开关的操作方向施加压力使内部电路闭合接通&#xff0c;当撤销压力时开关断开&#xff0…

html的表单标签(上):form标签和input标签

表单标签 表单是让用户输入信息的重要途径。 用表单标签来完成与服务器的一次交互&#xff0c;比如你登录QQ账号时的场景。 表单分成两个部分&#xff1a; 表单域&#xff1a;包含表单元素的区域&#xff0c;用form标签来表示。表单控件&#xff1a;输入框&#xff0c;提交按…

48 slab 的实现

前言 这里说的是 内核中分配小对象的一种内存分配方式 slab 呵呵 经典程度不必多说了, 内核使用的大多数数据结构 基本上是基于 slab 进行内存分配的 这里 我们来看一下 slab 如何分配对象 几个分配层级, c->free_list, c->page, c->partial, new_slab 1. 先…

【HarmonyOS】【DevEco ohpm ERROR: NOTFOUND package “@ohos/hypium“如何解决

参考 &#xff1a;&#xff08;无效&#xff09; 华为开发者论坛 DevEco创建项目时的错误解决_6 月 优质更文活动_路北路陈_InfoQ写作社区 解决&#xff1a; HormonyOS-DevEco Studio新建空项目ERROR解决_oh_modules\ohos\hypium-CSDN博客 将 .ohpm文件夹中的hypium文件夹复…

Openharmony - HDF驱动小示例和测试程序

By: fulinux E-mail: fulinux@sina.com Blog: https://blog.csdn.net/fulinus 喜欢的盆友欢迎点赞和订阅! 你的喜欢就是我写作的动力! 目录 1. 创建KHDF示例驱动1.1. 创建KHDF项目1.2. HDF驱动程序实现1.2.1. 驱动入口1.2.2. 实现Dispatch方法1.2.3. 硬件业务初始化与释放1.2…

(每日持续更新)信息系统项目管理(第四版)(高级项目管理)考试重点整理第10章 项目进度管理(六)

博主2023年11月通过了信息系统项目管理的考试&#xff0c;考试过程中发现考试的内容全部是教材中的内容&#xff0c;非常符合我学习的思路&#xff0c;因此博主想通过该平台把自己学习过程中的经验和教材博主认为重要的知识点分享给大家&#xff0c;希望更多的人能够通过考试&a…

Flink Catalog 解读与同步 Hudi 表元数据的最佳实践

博主历时三年精心创作的《大数据平台架构与原型实现&#xff1a;数据中台建设实战》一书现已由知名IT图书品牌电子工业出版社博文视点出版发行&#xff0c;点击《重磅推荐&#xff1a;建大数据平台太难了&#xff01;给我发个工程原型吧&#xff01;》了解图书详情&#xff0c;…

python-Scrapy框架入门1111111111111

此网站爬取江南大学官网新闻信息 https://news.jiangnan.edu.cn/yw.htm Scrapy安装 mac | Linux : pip install scrapy windows: pip install wheelpip install pywin32安装Twisted &#xff1a; pip install Twisted_iocpsupport-1.0.2-cp310-cp310-win_amd64.whl (该文件去…

使用Nginx或者Fiddler快速代理调试

1 背景问题 在分析业务系统程序问题时,存在服务系统环境是其它部门或者其它小组搭建或运维的,并且现在微服务时代,服务多且复杂,在个人机器上搭建起如此环境,要么费事费力,要么不具备充足条件。 急需有一种方法或者工具可以快速辅助调试定位分析问题。本文下面介绍代理方…

算法沉淀——多源 BFS(leetcode真题剖析)

算法沉淀——多源 BFS&#xff08;leetcode真题剖析&#xff09; 01.矩阵02.飞地的数量03.地图中的最高点04.地图分析 多源 BFS 是指从多个源点同时进行广度优先搜索的算法。在传统的 BFS 中&#xff0c;我们通常从一个起始点开始&#xff0c;逐层遍历所有的相邻节点。而在多…

IDEA-常用插件

1、Mybatis Log Free 当我们使用mybatis log在控制台输出sql 内容&#xff0c;输出内容将语句与参数分开打印&#xff0c;还需要手动将参数替换到指定位置。 使用对应插件后&#xff0c;自动将输出内容组装成完整的可直接执行的SQL 在插件市场 查看对应名称&#xff0c;并安装。…

KMPC++(Acwing)

代码&#xff1a; #include <iostream>using namespace std;const int N 100010, M 1000010;int n, m; int ne[N]; char s[M], p[N];int main() {cin >> n >> p 1 >> m >> s 1;for (int i 2, j 0; i < n; i ){while (j && p[…