[UE4] TCP Socket 傳輸實現

程式碼
// 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())));
}
view raw TcpClient.cpp hosted with ❤ by GitHub
// 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;
};
view raw TcpClient.h hosted with ❤ by GitHub
// 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
PublicDependencyModuleNames.AddRange(new string[] {
"Core",
"Sockets", "Networking", // TCP socket required
});
view raw Build.cs hosted with ❤ by GitHub

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通信

這個網誌中的熱門文章

Windows10 版本1607後可啟用支援長路徑檔名 (Maximum Path Length Limitation)

標準使用者如何執行需系統管理者權限的程式