UE4C++UDP通信

UE4C++UDP通信

首先创建继承自Actor类的C++类,本例中为UdpSend,UdpReceive
具体代码如下:
首先要在项目的build.cs文件中添加模块:
添加Sockets,Networking模块
在这里插入图片描述
在这里插入图片描述

UdpSend.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Runtime/Sockets/Public/Sockets.h"
#include "Sockets/Public/SocketSubsystem.h"
#include "Runtime/Networking/Public/Common/UdpSocketBuilder.h"
#include "UdpSend.generated.h"

UCLASS()
class GPSPC_API AUdpSend : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AUdpSend();

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* SenderSocket;
	//远程的地址
	TSharedPtr<FInternetAddr> RemoteAddr;
	UFUNCTION(BlueprintCallable, Category = "UDP")
		bool StartUDPSender(const FString& YourChosenSocketName, const FString& TheIP, const int32 ThePort, bool UDP);
	bool IsUDP;

	UFUNCTION(BlueprintCallable, Category = "UDP")
		bool RamaUDPSender_SendString(FString ToSend);
	UFUNCTION(BlueprintCallable, Category = "UDP")
		bool RamaUDPSender_SendDate(TArray<uint8>data);
};

UdpSend.cpp

#include "UdpSend.h"
// Sets default values
AUdpSend::AUdpSend()
{
 	// 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;
	SenderSocket = NULL;
}

// Called when the game starts or when spawned
void AUdpSend::BeginPlay()
{
	Super::BeginPlay();
	
}

// Called every frame
void AUdpSend::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

void AUdpSend::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
	Super::EndPlay(EndPlayReason);
	if (SenderSocket)
	{
		SenderSocket->Close();
		ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(SenderSocket);
	}
}



bool AUdpSend::StartUDPSender(const FString& YourChosenSocketName, const FString& TheIP, const int32 ThePort, bool UDP)
{
	RemoteAddr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();
	bool bIsValid;
	RemoteAddr->SetIp(*TheIP, bIsValid);
	RemoteAddr->SetPort(ThePort);
	if (!bIsValid)
	{
		UE_LOG(LogTemp, Warning, TEXT("Rama UDP Sender>> IP address was not valid! "), *TheIP);
		return false;
	}

	SenderSocket = FUdpSocketBuilder(*YourChosenSocketName)
		.AsReusable()
		.WithBroadcast() // 广播
		.WithSendBufferSize(2 * 1024 * 1024);

	int32 SendSize = 2 * 1024 * 1024;
	SenderSocket->SetSendBufferSize(SendSize, SendSize);
	SenderSocket->SetReceiveBufferSize(SendSize, SendSize);
	if (bIsValid)
	{
		bIsValid = true;
	}
	return bIsValid;

}

bool AUdpSend::RamaUDPSender_SendString(FString ToSend)//发送消息
{
	if (!SenderSocket)
	{
		UE_LOG(LogTemp, Warning, TEXT("No sender socket"));
		return false;
	}
	//消息处理
	int32 BytesSent = 0;
	FString serialized = ToSend;
	TCHAR* serializedChar = serialized.GetCharArray().GetData();
	int32 size = FCString::Strlen(serializedChar);
	int32 sent = 0;
	SenderSocket->SendTo((uint8*)TCHAR_TO_UTF8(serializedChar), size, BytesSent, *RemoteAddr);
	if (BytesSent < 0)
	{
		const FString Str = "Socket is valid but the receiver received 0 bytes, make sure it is listening properly!";
		UE_LOG(LogTemp, Error, TEXT("%s"), *Str);
		return false;
	}
	UE_LOG(LogTemp, Warning, TEXT("UDP Send Succcess! INFO Sent = %s "), *ToSend);

	return true;

}

bool AUdpSend::RamaUDPSender_SendDate(TArray<uint8> data)
{
	if (!SenderSocket)
	{
		UE_LOG(LogTemp, Warning, TEXT("No sender socket"));
		return false;
	}
	//消息处理
	int32 BytesSent = 0;
	int32 size = data.Num();
	int32 sent = 0;
	SenderSocket->SendTo(&data[0], size, BytesSent, *RemoteAddr);
	if (BytesSent < 0)
	{
		const FString Str = "Socket is valid but the receiver received 0 bytes, make sure it is listening properly!";
		UE_LOG(LogTemp, Error, TEXT("%s"), *Str);
		return false;
	}
	//UE_LOG(LogTemp, Warning, TEXT("UDP Send Succcess! INFO Sent = %s "), *ToSend);
	return true;
}

其中RamaUDPSender_SendString(FString ToSend);函数用来发送字符串,RamaUDPSender_SendDate用于发送字节数据

UdpReceive.h

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Runtime/Sockets/Public/Sockets.h"
#include "Sockets/Public/SocketSubsystem.h"
#include"Runtime/Networking/Public/Common/UdpSocketReceiver.h"
#include "Runtime/Networking/Public/Common/UdpSocketBuilder.h"
#include "Networking/Public/Interfaces/IPv4/IPv4Address.h"
#include "UdpReceive.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnUdpByteMessageDelegate, const TArray<uint8>&, Message, const FString&, SenderIp);
UCLASS()
class GPSPC_API AUdpReceive : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AUdpReceive();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;
	virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
	FSocket* ListenSocket;
	FUdpSocketReceiver* Receiver = nullptr;
	//FUdpSocketReceiver* UDPReceiver = nullptr;
public:
	UFUNCTION(BlueprintCallable, Category = "UDP")
		void StartUDPReceiver(const FString& YourChosenSocketName, const FString& TheIP, const int32 ThePort, bool& success);

	UFUNCTION(BlueprintCallable, Category = "UDP")
		void DataRecv(FString& str, bool& success);
	UFUNCTION(BlueprintCallable, Category = "UDP")
		TArray<uint8> DataRecvBytes(bool& success);
	UPROPERTY(BlueprintAssignable, Category = "Socket|Event")
		FOnUdpByteMessageDelegate OnByteMessage;
};

UdpReceive.cpp


#include "UdpReceive.h"
// Sets default values
AUdpReceive::AUdpReceive()
{
 	// 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 AUdpReceive::BeginPlay()
{
	Super::BeginPlay();
	
}

// Called every frame
void AUdpReceive::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}
void AUdpReceive::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
	Super::EndPlay(EndPlayReason);
	//delete UDPReceiver;
	//UDPReceiver = nullptr;

	//Clear all sockets!
	//      makes sure repeat plays in Editor dont hold on to old sockets!
	if (this->Receiver != nullptr)
	{
		this->Receiver->Stop();
		delete this->Receiver;
		this->Receiver = nullptr;
	}
	if (ListenSocket)
	{
		ListenSocket->Close();
		ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(ListenSocket);
	}

}

void AUdpReceive::StartUDPReceiver(const FString& YourChosenSocketName, const FString& TheIP, const int32 ThePort, bool& success)
{
	TSharedRef<FInternetAddr> targetAddr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();
	FIPv4Address Addr;
	FIPv4Address::Parse(TheIP, Addr);
	FIPv4Endpoint Endpoint(FIPv4Address::Any, ThePort);  //所有ip地址本地
														 //FIPv4Endpoint Endpoint(Addr, ThePort);                 //指定ip地址
	ListenSocket = FUdpSocketBuilder(*YourChosenSocketName)
		.AsNonBlocking()
		.AsReusable()
		.BoundToEndpoint(Endpoint)
		.WithReceiveBufferSize(2 * 1024 * 1024);
	//BUFFER SIZE
	int32 BufferSize = 2 * 1024 * 1024;
	ListenSocket->SetSendBufferSize(BufferSize, BufferSize);
	ListenSocket->SetReceiveBufferSize(BufferSize, BufferSize);

	if (!ListenSocket)
	{
		UE_LOG(LogTemp, Warning, TEXT("No Scokets"));
		success = false;

	}
	if (ListenSocket)
	{
		UE_LOG(LogTemp, Warning, TEXT("The receiver is initialized"));
		success = true;
		this->Receiver = new FUdpSocketReceiver(ListenSocket, FTimespan::FromMilliseconds(100), TEXT("DEFAULT"));
		this->Receiver->OnDataReceived().BindLambda([this](const FArrayReaderPtr& Data, const FIPv4Endpoint& From)
			{
				TArray<uint8> ReceiveBytes;
				ReceiveBytes.Append(Data->GetData(), Data->Num());
				FMemory::Memcpy(ReceiveBytes.GetData(), Data->GetData(), Data->Num() * sizeof(uint8));
				if (this->OnByteMessage.IsBound())
				{
					this->OnByteMessage.Broadcast(ReceiveBytes, From.ToString());
				}

			});
		//this->Receiver->OnDataReceived().BindUFunction(this, "DataRecvBytes2" );
		this->Receiver->Start();
	}
	
}

void AUdpReceive::DataRecv(FString& str, bool& success)
{
	if (!ListenSocket)
	{
		UE_LOG(LogTemp, Warning, TEXT("No Send Sockets"));
		success = false;
		//return success;
	}
	TSharedRef<FInternetAddr> targetAddr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();
	TArray<uint8> ReceivedData;//定义一个接收器
	uint32 Size;
	if (ListenSocket->HasPendingData(Size))
	{
		success = true;
		str = "";
		uint8* Recv = new uint8[Size];
		int32 BytesRead = 0;

		ReceivedData.SetNumUninitialized(FMath::Min(Size, 65507u));
		ListenSocket->RecvFrom(ReceivedData.GetData(), ReceivedData.Num(), BytesRead, *targetAddr);//创建远程接收地址
		char ansiiData[1024];
		memcpy(ansiiData, ReceivedData.GetData(), BytesRead);//拷贝数据到接收器
		ansiiData[BytesRead] = 0;                            //判断数据结束
		FString debugData = ANSI_TO_TCHAR(ansiiData);         //字符串转换
		str = debugData;
		// memset(ansiiData,0,1024);//清空 

	}
	else
	{
		success = false;
	}
}

TArray<uint8> AUdpReceive::DataRecvBytes(bool& success)
{
	if (!ListenSocket)
	{
		UE_LOG(LogTemp, Warning, TEXT("No Send Sockets"));
		success = false;
		//return success;
	}
	TSharedRef<FInternetAddr> targetAddr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();
	TArray<uint8> ReceivedData;//定义一个接收器
	uint32 Size;
	if (ListenSocket->HasPendingData(Size))
	{
		success = true;
		
		uint8* Recv = new uint8[Size];
		int32 BytesRead = 0;

		ReceivedData.SetNumUninitialized(FMath::Min(Size, 65507u));
		ListenSocket->RecvFrom(ReceivedData.GetData(), ReceivedData.Num(), BytesRead, *targetAddr);//创建远程接收地址
		// memset(ansiiData,0,1024);//清空 
		ReceivedData.SetNum(BytesRead);
		return ReceivedData;
	}
	else
	{
		success = false;
	}
	return TArray<uint8>();
}

关于Udp的接收,可以使用线程与非线程的方式。在代码中主要就是是否使用FUdpSocketReceiver* Receiver 这个变量。可以看到在StartUDPReceiver函数中this->Receiver->Start();该语句的作用就是开辟一个线程用于监听Udp数据,具体实现已经由UE4底层封装好,不必深究。如果不打算使用线程,则直接使用DataRecv或者DataRecvBytes函数。
代码编译完后,生成对应的蓝图子类。
在这里插入图片描述
对于MyUdpSend:
在这里插入图片描述
开始运行的时候用StartUDPSender初始化,接着调用sendData传输数据。
对于MyUdpReceiver:
在这里插入图片描述
如果使用线程,则参照上图,调用start函数,然后创建事件绑定,当接收到数据时便会触发事件。
如果不适用线程,则将代码中的StartUDPReceiver函数内

this->Receiver = new FUdpSocketReceiver(ListenSocket, FTimespan::FromMilliseconds(100), TEXT("DEFAULT"));
		this->Receiver->OnDataReceived().BindLambda([this](const FArrayReaderPtr& Data, const FIPv4Endpoint& From)
			{
				TArray<uint8> ReceiveBytes;
				ReceiveBytes.Append(Data->GetData(), Data->Num());
				FMemory::Memcpy(ReceiveBytes.GetData(), Data->GetData(), Data->Num() * sizeof(uint8));
				if (this->OnByteMessage.IsBound())
				{
					this->OnByteMessage.Broadcast(ReceiveBytes, From.ToString());
				}

			});
		//this->Receiver->OnDataReceived().BindUFunction(this, "DataRecvBytes2" );
		this->Receiver->Start();

这部分注释掉,然后蓝图中如下:
在这里插入图片描述
区别在于,由于不是线程,所以datarecv函数是单次触发,即调用一次,就检查有没有接收到数据,因此需要放到tick事件中。

注:

如果采用线程进行数据接收的监听,则需注意,线程中不能调用删除actor,object的相关事件,因为对于线程而言,多线程通信进行修改操作会导致线程奔溃的。这部分的相关内容可以搜索UE4多线程相关的知识。
之前找过一些问答可以参考一下:
在这里插入图片描述
也参考了另一个博主的博客:
自己创建的线程哪些不能做?官方文档上的回答:

https://wiki.unrealengine.com/Multi-Threading:_How_to_Create_Threads_in_UE4#What_Not_to_Do

Do not try to modify, create, or delete UObjects from other threads!
You can prepare all the data / do all the calculations, but only the game thread should be actually spawning / modifying / deleting UObjects / AActors.

Dont try to use TimerManager outside of the game thread 😃
Don’t try to draw debug lines/points etc, as it will likely crash, ie DrawDebugLine(etc…)

  • 4
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是一个简单的UE4 C++ UDP通信组播的案例。 1. 首先,需要创建一个UDP Socket: ```c++ FSocket* ListenSocket = FUdpSocketBuilder(TEXT("MySocket")).BoundToAddress(FIPv4Address::Any).BoundToPort(3000); ``` 2. 接下来,需要创建一个Multicast组: ```c++ TSharedPtr<FInternetAddr> MulticastAddr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr(); bool bIsValid; MulticastAddr->SetIp(TEXT("239.255.0.1"), bIsValid); MulticastAddr->SetPort(3000); ``` 3. 然后,将Socket加入到Multicast组: ```c++ ListenSocket->JoinMulticastGroup(*MulticastAddr); ``` 4. 最后,可以使用以下代码来发送和接收消息: ```c++ // 发送消息 FString Message = TEXT("Hello, World!"); TArray<uint8> SendBuffer; FTCHARToUTF8 Converter(*Message); SendBuffer.Append((uint8*)Converter.Get(), Converter.Length()); int32 BytesSent; ListenSocket->SendTo(SendBuffer.GetData(), SendBuffer.Num(), BytesSent, *MulticastAddr); // 接收消息 TArray<uint8> ReceiveBuffer; FIPv4Endpoint Endpoint; if (ListenSocket->HasPendingData(Size)) { ReceiveBuffer.SetNumUninitialized(FMath::Min(Size, 65507u)); int32 BytesRead; ListenSocket->RecvFrom(ReceiveBuffer.GetData(), ReceiveBuffer.Num(), BytesRead, Endpoint); if (BytesRead > 0) { FString Message = FString(UTF8_TO_TCHAR(reinterpret_cast<const char*>(ReceiveBuffer.GetData()))); UE_LOG(LogTemp, Warning, TEXT("Received message: %s"), *Message); } } ``` 这是一个简单的UDP组播通信案例,可以根据自己的实际需求进行修改和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值