TCP Receiver and Sender
Dear Unreal Community,
First of all thank you very much Rama for your great work!
Cause we’ve had some trouble with the TCP Connection between Unreal Engine and MATLAB we’ve extended (Sending feature) and did some changes to the RAMA Code
to work with the actual Unreal Engine and want to share the code with you. We used it inside an Actor Class - smarter would be a plugin … maybe there’s some time in the future.
It’s written quick & dirty any recommendations are welcome!
- Do not forget to Add the Dependencies in YourClassName.build.cs => AND right-click on the C++ Project File => Right-click on the .uproject file. => Select “Generate Visual Studio project files”. Re-open the Project (Unreal & VisualStudio) after before trying to compile!
PublicDependencyModuleNames.AddRange(new string] { "Core", "CoreUObject", "Engine", "InputCore", "Sockets", "Networking" });
- Header-File
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "GameFramework/Actor.h"
#include "Networking.h"
#include "MyActorTCP.generated.h"
UCLASS()
class VECTION_API AMyActorTCP : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AMyActorTCP();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
FSocket* ListenerSocket;
FSocket* ConnectionSocket;
FIPv4Endpoint RemoteAddressForConnection;
FTimerHandle TCPSocketListenerTimerHandle;
FTimerHandle TCPConnectionListenerTimerHandle;
bool StartTCPReceiver(
const FString& YourChosenSocketName,
const FString& TheIP,
const int32 ThePort
);
FSocket* CreateTCPConnectionListener(
const FString& YourChosenSocketName,
const FString& TheIP,
const int32 ThePort,
const int32 ReceiveBufferSize = 2 * 1024 * 1024
);
//Timer functions, could be threads
void TCPConnectionListener(); //can thread this eventually
FString StringFromBinaryArray(TArray<uint8> BinaryArray);
void TCPSocketListener(); //can thread this eventually
//Format String IP4 to number array
bool FormatIP4ToNumber(const FString& TheIP, uint8(&Out)[4]);
UFUNCTION(BlueprintCallable, Category = "TCPServer")
bool LaunchTCP();
UFUNCTION(BlueprintCallable, Category = "TCPServer")
void TCPSend(FString message);
UFUNCTION(BlueprintImplementableEvent, Category = "TCPServer")
void recievedMessage(const FString &message);
};
- CPP File
#include "Vection.h"
#include <string>
#include "MyActorTCP.h"
// Sets default values
AMyActorTCP::AMyActorTCP()
{
// 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;
}
// Called when the game starts or when spawned
void AMyActorTCP::BeginPlay()
{
Super::BeginPlay();
}
void AMyActorTCP::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
Super::EndPlay(EndPlayReason);
UWorld* World = GetWorld();
GetWorld()->GetTimerManager().ClearTimer(TCPConnectionListenerTimerHandle);
GetWorld()->GetTimerManager().ClearTimer(TCPSocketListenerTimerHandle);
if (ConnectionSocket != NULL) {
ConnectionSocket->Close();
}
if (ListenerSocket != NULL) {
ListenerSocket->Close();
}
}
// Called every frame
void AMyActorTCP::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
// TCP Server Code
bool AMyActorTCP::LaunchTCP()
{
if (!StartTCPReceiver("RamaSocketListener", "127.0.0.1", 62))
{
return false;
}
return true;
}
//Rama's Start TCP Receiver
bool AMyActorTCP::StartTCPReceiver(
const FString& YourChosenSocketName,
const FString& TheIP,
const int32 ThePort
) {
//Rama's CreateTCPConnectionListener
ListenerSocket = CreateTCPConnectionListener(YourChosenSocketName, TheIP, ThePort);
//Not created?
if (!ListenerSocket)
{
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, FString::Printf(TEXT("StartTCPReceiver>> Listen socket could not be created! ~> %s %d"), *TheIP, ThePort));
return false;
}
//Start the Listener! //thread this eventually
UWorld* World = GetWorld();
World->GetTimerManager().SetTimer(TCPConnectionListenerTimerHandle, this, &AMyActorTCP::TCPConnectionListener, 1.0f, true);
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, FString::Printf(TEXT("StartTCPReceiver>> Listen socket created")));
return true;
}
//Format IP String as Number Parts
bool AMyActorTCP::FormatIP4ToNumber(const FString& TheIP, uint8(&Out)[4])
{
//IP Formatting
TheIP.Replace(TEXT(" "), TEXT(""));
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// IP 4 Parts
//String Parts
TArray<FString> Parts;
TheIP.ParseIntoArray(Parts, TEXT("."), true);
if (Parts.Num() != 4)
return false;
//String to Number Parts
for (int32 i = 0; i < 4; ++i)
{
Out* = FCString::Atoi(*Parts*);
}
return true;
}
//Rama's Create TCP Connection Listener
FSocket* AMyActorTCP::CreateTCPConnectionListener(const FString& YourChosenSocketName, const FString& TheIP, const int32 ThePort, const int32 ReceiveBufferSize)
{
uint8 IP4Nums[4];
if (!FormatIP4ToNumber(TheIP, IP4Nums))
{
return false;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//Create Socket
FIPv4Endpoint Endpoint(FIPv4Address(IP4Nums[0], IP4Nums[1], IP4Nums[2], IP4Nums[3]), ThePort);
FSocket* ListenSocket = FTcpSocketBuilder(*YourChosenSocketName)
.AsReusable()
.BoundToEndpoint(Endpoint)
.Listening(8);
//Set Buffer Size
int32 NewSize = 0;
ListenSocket->SetReceiveBufferSize(ReceiveBufferSize, NewSize);
//Done!
return ListenSocket;
}
//Rama's TCP Connection Listener
void AMyActorTCP::TCPConnectionListener()
{
//~~~~~~~~~~~~~
if (!ListenerSocket) return;
//~~~~~~~~~~~~~
//Remote address
TSharedRef<FInternetAddr> RemoteAddress = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();
bool Pending;
// handle incoming connections
ListenerSocket->HasPendingConnection(Pending);
if (Pending)
{
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//Already have a Connection? destroy previous
if (ConnectionSocket)
{
ConnectionSocket->Close();
ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(ConnectionSocket);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//New Connection receive!
ConnectionSocket = ListenerSocket->Accept(*RemoteAddress, TEXT("RamaTCP Received Socket Connection"));
if (ConnectionSocket != NULL)
{
//Global cache of current Remote Address
RemoteAddressForConnection = FIPv4Endpoint(RemoteAddress);
//can thread this too
UWorld* World = GetWorld();
World->GetTimerManager().SetTimer(TCPSocketListenerTimerHandle, this, &AMyActorTCP::TCPSocketListener, 0.1f, true);
}
}
}
//Rama's String From Binary Array
FString AMyActorTCP::StringFromBinaryArray(TArray<uint8> BinaryArray)
{
//Create a string from a byte array!
const std::string cstr(reinterpret_cast<const char*>(BinaryArray.GetData()), BinaryArray.Num());
return FString(cstr.c_str());
//BinaryArray.Add(0); // Add 0 termination. Even if the string is already 0-terminated, it doesn't change the results.
// Create a string from a byte array. The string is expected to be 0 terminated (i.e. a byte set to 0).
// Use UTF8_TO_TCHAR if needed.
// If you happen to know the data is UTF-16 (USC2) formatted, you do not need any conversion to begin with.
// Otherwise you might have to write your own conversion algorithm to convert between multilingual UTF-16 planes.
//return FString(ANSI_TO_TCHAR(reinterpret_cast<const char*>(BinaryArray.GetData())));
}
void AMyActorTCP::TCPSend(FString ToSend) {
ToSend = ToSend + LINE_TERMINATOR; //For Matlab we need a defined line break (fscanf function) "
" ist not working, therefore use the LINE_TERMINATOR macro form UE
TCHAR *SerializedChar = ToSend.GetCharArray().GetData();
int32 Size = FCString::Strlen(SerializedChar);
int32 Sent = 0;
uint8* ResultChars = (uint8*)TCHAR_TO_UTF8(SerializedChar);
if (!ConnectionSocket->Send(ResultChars, Size, sent)) {
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, FString::Printf(TEXT("Error sending message")));
}
}
//Rama's TCP Socket Listener
void AMyActorTCP::TCPSocketListener()
{
//~~~~~~~~~~~~~
if (!ConnectionSocket) return;
//~~~~~~~~~~~~~
//Binary Array!
TArray<uint8> ReceivedData;
uint32 Size;
while (ConnectionSocket->HasPendingData(Size))
{
ReceivedData.Init(FMath::Min(Size, 65507u), Size);
int32 Read = 0;
ConnectionSocket->Recv(ReceivedData.GetData(), ReceivedData.Num(), Read);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
if (ReceivedData.Num() <= 0)
{
return;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Rama's String From Binary Array
const FString ReceivedUE4String = StringFromBinaryArray(ReceivedData);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
recievedMessage(ReceivedUE4String);
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, FString::Printf(TEXT("As String Data ~> %s"), *ReceivedUE4String));
}