UE4的联网系统研究

时间:2022-09-05 19:12:37

1. 物体复制

  具体细节可参考官网内容:http://api.unrealengine.com/CHN/Gameplay/Networking/index.html

  这里只挑部分点来展开。

  首先,分为服务端和客户端。

  然后,先看在c++中的两个参数:bNetLoadOnClient和SetReplicates(true), 对应蓝图的参数如下图所示:

  UE4的联网系统研究

  Replicate的意思为复制。

  假设现在要生成一个物体(如果执行者是服务端时),如果这个物体的Replicate为true,则服务端和客户端都会生成;如果这个物体的Replicate为false,则只会在服务端生成,客户端不会生成。

  但如果执行者是客户端时,则无论物体的Replicate是否为true,服务端都不会生成这个物体。

  PS:

  1. 判断执行者是否为服务端可通过 if (GetWorld()->IsServer())来进行。

  2. 判断执行者是否为服务端通过HasAuthority()有时候是不准确的,例如当一个物体是由客户端生成的,此物体的HasAuthority()就会返回true。

  Net Load on Client意思大概是在加载地图时,这个物体是否在客户端中加载出来。

  如果地图上放置了一个物体,且这个物体的Net Load on Client为false,则客户端不会加载这个物体;反之则会。

2. 变量复制

  所谓变量复制就是,服务端的变量进行修改时,客户端的变量也跟着修改。

  实现很简单,只需在变量上加UPROPERTY(Replicated);或者在蓝图中,勾选Replicated,如下图所示:

  UE4的联网系统研究

  如果想实现,服务端修改某个变量后,自动触发某个事件,则需要在此变量上添加特别的东西,如:

//注意,OnRep_XXX中的XXX是要监测的变量。
UPROPERTY(ReplicatedUsing = OnRep_Deactivate)
bool Deactivate; //一旦变量Deactivate在服务端中进行修改,则会触发这个函数
UFUNCTION()
void OnRep_Deactivate(); //在此例子中,变量Deactivate一旦在服务端被修改,客户端的OnRep_Deactivate()就会被调用,但服务端的这个函数不会被调用,需要特意去手动调用一下,如:
void AFireEffectActor::UpdateTimer()
{
//更新数字
if (CountDownTimer > ) CountDownTimer -= ;
else
{
//修改变量,且通知修改事件
Deactivate = !Deactivate;
//修改事件函数只会在客户端运行,而服务端的则需要特意调用一下(如这里)
OnRep_Deactivate();
}
}

3. 服务端与客户端的信息交流

  学习资料:http://api.unrealengine.com/CHN/Gameplay/Networking/Actors/RPCs/index.html

  信息交流有3种方法:

  a. NetMulticast: 服务端广播,所有客户端能收到;客户端广播,只有该客户端能收到。

  b. Client:服务端发出通知,拥有这个人物的客户端都会调用此方法;客户端调用,则只有该客户端能调用。

  c. Server:客户端传递信息给服务端的方法;如果服务端调用则服务端能收到。

  以下将逐一讨论:

a. NetMulticast

.h:
UFUNCTION(NetMulticast, Reliable)
void SpaceBarNetMulticast(); .cpp:
void ARPCCourseCharacter::SpaceBarNetMulticast_Implementation()
{
//获取蓝图
UClass* FireEffectClass = LoadClass<AActor>
(NULL, TEXT("Blueprint'/Game/BP/UnReplicateFire.UnReplicateFire_C'"));
//在玩家那生成物体
GetWorld()->SpawnActor<AActor>(FireEffectClass, GetActorTransform());
}

  注意:

  1. Reliable是可靠的意思,意味着服务端发出的信息,客户端绝对能收到。

  2. 方法的实现要加后缀_Implementation。

b.Client

.h:
//Client联网方法,服务端发出通知,拥有这个人物的客户端都会调用此方法。
UFUNCTION(Client, Reliable)
void KeyJClient(int32 InInt); .cpp:
void ARPCCourseCharacter::KeyJEvent()
{
if (GetWorld()->IsServer())
{
//获取所有ARPCCourseCharacter
TArray<AActor*> ActArray;
UGameplayStatics::GetAllActorsOfClass(
GetWorld(), ARPCCourseCharacter::StaticClass(), ActArray);
//呼叫所有ARPCCourseCharacter(除了自己)
for (int i = ; i < ActArray.Num(); ++i)
{
if (ActArray[i] != this)
{
Cast<ARPCCourseCharacter>(ActArray[i])->KeyJClient(i);
}
}
}
}
void ARPCCourseCharacter::KeyJClient_Implementation(int32 InInt)
{
ANumPad* NumPad = GetWorld()->SpawnActor<ANumPad>(ANumPad::StaticClass(),
GetActorTransform());
NumPad->AssignRenderText(FString::FromInt(InInt));
}

  注意,方法的实现要加后缀_Implementation。

c.Server

.h:
//H键绑定
void KeyHEvent(); //Server方法
UFUNCTION(Server, Reliable, WithValidation)
void KeyHServer(int32 InInt); //Serve方法逻辑
void KeyHServer_Implementation(int32 InInt); //Serve方法数据验证(如果验证后的结果为true,KeyHServer可以正常运行;如果为false,则发出此信息的客户端被踢出房间,此客户端重新开了一局单机游戏)
bool KeyHServer_Validate(int32 InInt); .cpp:
void ARPCCourseCharacter::KeyHEvent()
{
//客户端执行
if (!GetWorld()->IsServer()) KeyHServer();
} void ARPCCourseCharacter::KeyHServer_Implementation(int32 InInt)
{
//生成数字
ANumPad* NumPad = GetWorld()->SpawnActor<ANumPad>(ANumPad::StaticClass(),
GetActorTransform());
NumPad->AssignRenderText(FString::FromInt(InInt));
} bool ARPCCourseCharacter::KeyHServer_Validate(int32 InInt)
{
if (InInt > ) return true;
return false;
}

  注意:

  1. 方法的实现要加后缀_Implementation。

  2. 为预防玩家作弊,客户端传过来的信息要先进行验证,通过了才能被服务端接收。方法的验证要加后缀_Validate。

4.创建会话、登入与登出

  UE4的创建会话(Create Session),相当于创建房间。然后等客户端寻找房间并加入即可。如:

  UE4的联网系统研究

  UE4的联网系统研究

  GameMode只存在于服务端,不存在于客户端,因此登入与登出的行为在GameMode上做比较好。

.h:
UCLASS(minimalapi)
class ARPCCourseGameMode : public AGameModeBase
{
GENERATED_BODY() public:
ARPCCourseGameMode(); //GameMode只存在于服务端! //用户登入
virtual void PostLogin(APlayerController* NewPlayer) override;
//用户登出
virtual void Logout(AController* Exiting) override; protected: //计算有多少个人加入了游戏
int32 PlayerCount;
}; .cpp:
ARPCCourseGameMode::ARPCCourseGameMode()
{
PlayerControllerClass = ARPCController::StaticClass(); //如果不给WorldSetting指定GameMode,游戏运行时会自动把创建项目时生成的项目名GameMode这个类给设置上去
//如果创建的GameMode不指定PawnClass的话,会自动设定为ADefaultPawn类,所以这里必须设置为NULL
DefaultPawnClass = NULL;
PlayerCount = ;
} void ARPCCourseGameMode::PostLogin(APlayerController* NewPlayer)
{
Super::PostLogin(NewPlayer); //如果这个控制器自带了一个Pawn,则摧毁它
if (NewPlayer->GetPawn())
{
GetWorld()->DestroyActor(NewPlayer->GetPawn());
} TArray<AActor*> ActArray;
UGameplayStatics::GetAllActorsOfClass(GetWorld(),
APlayerStart::StaticClass(), ActArray);
if (ActArray.Num() > )
{
//人数+1
PlayerCount++;
//读取角色蓝图
UClass* CharacterClass = LoadClass<ARPCCourseCharacter>
(NULL, TEXT("Blueprint'/Game/ThirdPersonCPP/Blueprints/ThirdPersonCharacter.ThirdPersonCharacter_C'"));
//生成角色,位置是PlayerStart或者它的右边
ARPCCourseCharacter* NewCharacter = GetWorld()->SpawnActor<ARPCCourseCharacter>
(CharacterClass, ActArray[]->GetActorLocation()
+ FVector(.f, PlayerCount*.f, .f), ActArray[]->GetActorRotation());
//把玩家交给他对应的控制器
NewPlayer->Possess(NewCharacter); DDH::Debug() << NewPlayer->GetName() << "Login" << DDH::Endl();
}
} void ARPCCourseGameMode::Logout(AController* Exiting)
{
Super::Logout(Exiting); PlayerCount--; DDH::Debug() << Exiting->GetName() << "Logout" << DDH::Endl();
}

5.特殊的联机方法

  项目打包后,直接打开exe文件,此时游戏视为单机游戏(Standalone)。

  如果在exe的快捷方式后缀加上" ?listen"。则此时游戏视为监听模式(NM_ListenServer),如:

  UE4的联网系统研究

  如果在exe的快捷方式后缀加上"  127.0.0.1 -game",并且处于监听模式的游戏存在时(即已经打开了上面的RPCCourseServer),则此时游戏视为客户端,自动加入该游戏(NM_Client)。(重复打开,则重复添加客户端)如:

  UE4的联网系统研究

  如果直接打开原exe文件,按"~"调出控制面板后,输入“open 127.0.0.1”。则会加入已处于监听模式的游戏中,此时,此游戏成为客户端。

  UE4的联网系统研究

  如果在已处于监听模式的游戏中,按"~"调出控制面板后,输入“open 127.0.0.1”,则会关闭联网模式,所有游戏变为单机游戏。

  

6. 用C++创建、加入或摧毁会话

  首先要在build.cs中添加组件:

  UE4的联网系统研究

  由于GameInstance在游戏中一直存在,故创建会话等操作都在GameInstance中进行:

.h:
#include "CoreMinimal.h"
#include "Engine/GameInstance.h" #include "../Plugins/Online/OnlineSubsystem/Source/Public/Interfaces/OnlineSessionInterface.h"
#include "IDelegateInstance.h" #include "RPCInstance.generated.h" class IOnlineSubsystem;
class APlayerController; /**
*
*/
UCLASS()
class RPCCOURSE_API URPCInstance : public UGameInstance
{
GENERATED_BODY() public: URPCInstance(); //指定玩家控制器
void AssignPlayerController(APlayerController* InController); //创建会话
void HostSession(); //加入会话
void ClientSession(); //摧毁会话
void DestroySession(); protected: //当创建会话结束后,调用这个函数
void OnCreateSessionComplete(FName SessionName, bool bWasSuccessful);
//当开始会话结束后,调用这个函数
void OnStartSessionComplete(FName SessionName, bool bWasSuccessful); //加入服务器(会话Session)回调函数
void OnFindSessionComplete(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; TSharedPtr<const FUniqueNetId> UserID; //保存寻找到的Sessions
TSharedPtr<FOnlineSessionSearch> SearchObject;
}; .cpp: #include "Public/RPCInstance.h"
#include "GameFramework/PlayerController.h"
#include "../Plugins/Online/OnlineSubsystem/Source/Public/Online.h"
#include "../Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Public/OnlineSubsystemUtils.h"
#include "Public/RPCHelper.h"
#include "Kismet/GameplayStatics.h" URPCInstance::URPCInstance()
{
//绑定回调函数
OnCreateSessionCompleteDelegate = FOnCreateSessionCompleteDelegate::
CreateUObject(this, &URPCInstance::OnCreateSessionComplete);
OnStartSessionCompleteDelegate = FOnStartSessionCompleteDelegate::
CreateUObject(this, &URPCInstance::OnStartSessionComplete);
OnFindSessionsCompleteDelegate = FOnFindSessionsCompleteDelegate::
CreateUObject(this, &URPCInstance::OnFindSessionComplete);
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() == )
DDH::Debug() << "No LocalPlayer Exist, Can't Get UserID" << DDH::Endl();
else
UserID = (*GetLocalPlayers()[]->GetPreferredUniqueNetId()).AsShared();
//用宏定义,使编译器不对下面这段代码编译
#if 0
//获取方式二:使用PlayerState获取,打包后运行没问题,但在编辑器多窗口模式下,PlayerState不存在
if (PlayerController->PlayerState)
UserID = PlayerController->PlayerState->UniqueId.GetUniqueNetId();
else
DDH::Debug() << "No PlayerState Exist, Can't Get UserID" << DDH::Endl();
#endif //在这里直接获取Session运行时会报错,生命周期的问题 } void URPCInstance::HostSession()
{
if (OnlineSub)
{
IOnlineSessionPtr Session = OnlineSub->GetSessionInterface();
if (Session.IsValid())
{
//会话设置
FOnlineSessionSettings Settings;
//连接数
Settings.NumPublicConnections = ;
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 = ;
//是否是局域网,就是IsLAN
SearchObject->bIsLanQuery = true;
SearchObject->QuerySettings.Set(SEARCH_PRESENCE, true,
EOnlineComparisonOp::Equals);
//绑定寻找会话委托
OnFindSessionsCompleteDelegateHandle = Session->
AddOnFindSessionsCompleteDelegate_Handle
(OnFindSessionsCompleteDelegate);
//进行会话寻找
Session->FindSessions(*UserID, SearchObject.ToSharedRef());
}
}
} void URPCInstance::OnCreateSessionComplete(FName SessionName, bool bWasSuccessful)
{
if (OnlineSub)
{
IOnlineSessionPtr Session = OnlineSub->GetSessionInterface();
if (Session.IsValid())
{
//解绑创建会话完成回调函数
Session->
ClearOnCreateSessionCompleteDelegate_Handle
(OnCreateSessionCompleteDelegateHandle);
//判断创建会话是否成功
if (bWasSuccessful)
{
DDH::Debug() << "CreatSession Succeed" << DDH::Endl(); //绑定开启会话委托
OnStartSessionCompleteDelegateHandle = Session->
AddOnStartSessionCompleteDelegate_Handle
(OnStartSessionCompleteDelegate);
Session->StartSession(NAME_GameSession);
}
else
DDH::Debug() << "CreateSession Failed" << DDH::Endl();
}
}
} void URPCInstance::OnStartSessionComplete(FName SessionName, bool bWasSuccessful)
{
DDH::Debug() << "StartSession Start" << DDH::Endl();
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::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::OnFindSessionComplete(bool bWasSuccessful)
{
if (OnlineSub)
{
IOnlineSessionPtr Session = OnlineSub->GetSessionInterface();
if (Session.IsValid())
{
//取消寻找会话委托绑定
Session->ClearOnStartSessionCompleteDelegate_Handle
(OnStartSessionCompleteDelegateHandle);
//如果寻找会话成功
if (bWasSuccessful)
{
//如果收集的结果存在且大于1
if (SearchObject.IsValid() && SearchObject->SearchResults.Num() > )
{
DDH::Debug() << "Find Sessions Succeed" << DDH::Endl();
//绑定加入Session委托
OnJoinSessionCompleteDelegateHandle = Session
->AddOnJoinSessionCompleteDelegate_Handle
(OnJoinSessionCompleteDelegate);
//执行加入会话
Session->JoinSession(*UserID, NAME_GameSession, SearchObject->SearchResults[]);
}
else
DDH::Debug() << "Find Sessions Succeed But Num = 0" << DDH::Endl();
}
else
DDH::Debug() << "Find Sessions Failed" << DDH::Endl();
}
}
} void URPCInstance::OnDestroySessionComplete(FName SessionName, bool bWasSuccessful)
{
if (OnlineSub)
{
IOnlineSessionPtr Session = OnlineSub->GetSessionInterface();
if (Session.IsValid())
{
//注销销毁会话委托
Session->ClearOnDestroySessionCompleteDelegate_Handle
(OnDestroySessionCompleteDelegateHandle);
//其它逻辑。。。
}
}
} void URPCInstance::DestroySession()
{
if (OnlineSub)
{
IOnlineSessionPtr Session = OnlineSub->GetSessionInterface();
if (Session.IsValid())
{
//绑定销毁会话委托
OnDestroySessionCompleteDelegateHandle = Session->
AddOnDestroySessionCompleteDelegate_Handle
(OnDestroySessionCompleteDelegate);
//执行销毁会话
Session->DestroySession(NAME_GameSession);
}
}
}

7.注意事项:

  必须满足一些要求才能充分发挥 RPC 的作用:

  1. 它们必须从 Actor 上调用。

  2. Actor 必须被复制。

  3. 如果 RPC 是从服务器调用并在客户端上执行,则只有实际拥有这个 Actor 的客户端才会执行函数。

  4. 如果 RPC 是从客户端调用并在服务器上执行,客户端就必须拥有调用 RPC 的 Actor。

  5. 多播 RPC 则是个例外:

    • 如果它们是从服务器调用,服务器将在本地和所有已连接的客户端上执行它们。

    • 如果它们是从客户端调用,则只在本地而非服务器上执行。

    • 现在,我们有了一个简单的多播事件限制机制:在特定 Actor 的网络更新期内,多播函数将不会复制两次以上。按长期计划,我们会对此进行改善,同时更好的支持跨通道流量管理与限制

  6. 关于可靠性(Reliable)

  UE4的联网系统研究

8.讨论:

1.在一个可复制的(Replicated)且一开始就在地图里的物体中,调用广播:

 UE4的联网系统研究

结果:客户端和服务器都打印了。

  UE4的联网系统研究

2. 服务器生成一个可复制的物体,客户端会存在这个物体吗?

实验1.在服务器生成一个物体,物体设置为可复制的(Replicated),然后把这个物体广播出去。(在关卡蓝图中)

 UE4的联网系统研究

结果:只有服务器存在石头,客户端不存在。

  UE4的联网系统研究

3. 服务器生成一个可复制的物体,且调用此物体的多播函数:

  UE4的联网系统研究

  UE4的联网系统研究

结果:客户端和服务器都打印了。

  UE4的联网系统研究

UE4的联网系统研究的更多相关文章

  1. UE4联网游戏中让不同的客户端生成不同的Pawn类型

    效果描述 一个服务器,两个客户端,让他们连接后分别生成不同的Pawn,并且在不同的位置生成. 意义 这是个项目需求,但是我发现如果能够彻底理解并制作出这个功能,会对虚幻4内置的网络功能以及一些重要的G ...

  2. UE4联网测试的快捷方法

    工程中测试 创建bat文件,格式如下: UE4Editor.exe路径 工程文件名 [地图名及参数] -game [其他自定义参数] UE4Editor.exe路径表示虚幻编辑器相应版本的UE4Edi ...

  3. win10环境下ue4使用游戏手柄输入

    忙里偷闲,趁着源码编译需要好久的时间,把这篇博客补上,来说说怎么在win10环境中,ue4使用游戏手柄输入,也就是gamepad输入. 1.我用的手柄是rapoo v10 这款手柄,连接无线USB之后 ...

  4. UE4游戏开发基础命令

    在个人的Unrealengine账户中关联自己的GitHub账户成功之后,就可以访问UE4引擎的源码了. git clone -b release https://github.com/EpicGam ...

  5. UE4程序及资源加密保护方案

    UnrealEngine4外壳加密 . Virbox Protector 解决代码反汇编和反dump代码,解决软件盗版与算法抄袭. 虚幻引擎4是由游戏开发者为开发游戏而制作的.完整的游戏开发工具套件. ...

  6. UE4 C&plus;&plus; Tips

    篇写的是关于UE4的C++方面的小技巧: 1.在构造函数里 //构建组件 RootComponent = CreateDefaultSubobject<USceneComponent>(T ...

  7. &lbrack;UE4&rsqb;创建游戏、加入游戏

    google搜: UE4 compile dedicated server,编译UE4专用服务器 UE4默认网络端口可以在引擎配置文件中修改: 一.创建文件.需要修改一下工程的配置文件DefaultE ...

  8. 【UE4】二十三、UE4笔试面试题

    在CSDN博客看到的,带着这些问题,多多留意,正所谓带着问题学习. 一. 1.Actor的EndPlay事件在哪些时候会调用? 2.BlueprintImplementableEvent和Bluepr ...

  9. Aery的UE4 C&plus;&plus;游戏开发之旅(3)蓝图

    目录 蓝图 蓝图命名规范 蓝图优化 暴露C++至蓝图 暴露C++类 暴露C++属性 暴露C++函数 暴露C++结构体/枚举 暴露C++接口 蓝图和C++的结合方案 使用继承重写蓝图 使用组合重写蓝图 ...

随机推荐

  1. css 绘制对话框三角符号

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  2. iOS FMDB的使用

    简介: SQLite (http://www.sqlite.org/docs.html) 是一个轻量级的关系数据库.iOS SDK 很早就支持了 SQLite,在使用时,只需要加入 libsqlite ...

  3. 凡聊过必留下痕迹-破解加密的WeChat数据库

    有个朋友上门寻求协助,带着她朋友的朋友的手机,说是手机硬件有问题,想把手机内的资料都备份出来,尤其是WeChat的聊天内容…我跟她说,那iTool等工具不就可以帮上忙了吗?没想到她早就试过了, 说iT ...

  4. bootstrap知识小点

    年底没什么项目做了,整理下最近做的网站使用到的bootstrap知识 一.导入bootstrap样式和脚本 <link href="css/bootstrap.min.css&quot ...

  5. 【Mood-4】心静是一门艺术

    到现在工作还没有着落,心里面反而比以前平静,以前也知道自己的水平 属于一瓶不满,半瓶咣当的那种情况,但是那时候的自己总是觉得自己的综合水平可能会弥补一下自己在技术上的缺失,但是,现在看来,太过于自信, ...

  6. win10桌面和手机的扩展API,判断是否有实体后退键API

    喜大普奔的win10 uap开发预览版终于出了,这次更新跟8.1的变化不是很大,但是将原本win8.1和wp8.1uap的分项目的形式,改为了整合成一个项目,经过一次编译打包成一个appx包,实现了无 ...

  7. Convert ResultSet to JSON and XML

    public static JSONArray convertToJSON(ResultSet resultSet) throws Exception { JSONArray jsonArray = ...

  8. mysql盲注学习-1

    mysql: 1.left() //left()函数 left(a,b)从左侧截取a,的b位 2.mid() //mid()函数 参数 描述 column_name 必需.要提取字符的字段. star ...

  9. mysql事务(一)——redo log与undo log

    数据事务 即支持ACID四大特性. A:atomicity          原子性——事务中所有操作要么全部执行成功,要么全部执行失败,回滚到初始状态 C:consistency     一致性—— ...

  10. Django之Form功能

    一 什么是Form?什么是DjangoForm? Django表单系统中,所有的表单类都作为django.forms.Form的子类创建,包括ModelForm 关于django的表单系统,主要分两种 ...