Legacy/TCP Socket Listener, Receive Binary Data From an IP/Port Into UE4, (Full Code Sample)

Overview Author: () Dear Community, In this tutorial I am giving you the code I used to enable a python script to communicate with UE4 over a TCP socket! That's right! I am just straight up giving ...

Overview

Author: ()

Dear Community,

In this tutorial I am giving you the code I used to enable a python script to communicate with UE4 over a TCP socket!

That's right!

I am just straight up giving you my whole source code that I spent the past several hours writing!

ÔÖÑ

Rama

COMMUNITY CAVEAT [02/06/2017, UE4.14.3] From the readme found in the Networking module (/Source/Runtime/Networking/Readme.txt):

"This module is an internal R\&D effort to explore how the socket subsystem could be improved. Production use is NOT encouraged at this point."

For the reader of this tutorial this means the module will likely perpetually change as development on the UE4 is progressing. For example, since the initial writing of this wiki tutorial the FTcpListener class has been added which makes a lot of Rama's delightful work redundant. However, in the future this class may again be changed or removed entirely.

1000px

Build.CS

You will need at least this to work with sockets in UE4

PublicDependencyModuleNames.AddRange(new string[] { 
        "Core", 
        "CoreUObject", 
        "Engine", 
        "InputCore",

        "Sockets","Networking"
});

.h

#include "Networking.h"

//AYourClass definition

public:

FSocket* ListenerSocket;
FSocket* ConnectionSocket;
FIPv4Endpoint RemoteAddressForConnection;

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
void TCPSocketListener();       //can thread this eventually

//Format String IP4 to number array
bool FormatIP4ToNumber(const FString& TheIP, uint8 (&Out)[4]);

//Rama's StringFromBinaryArray
FString StringFromBinaryArray(const TArray& BinaryArray);

.cpp

The flow goes basically from the top down, you can probably guess the purpose of this first function.

void AYourClass::Laaaaaauuuunch()
{
                                        //IP = 127.0.0.1, Port = 8890 for my Python test case
    if( ! StartTCPReceiver("RamaSocketListener", "127.0.0.1", 8890))
    {
        //UE_LOG  "TCP Socket Listener Created!"
        return;
    }

    //UE_LOG  "TCP Socket Listener Created! Yay!"
}

//Rama's Start TCP Receiver
bool AYourClass::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
    GetWorldTimerManager().SetTimer(this, 
        &AYourClass::TCPConnectionListener, 0.01, true);    

    return true;
}
//Format IP String as Number Parts
bool AYourClass::FormatIP4ToNumber(const FString& TheIP, uint8 (&Out)[4])
{
    //IP Formatting
    TheIP.Replace( TEXT(" "), TEXT("") );

    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    //                         IP 4 Parts

    //String Parts
    TArray Parts;
    TheIP.ParseIntoArray( &Parts, TEXT("."), true );
    if ( Parts.Num() != 4 )
        return false;

    //String to Number Parts
    for ( int32 i = 0; i < 4; ++i )
    {
        Out[i] = FCString::Atoi( *Parts[i] );
    }

    return true;
}
//Rama's Create TCP Connection Listener
FSocket* AYourClass::CreateTCPConnectionListener(const FString&amp; YourChosenSocketName,const FString&amp; TheIP, const int32 ThePort,const int32 ReceiveBufferSize)
{
    uint8 IP4Nums[4];
    if( ! FormatIP4ToNumber(TheIP, IP4Nums))
    {
        VShow("Invalid IP! Expecting 4 parts separated by .");
        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 AYourClass::TCPConnectionListener()
{
    //~~~~~~~~~~~~~
    if(!ListenerSocket) return;
    //~~~~~~~~~~~~~

    //Remote address
    TSharedRef RemoteAddress = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();
    bool Pending;

    // handle incoming connections
    if (ListenerSocket->HasPendingConnection(Pending) &amp;&amp; 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);

            //UE_LOG "Accepted Connection! WOOOHOOOO!!!";

            //can thread this too
            GetWorldTimerManager().SetTimer(this, 
                &amp;AYourClass::TCPSocketListener, 0.01, true);    
        }
    }
}

//Rama's String From Binary Array
FString AYourClass::StringFromBinaryArray(TArray BinaryArray)
{
    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(BinaryArray.GetData()));
}

//Rama's TCP Socket Listener
void AYourClass::TCPSocketListener()
{
    //~~~~~~~~~~~~~
    if(!ConnectionSocket) return;
    //~~~~~~~~~~~~~

    //Binary Array!
    TArray ReceivedData;

    uint32 Size;
    while (ConnectionSocket->HasPendingData(Size))
    {
        ReceivedData.Init(FMath::Min(Size, 65507u));

        int32 Read = 0;
        ConnectionSocket->Recv(ReceivedData.GetData(), ReceivedData.Num(), Read);

        //GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, FString::Printf(TEXT("Data Read! %d"), ReceivedData.Num()));
    }
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    if(ReceivedData.Num() AddOnScreenDebugMessage(-1, 5.f, FColor::Red, FString::Printf(TEXT("Data Bytes Read ~> %d"), ReceivedData.Num()));

    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    //                      Rama's String From Binary Array
    const FString ReceivedUE4String = StringFromBinaryArray(ReceivedData);
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    VShow("As String!!!!! ~>",ReceivedUE4String);   
    GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, FString::Printf(TEXT("As String Data ~> %s"), *ReceivedUE4String));
}

How It Works

The first socket listens on the port and IP supplied, and if a connection is received on this port, then the actual ListenerSocket is created.

The ListenerSocket is run in very short looping timer of 0.01 seconds.

You could also use a thread for this :)

Rama's Tutorial on Multi-Threading, Create Threads in UE4

Whenever the ListenerSocket receives data, it makes sure to gather all of it using the while loop, and then I convert the results into a FString.

//Binary Array!
TArray ReceivedData;

uint32 Size;
while (ConnectionSocket->HasPendingData(Size))
{
    ReceivedData.Init(FMath::Min(Size, 65507u));

    int32 Read = 0;
    ConnectionSocket->Recv(ReceivedData.GetData(), ReceivedData.Num(), Read);

    //GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, FString::Printf(TEXT("Data Read! %d"), ReceivedData.Num()));
}

If you are not receiving string data you can skip that part and just pass out the binary array itself :)

Another TCP Source Code, by alfalfasprossen

Community Member alfalfasprossen has been developing a communication interface between UE4 and Maya since 2014 using TCP sockets. Check out his code and see if you can learn some more!

alfalfa's m2u Plugin

Another TCP Source, by Phi.WOP

Here is another implementation you can check out from a UE4 Community member!

UDP

I have written a wiki on communicating via UDP from one UE4 instance to another here! UDP_Socket_Sender_Receiver_From_One_UE4_Instance_To_Another

Conclusion

I am sharing my last several hours of research with you in fully functional C++ code format!

Enjoy!

()

Hoodini Hoodini Bot Last updated on