UE4 C++联网RPC教程笔记(三)(第8~9集)完结

UE4 C++联网RPC教程笔记(三)(第8~9集)完结

  • 8. exe 后缀实现监听服务器
  • 9. C++ 实现监听服务器

8. exe 后缀实现监听服务器

前面我们通过蓝图节点实现了局域网连接的功能,实际上我们还可以给项目打包后生成的 .exe 文件创建一个快捷方式,然后修改这个快捷方式的属性中的目标就可以实现简易的联网功能。

下面内容截取自梁迪老师准备的 RPC 联网文档:

使用 .exe 后缀输入和 open IP 地址联网

注意:这里只讨论 NM_Standalone、NM_ListenServer 以及 NM_Client 的情况

  1. NM_Standalone:打包出 exe 后,不创建快捷方式直接运行 exe,执行 GetNetMode() 返回 NM_Standalone 类型,在这个状态下打印 GetWorld()->IsServer() 以及 HasAuthority() 都是 true,如果在存在监听服务器的情况下,运行命令行 open 127.0.0.1,该单独端就会链接上监听服务器,执行 GetNetMode() 返回 NM_Client 类型,打印 GetWorld()->IsServer() 以及 HasAuthority() 都是 false,如果再次运行命令行 open 127.0.0.1,该端会先断开服务器,然后再链接一次
  2. NM_ListenServer:打包出来的 exe 生成快捷方式,并且在快捷方式的属性下在 exe 结尾添加 (空格)?listen,如:RPCProject.exe ?listen,运行该快捷方式就会运行监听服务器端,执行 GetNetMode() 返回 NM_ListenServer 类型,打印 GetWorld()->IsServer() 以及 HasAuthority() 都是true,如果运行命令行 open 127.0.0.1,该监听服务器端就会变成单独端 NM_Standalone
  3. NM_Client:打包出来的 exe 生成快捷方式,并且在快捷方式的属性下在 exe 结尾添加 (空格)127.0.0.1 -game,如:RPCProject.exe 127.0.0.1 -game,运行该快捷方式,如果存在监听端,就会链接上监听服务器,成为客户端,执行 GetNetMode() 返回 NM_Client 类型,打印 GetWorld()->IsServer() 以及 HasAuthority() 都是 false

接下来我们在 GameMap 里用到的玩家控制器中添加一些打印当前端的逻辑。

RPCController.h

protected:.

	void EchoNetMode();

RPCController.cpp

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

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

	// 限定打开的窗口尺寸。此处老师将变量名拼写错成 “Src”
	FString ScreenCommand = FString("r.setres 1280x720w");
	ConsoleCommand(ScreenCommand);

	bShowMouseCursor = false;
	FInputModeGameOnly InputMode;
	SetInputMode(InputMode);

	// 打印当前的端
	EchoNetMode();
}

void ARPCController::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;
	}
}

编译后,将默认关卡设置为 GameMap。随后开始打包。

在这里插入图片描述
打包成功后,运行 .exe 文件,可以看到左上角打印了当前的端名以及控制器名。

在这里插入图片描述
创建 .exe 文件的一个快捷方式,命名为 RPCCourseServer (保留 .exe 后缀),随后修改该文件的属性。

在这里插入图片描述
运行 RPCCourseServer.exe,可以看到左上角已经变成了聆听服务器。

在这里插入图片描述
再创建一个快捷方式,命名为 RPCCourseClient,这次给属性里的目标添加后缀 (空格)127.0.0.1 -game。先运行 RPCCourseServer,然后再运行 RPCCourseClient.exe,可以看到后者的窗口里左上角显示是客户端。

在这里插入图片描述
此时在服务端按下 J 键,只有在客户端能看到角色处生成了红色的数字 1。说明联网方法和变量都是可以用的。

关掉客户端,服务端左上角会显示客户端的控制器登出了。

在这里插入图片描述
保持服务端开启,运行 RPCCourse.exe,按 ~ 键(波浪符)呼出控制台,输入 open 127.0.0.1。可以看到独立端加入了服务端,并且原独立端左上角输出了当前为客户端。

在这里插入图片描述
此时在服务端按下 J 键,也是只有原独立端可以看到自己角色位置生成了一个红色的数字 1。

在服务端呼出控制台然后输入 open 127.0.0.1,服务器会关闭。

在这里插入图片描述

9. C++ 实现监听服务器

前面我们用蓝图和快捷方式实现聆听服务器联机,接下来我们尝试下用 C++ 来实现同样的效果。下面内容截取自梁迪老师准备的 RPC 联网文档:

创建寻找加入会话 C++ 模式(这里只实现局域网)

C++ 联网步骤和蓝图基本相同,但是中间多了一个 StartSession() 方法需要调用,主要可以参考

  1. UCreateSessionCallbackProxy
  2. UStartSessionCallbackProxy
  3. UFindSessionsCallbackProxy
  4. UJoinSessionCallbackProxy
  5. UDestroySessionCallbackProxy

这些类的实现,具体实现参考项目里的 URPCInstance 类。

UE4 官方推荐将联网的逻辑放在 GameInstance 下处理,GameInstance 在整个游戏所有关卡中都存在,用来传递关卡数据,在保存数据方面起作用,联网数据放在 GameInstance 下方便在任何关卡去操作联网

接下来开始实操。在默认路径下创建一个 C++ 的 Instance 类,命名为 RPCInstance

将默认地图设置成 MenuMap。

来到主界面 UI 类,声明一个 URPCInstance 的指针和相应的注册方法,用来保存对 GameInstance 的引用,并且声明两个蓝图可调用的方法用于接入主界面的按钮点击事件。

MenuWidget.h

// 提前声明
class URPCInstance;

UCLASS()
class RPCCOURSE_API UMenuWidget : public UUserWidget
{
	GENERATED_BODY()
	
public:

	void AssignRPCInstance(URPCInstance* InInstance);

	UFUNCTION(BlueprintCallable)
	void LANServerEvent();

	UFUNCTION(BlueprintCallable)
	void LANClientEvent();
	
public:

	URPCInstance* RPCInstance;
};

MenuWidget.cpp

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

void UMenuWidget::AssignRPCInstance(URPCInstance* InInstance)
{
	RPCInstance = InInstance;
}

void UMenuWidget::LANServerEvent()
{
	RPCInstance->HostSession();
}

void UMenuWidget::LANClientEvent()
{
	RPCInstance->ClientSession();
}

RPCInstance 里承载着联网相关的逻辑,主要都是调用网络模块的 API 和利用委托绑定回调函数。

RPCInstance.h

// 引入头文件
#include "Interfaces/OnlineSessionInterface.h"	// 如果这个不行就用下面这句
//#include "../Plugins/Online/OnlineSubsystem/Source/Public/Interfaces/OnlineSessionInterface.h"	
#include "Delegates/IDelegateInstance.h"

#include "RPCInstance.generated.h"

// 提前声明
class IOnlineSubsystem;
class APlayerController;

UCLASS()
class RPCCOURSE_API URPCInstance : public UGameInstance
{
	GENERATED_BODY()
	
public:

	URPCInstance();

	// 注册玩家控制器,并且获取联网系统和端 ID
	void AssignPlayerController(APlayerController* InController);

	// 创建会话
	void HostSession();

	// 寻找会话
	void ClientSession();

	// 销毁会话
	void DestroySession();

protected:

	// 开启服务器回调函数
	void OnCreateSessionComplete(FName SessionName, bool bWasSuccessful);
	void OnStartOnlineGameComplete(FName SessionName, bool bWasSuccessful);

	// 加入服务器回调函数
	void OnFindSessionsComplete(bool bWasSuccessful);
	void OnJoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result);

	// 销毁会话回调函数
	void OnDestroySessionComplete(FName SessionName, bool bWAsSuccessful);

protected:

	APlayerController* PlayerController;

	// 开启服务器委托与句柄
	FOnCreateSessionCompleteDelegate OnCreateSessionCompleteDelegate;
	FOnStartSessionCompleteDelegate OnStartSessionCompleteDelegate;

	FDelegateHandle OnCreateSessionCompleteDelegateHandle;
	FDelegateHandle OnStartSessionCompleteDelegateHandle;

	// 加入服务器委托与句柄
	FOnFindSessionsCompleteDelegate OnFindSessionsCompleteDelegate;
	FOnJoinSessionCompleteDelegate OnJoinSessionCompleteDelegate;

	FDelegateHandle OnFindSessionsCompleteDelegateHandle;
	FDelegateHandle OnJoinSessionCompleteDelegateHandle;

	// 销毁会话委托与句柄
	FOnDestroySessionCompleteDelegate OnDestroySessionCompleteDelegate;
	FDelegateHandle OnDestroySessionCompleteDelegateHandle;

	// 联网系统
	IOnlineSubsystem* OnlineSub;

	// 端的 ID
	TSharedPtr<const FUniqueNetId> UserID;

	// 保存寻找到的 Sessions
	TSharedPtr<FOnlineSessionSearch> SearchObject;
};

RPCInstance.cpp

// 引入头文件
#include "../Plugins/Online/OnlineSubsystem/Source/Public/OnlineSubsystem.h"
#include "../Plugins/Online/OnlineSubsystem/Source/Public/OnlineSessionSettings.h"
#include "../Plugins/Online/OnlineSubsystem/Source/Public/Interfaces/OnlineSessionInterface.h"
#include "../Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Public/OnlineSubsystemUtils.h"
#include "GameFramework/PlayerController.h"
#include "RPCHelper.h"
#include "Kismet/GameplayStatics.h"

URPCInstance::URPCInstance()
{
	// 绑定回调函数
	OnCreateSessionCompleteDelegate = FOnCreateSessionCompleteDelegate::CreateUObject(this, &URPCInstance::OnCreateSessionComplete);
	OnStartSessionCompleteDelegate = FOnStartSessionCompleteDelegate::CreateUObject(this, &URPCInstance::OnStartOnlineGameComplete);
	OnFindSessionsCompleteDelegate = FOnFindSessionsCompleteDelegate::CreateUObject(this, &URPCInstance::OnFindSessionsComplete);
	OnJoinSessionCompleteDelegate = FOnJoinSessionCompleteDelegate::CreateUObject(this, &URPCInstance::OnJoinSessionComplete);
	OnDestroySessionCompleteDelegate = FOnDestroySessionCompleteDelegate::CreateUObject(this, &URPCInstance::OnDestroySessionComplete);
}

void URPCInstance::AssignPlayerController(APlayerController* InController)
{
	PlayerController = InController;
	
	// 获取 OnlineSub
	// 获取方式一:Online::GetSubsystem(GetWorld(), NAME_None),推荐方式
	// 获取方式二:使用 IOnlineSubsystem::Get(),直接获取可以 CreateSession 但是 JoinSession 后客户端没有跳转场景
	OnlineSub = Online::GetSubsystem(PlayerController->GetWorld(), NAME_None);

	// 获取 UserID
	// 获取方式一:UGameplayStatics::GetGameInstance(GetWorld())->GetLocalPlayers()[0]->GetPreferredUniqueNetId()
	if (GetLocalPlayers().Num() == 0) {
		DDH::Debug() << "No LocalPlayer Exists, Can't Get UserID" << DDH::Endl();
	}
	else {
		UserID = (*GetLocalPlayers()[0]->GetPreferredUniqueNetId()).AsShared();
	}

#if 0
	// 获取方式二:使用 PlayerState 获取,该方式在打包成 exe 运行无问题,但是在编辑器模式下运行多个窗口,就会找不到 PlayerState
	if (PlayerController->PlayerState)
		UserID = PlayerController->PlayerState->UniqueId.GetUniqueNetId();
	else
		DDH::Debug() << "No PlayerState Exists, Can't Get UserID" << DDH::Endl();
#endif

	// 如果在这里直接获取 Session 运行时会报错,生命周期的问题
}

void URPCInstance::HostSession()
{
	if (OnlineSub) {
		IOnlineSessionPtr Session = OnlineSub->GetSessionInterface();
		if (Session.IsValid()) {
			// 会话设置
			FOnlineSessionSettings Settings;
			// 连接数
			Settings.NumPublicConnections = 10;
			Settings.bShouldAdvertise = true;
			Settings.bAllowJoinInProgress = true;
			// 使用局域网
			Settings.bIsLANMatch = true;
			Settings.bUsesPresence = true;
			Settings.bAllowJoinViaPresence = true;
			// 绑定委托
			OnCreateSessionCompleteDelegateHandle = Session->AddOnCreateSessionCompleteDelegate_Handle(OnCreateSessionCompleteDelegate);
			// 创建会话
			Session->CreateSession(*UserID, NAME_GameSession, Settings);
		}
	}
}

void URPCInstance::ClientSession()
{
	if (OnlineSub) {
		IOnlineSessionPtr Session = OnlineSub->GetSessionInterface();
		if (Session.IsValid()) {
			// 实例化搜索结果指针并且设定参数
			SearchObject = MakeShareable(new FOnlineSessionSearch);
			// 返回结果数
			SearchObject->MaxSearchResults = 10;
			// 是否是局域网,就是 IsLAN
			SearchObject->bIsLanQuery = true;
			SearchObject->QuerySettings.Set(SEARCH_PRESENCE, true, EOnlineComparisonOp::Equals);
			// 绑定寻找会话委托
			OnFindSessionsCompleteDelegateHandle = Session->AddOnFindSessionsCompleteDelegate_Handle(OnFindSessionsCompleteDelegate);
			// 进行会话寻找
			Session->FindSessions(*UserID, SearchObject.ToSharedRef());
		}
	}
}

void URPCInstance::DestroySession()
{
	if (OnlineSub) {
		IOnlineSessionPtr Session = OnlineSub->GetSessionInterface();
		if (Session.IsValid()) {
			// 绑定销毁会话委托
			OnDestroySessionCompleteDelegateHandle = Session->AddOnDestroySessionCompleteDelegate_Handle(OnDestroySessionCompleteDelegate);
			// 执行销毁会话
			Session->DestroySession(NAME_GameSession);
		}
	}
}

void URPCInstance::OnCreateSessionComplete(FName SessionName, bool bWasSuccessful)
{
	if (OnlineSub) {
		IOnlineSessionPtr Session = OnlineSub->GetSessionInterface();
		if (Session.IsValid()) {
			// 解绑创建会话完成回调函数
			Session->ClearOnCreateSessionCompleteDelegate_Handle(OnCreateSessionCompleteDelegateHandle);
			// 判断创建会话是否成功
			if (bWasSuccessful) {
				DDH::Debug() << "CreateSession Succeed" << DDH::Endl();

				// 绑定开启会话委托
				OnStartSessionCompleteDelegateHandle = Session->AddOnStartSessionCompleteDelegate_Handle(OnStartSessionCompleteDelegate);
				// 执行开启会话
				Session->StartSession(NAME_GameSession);
			}
			else
				DDH::Debug() << "CreateSession Failed" << DDH::Endl();
		}
	}
}

void URPCInstance::OnStartOnlineGameComplete(FName SessionName, bool bWasSuccessful)
{
	if (OnlineSub) {
		IOnlineSessionPtr Session = OnlineSub->GetSessionInterface();
		if (Session.IsValid()) {
			// 注销开启会话委托绑定
			Session->ClearOnStartSessionCompleteDelegate_Handle(OnStartSessionCompleteDelegateHandle);
			if (bWasSuccessful) {
				DDH::Debug() << "StartSession Succeed" << DDH::Endl();
				// 服务端跳转场景
				UGameplayStatics::OpenLevel(PlayerController->GetWorld(), FName("GameMap"), true, FString("listen"));
			}
			else
				DDH::Debug() << "StartSession Failed" << DDH::Endl();
		}
	}
}

void URPCInstance::OnFindSessionsComplete(bool bWasSuccessful)
{
	if (OnlineSub) {
		IOnlineSessionPtr Session = OnlineSub->GetSessionInterface();
		if (Session.IsValid()) {
			// 取消寻找会话委托绑定
			Session->ClearOnStartSessionCompleteDelegate_Handle(OnStartSessionCompleteDelegateHandle);
			if (bWasSuccessful) {
				// 如果收集的结果存在并且大于 1
				if (SearchObject.IsValid() && SearchObject->SearchResults.Num() > 0) {
					DDH::Debug() << "Find Sessions Succeed" << DDH::Endl();
					// 绑定加入 Session 委托
					OnJoinSessionCompleteDelegateHandle = Session->AddOnJoinSessionCompleteDelegate_Handle(OnJoinSessionCompleteDelegate);
					// 执行加入 Session
					Session->JoinSession(*UserID, NAME_GameSession, SearchObject->SearchResults[0]);
				}
				else
					DDH::Debug() << "Find Sessions Succeed But Num == 0" << DDH::Endl();
			}
			else
				DDH::Debug() << "Find Sessions Failed" << DDH::Endl();
		}
	}
}

void URPCInstance::OnJoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result)
{
	if (OnlineSub) {
		IOnlineSessionPtr Session = OnlineSub->GetSessionInterface();
		if (Session.IsValid()) {
			// 取消加入会话委托绑定
			Session->ClearOnJoinSessionCompleteDelegate_Handle(OnJoinSessionCompleteDelegateHandle);
			// 如果加入成功
			if (Result == EOnJoinSessionCompleteResult::Success) {
				// 传送玩家到新地图
				FString ConnectString;
				if (Session->GetResolvedConnectString(NAME_GameSession, ConnectString)) {
					DDH::Debug() << "Join Sessions Succeed" << DDH::Endl();
					// 客户端切换到服务器的关卡
					PlayerController->ClientTravel(ConnectString, TRAVEL_Absolute);
				}
			}
			else 
				DDH::Debug() << "Join Sessions Failed" << DDH::Endl();
		}
	}
}

void URPCInstance::OnDestroySessionComplete(FName SessionName, bool bWAsSuccessful)
{
	if (OnlineSub) {
		IOnlineSessionPtr Session = OnlineSub->GetSessionInterface();
		if (Session.IsValid()) {
			// 注销销毁会话委托
			Session->ClearOnDestroySessionCompleteDelegate_Handle(OnDestroySessionCompleteDelegateHandle);
			// 其他逻辑...
		}
	}
}

读者可能会发现上面的代码中,绑定委托后直接就通过 Session 执行相应逻辑了,然后在接下来的逻辑里解绑委托。这里笔者倾向于将这一过程理解为 装弹 —> 发射 —> 退弹壳。UE4 已经将网络模块的细枝末节都为我们封装好了,我们只需要知道如何使用就够了,当然,喜欢探索的读者也可以查阅源码去深入理解。

在主界面控制器里注册自己到 RPCInstance,并且将 RPCInstance 注册到主界面 UI。

MenuController.cpp

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

void AMenuController::BeginPlay()
{


	// 获取 GameInstance
	URPCInstance* RPCInstance = Cast<URPCInstance>(UGameplayStatics::GetGameInstance(GetWorld()));
	RPCInstance->AssignPlayerController(this);
	
	UClass* MenuWidgetClass = LoadClass<UMenuWidget>(NULL, TEXT("WidgetBlueprint'/Game/Blueprint/MenuWidget_BP.MenuWidget_BP_C'"));
	UMenuWidget* MenuWidget = CreateWidget<UMenuWidget>(GetWorld(), MenuWidgetClass);
	MenuWidget->AddToViewport();
	MenuWidget->AssignRPCInstance(RPCInstance);	// 注册 RPCInstance 到 MenuWidget
}

最后添加一些网络模块相关的依赖。

RPCCourse.Build.cs

public RPCCourse(ReadOnlyTargetRules Target) : base(Target)
	{
		PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;

		PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "HeadMountedDisplay", "Slate", "UMG", 
		"HeadMountedDisplay", "OnlineSubsystem", "OnlineSubsystemUtils" });	// 添加这三个模块依赖

		// 添加动态加载模组
		DynamicallyLoadedModuleNames.AddRange(
			new string[] {
				"OnlineSubsystemNull",
			}
		);
	}

编译后,在项目设置里将默认的 GameInstance 设置为 RPCInstance。

来到 MenuWidget_BP 的图表,重新调整两个按钮点击事件的连接节点如下:

在这里插入图片描述
此时运行玩家数应该是 3。运行后,在服务端创建服务器,创建成功;让另外两个客户端加入服务器,也能进入成功。并且在服务端按 J 键,另外两个客户端各自能看到自己角色处生成红色数字。

不过如果在客户端创建服务器,另外一个客户端可以加入,但是服务端加入会显示找到会话和加入成功,但不会跳转到 GameMap。所以必须要让服务端创建服务器才能正常运作。

至此,梁迪老师的 RPC 课程到这里就结束了,衷心感谢梁迪老师提供的优质课程 : )

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

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

相关文章

edge安装fdm插件

下载 https://www.crxsoso.com/webstore/detail/ahmpjcflkgiildlgicmcieglgoilbfdp 安装 进入edge插件管理页面 edge://extensions/2. 将下载的crt文件拖到这个页面&#xff0c;就能自动安装了 在其他网页不能安装&#xff0c;会变成下载。

2024年noc比赛Coding创意编程赛项-创意实验室初赛模拟题

【单选题】 1.角色本来面向的方向是右方,执行下方积木后,角色面向的方向是() A.面向右上方 C.面向左上方 B.面向右下方 D.面向左下方 2.下列选项中关于图中按钮功能说法错误的是() A."本地传”按钮可以从本地电脑上传素材 B."重新画”按钮可以自己设计素材 C"…

QT的UI入门

二、UI入门 QWidget类&#xff08;熟悉&#xff09; QWidget类是所有组件和窗口的基类&#xff0c;内部包含了一些基础的界面特性。 常用属性&#xff1a; 修改坐标 x : const int 横坐标&#xff0c;每个图形的左上角为定位点&#xff0c;横轴的零点在屏幕的最左边&#xff0c…

Javase-方法的使用

文章目录 一 . 方法的初步认识二 . 方法的定义三 . 方法调用的执行过程四 . 实参与形参的关系五 . 方法的重载 一 . 方法的初步认识 方法其实就是一些代码片段,类似于c语言中的函数 方法存在的意义(理解): 是能够模块化的组织代码(当代码规模比较复杂的时候).做到代码被重复使…

一文搞懂match、match_phrase与match_phrase_prefix的检索过程

一、在开始之前&#xff0c;完成数据准备&#xff1a; # 创建映射 PUT /tehero_index {"settings": {"index": {"number_of_shards": 1,"number_of_replicas": 1}},"mappings": {"_doc": {"dynamic": …

[计算机网络]---TCP协议

前言 作者&#xff1a;小蜗牛向前冲 名言&#xff1a;我可以接受失败&#xff0c;但我不能接受放弃 如果觉的博主的文章还不错的话&#xff0c;还请点赞&#xff0c;收藏&#xff0c;关注&#x1f440;支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 目录 一 、TCP协…

如何在群辉7.2中使用Docker搭建容器魔方服务并远程访问【内网穿透】

文章目录 1. 拉取容器魔方镜像2. 运行容器魔方3. 本地访问容器魔方4. 群辉安装Cpolar5. 配置容器魔方远程地址6. 远程访问测试7. 固定公网地址 本文主要介绍如何在群辉7.2版本中使用Docker安装容器魔方&#xff0c;并结合Cpolar内网穿透工具实现远程访问本地网心云容器魔方界面…

深入了解Git

1.1 Git 的工作流程简介 克隆 Git 资源作为工作目录 在克隆的资源上添加或修改文件 如果其他人修改了&#xff0c;你可以更新资源 在提交前查看修改 提交修改 在修改完成后&#xff0c;如果发现错误&#xff0c;可以撤回提交并再次修改并提交 1.2 Git 工作区、暂存区和版…

2-21算法习题总结

由于蓝桥杯的题,我不知道从怎么复制,就只能粘贴图片了 翻硬币 代码 import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner sc new Scanner(System.in);String start sc.next();char[] starts start.toCharArray();String end sc…

nginx优化配置

一 全局配置的六个模块简介 全局块&#xff1a;全局配置&#xff0c;对全局生效 events块&#xff1a;配置影响 Nginx 服务器与用户的网络连接 http块&#xff1a;配置代理&#xff0c;缓存&#xff0c;日志定义等绝大多数功能和第三方模块的配置 server块&#xff1a;配置…

TypeScript01:安装TypeScript

一、TypeScript 官方网站&#xff1a;https://www.tslang.cn/docs/index.html 练习场&#xff1a;https://www.typescriptlang.org/zh/play 好处&#xff1a; 强类型语言&#xff0c;对JS弱类型的一个良好补充&#xff1b;TS利于大型项目团队合作&#xff0c;可以一定程度…

大工程 从0到1 数据治理 数仓篇(sample database classicmodels _No.7)

大工程 从0到1 数据治理 之数仓篇 我这里还是sample database classicmodels为案列&#xff0c;可以下载&#xff0c;我看 网上还没有类似的 案列&#xff0c;那就 从 0-1开始吧&#xff01; 提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生成可参…

WordPress后台自定义登录和管理页面插件Admin Customizer

WordPress默认的后台登录页面和管理员&#xff0c;很多站长都想去掉或修改一些自己不喜欢的功能&#xff0c;比如登录页和管理页的主题样式、后台左侧菜单栏的某些菜单、仪表盘的一些功能、后台页眉页脚某些小细节等等。这里boke112百科推荐这款可以让我们轻松自定义后台登录页…

2024-02-21 学习笔记(DETR)

自动多模态检测验证效果不佳&#xff08;过检太多&#xff09;后&#xff0c;节后开始尝试DETR路线。 基本梳理了下DETR发展和验证的脉络&#xff0c;先进行相应指定场景的效果验证。 关于DETR系列的介绍&#xff0c;B站上比较多&#xff0c;迪哥的都讲的比较细。 推荐大佬的…

【数据结构】_队列

目录 1.概念 2.队列的使用 3.队列模拟实现 4.循环队列 5.双端队列 6.栈与队列的互相实现 6.1 用队列实现栈 6.2 用栈实现队列 1.概念 &#xff08;1&#xff09;队列是只允许在一端进行插入数据操作&#xff0c;在另一端进行删除数据操作的特殊线性表&#xff1b; &am…

1Panel使用GMSSL+Openresty实现国密/RSA单向自适应

本文 首发于 Anyeの小站&#xff0c;转载请取得作者同意。 前言 国密算法是国家商用密码算法的简称。自2012年以来&#xff0c;国家密码管理局以《中华人民共和国密码行业标准》的方式&#xff0c;陆续公布了SM2/SM3/SM4等密码算法标准及其应用规范。其中“SM”代表“商密”&a…

C++日志库plog使用指南

前言 之前介绍过一个C语言日志库 轻量级c语言开源日志库log.c介绍&#xff0c;源代码只有不到200行&#xff0c;使用非常方便。但是也存在很多缺点&#xff0c;比如日志时间只支持打印到秒&#xff0c;没有作多线程处理&#xff0c;不支持日志回滚。在小型项目或者测试demo中使…

SpringBoot-helloworld

1. helloworld 使用springboot完成一个简单的web应用&#xff0c;当访问/hello时&#xff0c;返回"hello springboot"字符串。 首先&#xff0c;创建一个maven工程&#xff0c;并引入依赖。 <!--使用springboot编写web应用前&#xff0c;需要声明父工程spring-b…

Cent OS 7 无线网连接

环境&#xff1a;小型迷尔工控机 CentOS的镜像版本&#xff1a;CentOS-7-x86_64-DVD-2009.iso 下载地址&#xff1a;https://www.centos.org/download/ 安装CentOS的过程跳过。 成功安装后&#xff1a; 1、通过 ip addr 查看网卡的情况 2、通过nmcli dev status 查看网卡的…