Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Receive IP #15

Closed
Tivoyagefeak opened this issue Feb 26, 2021 · 2 comments
Closed

Receive IP #15

Tivoyagefeak opened this issue Feb 26, 2021 · 2 comments
Labels
enhancement New feature or request

Comments

@Tivoyagefeak
Copy link
Contributor

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 getnamo added the enhancement New feature or request label Feb 26, 2021
@getnamo
Copy link
Owner

getnamo commented Feb 26, 2021

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
Copy link
Owner

getnamo commented Mar 2, 2021

Clean pull request, nice. Merged here: #16. Thanks making the enhancement :)

@getnamo getnamo closed this as completed Mar 2, 2021
getnamo added a commit that referenced this issue Mar 10, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants