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…)