[UE4] TCP Socket 傳輸實現
程式碼
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Fill out your copyright notice in the Description page of Project Settings. | |
#include "TcpClient.h" | |
#include "UnrealNetwork.h" | |
// Sets default values | |
ATcpClient::ATcpClient() | |
{ | |
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. | |
PrimaryActorTick.bCanEverTick = true; | |
bReplicates = true; | |
Tcp_UpdateRate = 0.1f; | |
} | |
// Replicate | |
void ATcpClient::GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const | |
{ | |
Super::GetLifetimeReplicatedProps(OutLifetimeProps); | |
DOREPLIFETIME(ATcpClient, Tcp_UpdateRate); | |
} | |
// Called when the game starts or when spawned | |
void ATcpClient::BeginPlay() | |
{ | |
Super::BeginPlay(); | |
} | |
// Called every frame | |
void ATcpClient::Tick(float DeltaTime) | |
{ | |
Super::Tick(DeltaTime); | |
if (m_TcpRecvThread) | |
{ | |
m_TcpRecvThread->Tcp_UpdateRate = Tcp_UpdateRate; | |
} | |
} | |
void ATcpClient::EndPlay(const EEndPlayReason::Type EndPlayReason) | |
{ | |
ThreadEnd(); | |
if (Host) | |
{ | |
Host->Close(); | |
ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(Host); | |
} | |
Super::EndPlay(EndPlayReason); | |
} | |
bool ATcpClient::SocketCreate(FString IPStr, int32 port) | |
{ | |
FIPv4Address::Parse(IPStr, ip); //將傳入的IPStr轉為IPv4地址 | |
//創建一個addr存放ip地址和端口 | |
TSharedPtr<FInternetAddr> addr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr(); | |
addr->SetIp(ip.Value); | |
addr->SetPort(port); | |
//創建客户端socket | |
Host = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateSocket(NAME_Stream, TEXT("default"), false); | |
//連接成功 | |
if (Host->Connect(*addr)) | |
{ | |
UE_LOG(LogTemp, Warning, TEXT("TCP Connect Succeed!")); | |
return true; | |
} | |
//連接失敗 | |
else | |
{ | |
UE_LOG(LogTemp, Warning, TEXT("TCP Connect Failed!")); | |
return false; | |
} | |
} | |
bool ATcpClient::SocketSend(FString message) | |
{ | |
TCHAR *seriallizedChar = message.GetCharArray().GetData(); | |
int32 size = FCString::Strlen(seriallizedChar) + 1; | |
int32 sent = 0; | |
if (Host->Send((uint8*)TCHAR_TO_UTF8(seriallizedChar), size, sent)) | |
{ | |
UE_LOG(LogTemp, Warning, TEXT("___Send Succeed!")); | |
return true; | |
} | |
else | |
{ | |
UE_LOG(LogTemp, Warning, TEXT("___Send Failed!")); | |
return false; | |
} | |
} | |
bool ATcpClient::SocketReceive() | |
{ | |
if (Host->GetConnectionState() == ESocketConnectionState::SCS_Connected) | |
{ | |
// Create a counter | |
FThreadSafeCounter WorkerCounter; | |
// Increment the counter and create an unique name. | |
FString ThreadName(FString::Printf(TEXT("RecvThread%i"), WorkerCounter.Increment())); | |
m_TcpRecvThread = new FTcpReceiverThread(Host); | |
m_RecvThread = FRunnableThread::Create(m_TcpRecvThread, *ThreadName); | |
return true; | |
} | |
return false; | |
} | |
bool ATcpClient::ThreadEnd() | |
{ | |
if (m_RecvThread != nullptr) | |
{ | |
m_RecvThread->Kill(true); | |
delete m_RecvThread; | |
} | |
return true; | |
} | |
FString ATcpClient::StringFromBinaryArray(TArray<uint8> BinaryArray) | |
{ | |
return FString(ANSI_TO_TCHAR(reinterpret_cast<const char*>(BinaryArray.GetData()))); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Fill out your copyright notice in the Description page of Project Settings. | |
#pragma once | |
#include "Networking.h" | |
#include "CoreMinimal.h" | |
#include "TcpReceiverThread.h" | |
#include "GameFramework/Actor.h" | |
#include "TcpClient.generated.h" | |
UCLASS() | |
class UDPPLUGIN_API ATcpClient : public AActor | |
{ | |
GENERATED_BODY() | |
public: | |
// Sets default values for this actor's properties | |
ATcpClient(); | |
protected: | |
// Called when the game starts or when spawned | |
virtual void BeginPlay() override; | |
// Called when the game end | |
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; | |
public: | |
// Called every frame | |
virtual void Tick(float DeltaTime) override; | |
public: | |
UFUNCTION(BlueprintCallable, Category = "Tcp Client") | |
bool SocketCreate(FString IPStr, int32 port); | |
UFUNCTION(BlueprintCallable, Category = "Tcp Client") | |
bool SocketSend(FString message); | |
UFUNCTION(BlueprintCallable, Category = "Tcp Client") | |
virtual bool SocketReceive(); | |
public: | |
/** Update Rate (sec) */ | |
UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category = ShipParam, Meta = (DisplayName = "UpdateRate")) | |
float Tcp_UpdateRate; | |
public: | |
bool ThreadEnd(); | |
FString StringFromBinaryArray(TArray<uint8> BinaryArray); | |
FSocket *Host; | |
FIPv4Address ip; | |
FRunnableThread* m_RecvThread; | |
FTcpReceiverThread* m_TcpRecvThread; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Fill out your copyright notice in the Description page of Project Settings. | |
#pragma once | |
#include "ThreadingBase.h" | |
#include "Networking.h" | |
class UDPPLUGIN_API FTcpReceiverThread : public FRunnable | |
{ | |
public: | |
float Tcp_UpdateRate; | |
public: | |
FTcpReceiverThread(FSocket* client = NULL) : m_Client(client) | |
{} | |
~FTcpReceiverThread() | |
{ | |
stopping = true; | |
} | |
virtual bool Init() override | |
{ | |
stopping = false; | |
return true; | |
} | |
virtual uint32 Run() override | |
{ | |
if (!m_Client) | |
{ | |
return 0; | |
} | |
// Init | |
Tcp_UpdateRate = 0.1f; | |
TArray<uint8> ReceiveData; | |
uint32 size = 1024u; | |
//接收數據包 | |
while (!stopping) | |
{ | |
while (m_Client->HasPendingData(size)) | |
{ | |
ReceiveData.Init(0, FMath::Min(size, 65507u)); | |
int32 read = 0; | |
m_Client->Recv(ReceiveData.GetData(), ReceiveData.Num(), read); | |
UE_LOG(LogTemp, Warning, TEXT("Data Bytes Read : %s Byte."), *FString::FromInt(read)); | |
FString ReceivedUE4String = "Data : " + FString(ANSI_TO_TCHAR(reinterpret_cast<const char*>(ReceiveData.GetData()))); // byte to char | |
UE_LOG(LogTemp, Warning, TEXT("%s"), *ReceivedUE4String); | |
} | |
FPlatformProcess::Sleep(Tcp_UpdateRate); | |
} | |
return 1; | |
} | |
virtual void Stop() override | |
{ | |
stopping = true; | |
} | |
protected: | |
bool stopping; | |
FThreadSafeCounter m_StopTaskCounter; | |
private: | |
FSocket* m_Client; | |
}; |
需要注意的地方
記得在Build.cs添加Sockets, Networking
TcpClient.h
virtual bool SocketReceive();
//這裡添加virtual關鍵字是為了未來覆載此方法來實現從封包中拆解出自訂的資料格式
TcpClient.cpp
可在ATcpClient::ATcpClient() {...}建構函式中給予資料成員一個初始值,避免資料異常
若你想同步變數
ATcpClient::ATcpClient()
{
bReplicates = true; // 啟用同步
}
// Replicate
void ATcpClient::GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ATcpClient, Tcp_UpdateRate); // 你想同步的變數
}
在UE4停止運作時,確保有刪除Thread與關閉Socket
void ATcpClient::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
if (m_RecvThread != nullptr)
{
m_RecvThread->Kill(true);
delete m_RecvThread;
}
if (Socket)
{
Socket->Close();
ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(Socket);
}
Super::EndPlay(EndPlayReason);
}
Byte to String & Byte to float
// Byte to String
FString(ANSI_TO_TCHAR(reinterpret_cast<const char*>(ReceiveData.GetData())));
// Byte to float
TArray<uint8> binaryData;
binaryData.Add(ReceiveData.GetData()[(3]);
binaryData.Add(ReceiveData.GetData()[(2]);
binaryData.Add(ReceiveData.GetData()[(1]);
binaryData.Add(ReceiveData.GetData()[(0]);
decodeFloat = *reinterpret_cast<float*>(binaryData.GetData());
參考資料
UE4 Sockets多線程TCP通信
記得在Build.cs添加Sockets, Networking
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
PublicDependencyModuleNames.AddRange(new string[] { | |
"Core", | |
"Sockets", "Networking", // TCP socket required | |
}); |
TcpClient.h
virtual bool SocketReceive();
//這裡添加virtual關鍵字是為了未來覆載此方法來實現從封包中拆解出自訂的資料格式
TcpClient.cpp
可在ATcpClient::ATcpClient() {...}建構函式中給予資料成員一個初始值,避免資料異常
若你想同步變數
ATcpClient::ATcpClient()
{
bReplicates = true; // 啟用同步
}
// Replicate
void ATcpClient::GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ATcpClient, Tcp_UpdateRate); // 你想同步的變數
}
在UE4停止運作時,確保有刪除Thread與關閉Socket
void ATcpClient::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
if (m_RecvThread != nullptr)
{
m_RecvThread->Kill(true);
delete m_RecvThread;
}
if (Socket)
{
Socket->Close();
ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(Socket);
}
Super::EndPlay(EndPlayReason);
}
Byte to String & Byte to float
// Byte to String
FString(ANSI_TO_TCHAR(reinterpret_cast<const char*>(ReceiveData.GetData())));
// Byte to float
TArray<uint8> binaryData;
binaryData.Add(ReceiveData.GetData()[(3]);
binaryData.Add(ReceiveData.GetData()[(2]);
binaryData.Add(ReceiveData.GetData()[(1]);
binaryData.Add(ReceiveData.GetData()[(0]);
decodeFloat = *reinterpret_cast<float*>(binaryData.GetData());
參考資料
UE4 Sockets多線程TCP通信