getnamo / UDP-Unreal

Convenience UDP wrapper for the Unreal Engine.
MIT License
335 stars 79 forks source link

Receive IP #15

Closed Tivoyagefeak closed 3 years ago

Tivoyagefeak commented 3 years ago

Hey, First of all: thanks for this plugin. Its really easy to use, even for Unreal beginners (like I am) I wanted to share a litte enhancement I made, in case someone finds it helpful. For my use case, i need to get to know the IP adress from an receiving udp message. Im not sure if its the best way how i solved it because im brandnew to unreal in and their c++ usage but it works for now.

The UDPComponent.h:

#pragma once

#include "Components/ActorComponent.h"
#include "Sockets/Public/IPAddress.h"
#include "Common/UdpSocketBuilder.h"
#include "Common/UdpSocketReceiver.h"
#include "Common/UdpSocketSender.h"
#include "UDPComponent.generated.h"

USTRUCT(BlueprintType)
struct UDPWRAPPER_API FUDPSettings
{
    GENERATED_USTRUCT_BODY()

    /** Default sending socket IP string in form e.g. 127.0.0.1. */
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UDP Connection Properties")
    FString SendIP;

    /** Default connection port e.g. 3001*/
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UDP Connection Properties")
    int32 SendPort;

    /** Default connection port e.g. 3002*/
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UDP Connection Properties")
    int32 ReceivePort;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UDP Connection Properties")
    FString SendSocketName;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UDP Connection Properties")
    FString ReceiveSocketName;

    /** in bytes */
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UDP Connection Properties")
    int32 BufferSize;

    /** If true will auto-connect on begin play to IP/port specified for sending udp messages, plus when emit is called */
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UDP Connection Properties")
    bool bShouldAutoOpenSend;

    /** If true will auto-listen on begin play to port specified for receiving udp messages. */
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UDP Connection Properties")
    bool bShouldAutoOpenReceive;

    /** Whether we should process our data on the gamethread or the udp thread. */
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UDP Connection Properties")
    bool bReceiveDataOnGameThread;

    UPROPERTY(BlueprintReadOnly, Category = "UDP Connection Properties")
    bool bIsReceiveOpen;

    UPROPERTY(BlueprintReadOnly, Category = "UDP Connection Properties")
    bool bIsSendOpen;

    FUDPSettings();
};

class UDPWRAPPER_API FUDPNative
{
public:

    TFunction<void(const TArray<uint8>&, const FString&)> OnReceivedBytes;
    TFunction<void(int32 Port)> OnReceiveOpened;
    TFunction<void(int32 Port)> OnReceiveClosed;
    TFunction<void(int32 Port)> OnSendOpened;
    TFunction<void(int32 Port)> OnSendClosed;

    FUDPSettings Settings;

    FUDPNative();
    ~FUDPNative();

    //Send
    void OpenSendSocket(const FString& InIP = TEXT("127.0.0.1"), const int32 InPort = 3000);
    void CloseSendSocket();

    void EmitBytes(const TArray<uint8>& Bytes);

    //Receive
    void OpenReceiveSocket(const int32 InListenPort = 3002);
    void CloseReceiveSocket();

    //Callback convenience
    void ClearSendCallbacks();
    void ClearReceiveCallbacks();
protected:

    FSocket* SenderSocket;
    FSocket* ReceiverSocket;
    FUdpSocketReceiver* UDPReceiver;
    FString SocketDescription;
    TSharedPtr<FInternetAddr> RemoteAdress;
    ISocketSubsystem* SocketSubsystem;
};

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FUDPSocketStateSignature, int32, Port);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FUDPMessageSignature, const TArray<uint8>&, Bytes, const FString, IPAdress);

UCLASS(ClassGroup = "Networking", meta = (BlueprintSpawnableComponent))
class UDPWRAPPER_API UUDPComponent : public UActorComponent
{
    GENERATED_UCLASS_BODY()
public:

    //Async events

    /** On message received on the receiving socket. */
    UPROPERTY(BlueprintAssignable, Category = "UDP Events")
    FUDPMessageSignature OnReceivedBytes;

    /** Callback when we start listening on the udp receive socket*/
    UPROPERTY(BlueprintAssignable, Category = "UDP Events")
    FUDPSocketStateSignature OnReceiveSocketOpened;

    /** Called after receiving socket has been closed. */
    UPROPERTY(BlueprintAssignable, Category = "UDP Events")
    FUDPSocketStateSignature OnReceiveSocketClosed;

    /** The send pipeline is ready to use */
    UPROPERTY(BlueprintAssignable, Category = "UDP Events")
    FUDPSocketStateSignature OnSendSocketOpened;

    /** The send pipeline can't receive emit */
    UPROPERTY(BlueprintAssignable, Category = "UDP Events")
    FUDPSocketStateSignature OnSendSocketClosed;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UDP Connection Properties")
    FUDPSettings Settings;

    /**
    * Connect to a udp endpoint, optional method if auto-connect is set to true.
    * Emit function will then work as long the network is reachable. By default
    * it will attempt this setup for this socket on beginplay.
    *
    * @param InIP the ip4 you wish to connect to
    * @param InPort the udp port you wish to connect to
    */
    UFUNCTION(BlueprintCallable, Category = "UDP Functions")
    void OpenSendSocket(const FString& InIP = TEXT("127.0.0.1"), const int32 InPort = 3000);

    /**
    * Close the sending socket. This is usually automatically done on endplay.
    */
    UFUNCTION(BlueprintCallable, Category = "UDP Functions")
    void CloseSendSocket();

    /** 
    * Start listening at given port for udp messages. Will auto-listen on begin play by default
    */
    UFUNCTION(BlueprintCallable, Category = "UDP Functions")
    void OpenReceiveSocket(const int32 InListenPort = 3002);

    /**
    * Close the receiving socket. This is usually automatically done on endplay.
    */
    UFUNCTION(BlueprintCallable, Category = "UDP Functions")
    void CloseReceiveSocket();

    /**
    * Emit specified bytes to the udp channel.
    *
    * @param Message    Bytes
    */
    UFUNCTION(BlueprintCallable, Category = "UDP Functions")
    void EmitBytes(const TArray<uint8>& Bytes);

    virtual void InitializeComponent() override;
    virtual void UninitializeComponent() override;
    virtual void BeginPlay() override;
    virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;

protected:
    TSharedPtr<FUDPNative> Native;
    void LinkupCallbacks();
};

UDPComponent.cpp:

#include "UDPComponent.h"
#include "Async/Async.h"
#include "SocketSubsystem.h"
#include "Kismet/KismetSystemLibrary.h"

UUDPComponent::UUDPComponent(const FObjectInitializer &init) : UActorComponent(init)
{
    bWantsInitializeComponent = true;
    bAutoActivate = true;

    Native = MakeShareable(new FUDPNative);

    LinkupCallbacks();
}

void UUDPComponent::LinkupCallbacks()
{
    Native->OnSendOpened = [this](int32 Port)
    {
        OnSendSocketOpened.Broadcast(Port);
    };
    Native->OnSendClosed = [this](int32 Port)
    {
        OnSendSocketClosed.Broadcast(Port);
    };
    Native->OnReceiveOpened = [this](int32 Port)
    {
        OnReceiveSocketOpened.Broadcast(Port);
    };
    Native->OnReceiveClosed = [this](int32 Port)
    {
        OnReceiveSocketClosed.Broadcast(Port);
    };
    Native->OnReceivedBytes = [this](const TArray<uint8>& Data, const FString Endpoint)
    {
        OnReceivedBytes.Broadcast(Data, Endpoint);
    };
}

void UUDPComponent::CloseReceiveSocket()
{
    Native->CloseReceiveSocket();
}

void UUDPComponent::OpenSendSocket(const FString& InIP /*= TEXT("127.0.0.1")*/, const int32 InPort /*= 3000*/)
{
    Native->OpenSendSocket(InIP, InPort);
}

void UUDPComponent::CloseSendSocket()
{
    Native->CloseSendSocket();
}

void UUDPComponent::OpenReceiveSocket(const int32 InListenPort /*= 3002*/)
{
    Native->OpenReceiveSocket(InListenPort);
}

void UUDPComponent::EmitBytes(const TArray<uint8>& Bytes)
{
    Native->EmitBytes(Bytes);
}

void UUDPComponent::InitializeComponent()
{
    Super::InitializeComponent();
}

void UUDPComponent::UninitializeComponent()
{
    Super::UninitializeComponent();
}

void UUDPComponent::BeginPlay()
{
    Super::BeginPlay();

    //Sync settings
    Native->Settings = Settings;

    if (Settings.bShouldAutoOpenReceive)
    {
        Native->OpenReceiveSocket(Settings.ReceivePort);
    }
    if (Settings.bShouldAutoOpenSend)
    {
        Native->OpenSendSocket(Settings.SendIP, Settings.SendPort);
    }
}

void UUDPComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
    CloseSendSocket();
    CloseReceiveSocket();

    Native->ClearSendCallbacks();
    Native->ClearReceiveCallbacks();

    Super::EndPlay(EndPlayReason);
}

FUDPNative::FUDPNative()
{
    SenderSocket = nullptr;
    ReceiverSocket = nullptr;

    ClearReceiveCallbacks();
    ClearSendCallbacks();
}

FUDPNative::~FUDPNative()
{
    if (Settings.bIsReceiveOpen)
    {
        CloseReceiveSocket();
        ClearReceiveCallbacks();
    }
    if (Settings.bIsSendOpen)
    {
        CloseSendSocket();
        ClearSendCallbacks();
    }
}

void FUDPNative::OpenSendSocket(const FString& InIP /*= TEXT("127.0.0.1")*/, const int32 InPort /*= 3000*/)
{
    RemoteAdress = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();

    bool bIsValid;
    RemoteAdress->SetIp(*InIP, bIsValid);
    RemoteAdress->SetPort(InPort);

    if (!bIsValid)
    {
        UE_LOG(LogTemp, Error, TEXT("UDP address is invalid <%s:%d>"), *InIP, InPort);
        return;
    }

    SenderSocket = FUdpSocketBuilder(*Settings.SendSocketName).AsReusable().WithBroadcast();

    //check(SenderSocket->GetSocketType() == SOCKTYPE_Datagram);

    //Set Send Buffer Size
    SenderSocket->SetSendBufferSize(Settings.BufferSize, Settings.BufferSize);
    SenderSocket->SetReceiveBufferSize(Settings.BufferSize, Settings.BufferSize);

    bool bDidConnect = SenderSocket->Connect(*RemoteAdress);
    Settings.bIsSendOpen = true;

    if (OnSendOpened)
    {   
        OnSendOpened(Settings.SendPort);
    }
}

void FUDPNative::CloseSendSocket()
{
    if (SenderSocket)
    {
        SenderSocket->Close();
        ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(SenderSocket);
        SenderSocket = nullptr;

        if (OnSendClosed)
        {
            OnSendClosed(Settings.SendPort);
        }
    }

    Settings.bIsSendOpen = false;
}

void FUDPNative::EmitBytes(const TArray<uint8>& Bytes)
{
    if (SenderSocket->GetConnectionState() == SCS_Connected)
    {
        int32 BytesSent = 0;
        SenderSocket->Send(Bytes.GetData(), Bytes.Num(), BytesSent);
    }
    else if(Settings.bShouldAutoOpenSend)
    {
        OpenSendSocket(Settings.SendIP, Settings.SendPort);
        EmitBytes(Bytes);
    }
}

void FUDPNative::OpenReceiveSocket(const int32 InListenPort /*= 3002*/)
{
    if (Settings.bIsReceiveOpen)
    {
        CloseReceiveSocket();
    }

    //(const FArrayReaderPtr& DataPtr, const FIPv4Endpoint& Endpoint);

    FIPv4Address Addr;
    FIPv4Address::Parse(TEXT("0.0.0.0"), Addr);

    //Create Socket
    FIPv4Endpoint Endpoint(Addr, InListenPort);

    ReceiverSocket = FUdpSocketBuilder(*Settings.ReceiveSocketName)
        .AsNonBlocking()
        .AsReusable()
        .BoundToEndpoint(Endpoint)
        .WithReceiveBufferSize(Settings.BufferSize);

    FTimespan ThreadWaitTime = FTimespan::FromMilliseconds(100);
    FString ThreadName = FString::Printf(TEXT("UDP RECEIVER-FUDPNative"));
    UDPReceiver = new FUdpSocketReceiver(ReceiverSocket, ThreadWaitTime, *ThreadName);

    UDPReceiver->OnDataReceived().BindLambda([this](const FArrayReaderPtr& DataPtr, const FIPv4Endpoint& Endpoint)
    {
        if (!OnReceivedBytes)
        {
            return;
        }

        TArray<uint8> Data;
        Data.AddUninitialized(DataPtr->TotalSize());
        DataPtr->Serialize(Data.GetData(), DataPtr->TotalSize());

        if (Settings.bReceiveDataOnGameThread)
        {
            //Pass the reference to be used on gamethread
            AsyncTask(ENamedThreads::GameThread, [this, Data, Endpoint]()
            {
                //double check we're still bound on this thread
                if (OnReceivedBytes)
                {
                    OnReceivedBytes(Data, Endpoint.Address.ToString());
                }
            });
        }
        else
        {
            OnReceivedBytes(Data, Endpoint.Address.ToString());
        }
    });

    Settings.bIsReceiveOpen = true;

    if (OnReceiveOpened)
    {
        OnReceiveOpened(Settings.ReceivePort);
    }

    UDPReceiver->Start();
}

void FUDPNative::CloseReceiveSocket()
{
    if (ReceiverSocket)
    {
        UDPReceiver->Stop();
        delete UDPReceiver;
        UDPReceiver = nullptr;

        ReceiverSocket->Close();
        ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(ReceiverSocket);
        ReceiverSocket = nullptr;

        if (OnReceiveClosed)
        {
            OnReceiveClosed(Settings.ReceivePort);
        }
    }

    Settings.bIsReceiveOpen = false;
}

void FUDPNative::ClearSendCallbacks()
{

    OnSendOpened = nullptr;
    OnSendClosed = nullptr;
}

void FUDPNative::ClearReceiveCallbacks()
{
    OnReceivedBytes = nullptr;
    OnReceiveOpened = nullptr;
    OnReceiveClosed = nullptr;
}

FUDPSettings::FUDPSettings()
{
    bShouldAutoOpenSend = true;
    bShouldAutoOpenReceive = true;
    bReceiveDataOnGameThread = true;
    SendIP = FString(TEXT("127.0.0.1"));
    SendPort = 3001;
    ReceivePort = 3002;
    SendSocketName = FString(TEXT("ue4-dgram-send"));
    ReceiveSocketName = FString(TEXT("ue4-dgram-receive"));

    BufferSize = 2 * 1024 * 1024;   //default roughly 2mb
}

image

getnamo commented 3 years ago

That's looks like it could be a useful enhancement, nicely done!

If you can follow the guide on opening a pull request here: https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request, I could merge your change into the repository easily and it will record the contribution credit.

General gist of it is: fork this repository, add your code changes to your fork, commit them and make a pull request against this master branch and I can then merge it.

getnamo commented 3 years ago

Clean pull request, nice. Merged here: https://github.com/getnamo/udp-ue4/pull/16. Thanks making the enhancement :)