UE4——UDP广播消息
1、首先要创建两个C++类,我们就命名为Send和Recive。
下面是Send类的代码:
Send.h:
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Runtime/Networking/Public/Networking.h"
#include "UDPSend.generated.h"
UCLASS()
class GAME_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;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
//新建isudp的bool变量,表示用UDP进行通信
bool IsUDP;
//新建函数RamaUDPSender_SendString(),用于发送消息
UFUNCTION(BlueprintCallable, Category = "UDP")
bool RamaUDPSender_SendString(FString ToSend);
public:
TSharedPtr<FInternetAddr> RemoteAddr;
FSocket* SenderSocket;
// SocketName,IP,Port,IsUdp
UFUNCTION(BlueprintCallable, Category = "UDP")
bool StartUDPSender(const FString& YourChosenSocketName, const FString& TheIP, const int32 ThePort, bool UDP);
//此函数用于在虚幻C++内获取本地IP,转化为String类型
UFUNCTION(BlueprintPure, Category = "UDP")
FString GetIP();
UFUNCTION(BlueprintCallable, Category = "UDP")
bool SendGameTime(FString ToSend);
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UDP")
bool ShowOnScreenDebugMessages;
FORCEINLINE void ScreenMsg(const FString& Msg)
{
if (!ShowOnScreenDebugMessages) return;
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, *Msg);
}
FORCEINLINE void ScreenMsg(const FString& Msg, const float Value)
{
if (!ShowOnScreenDebugMessages) return;
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, FString::Printf(TEXT("%s %f"), *Msg, Value));
}
FORCEINLINE void ScreenMsg(const FString& Msg, const FString& Msg2)
{
if (!ShowOnScreenDebugMessages) return;
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, FString::Printf(TEXT("%s %s"), *Msg, *Msg2));
}
public:
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
};
Send.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;
ShowOnScreenDebugMessages = true;
}
// Called when the game starts or when spawned
void AUDPSend::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void AUDPSend::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
bool AUDPSend::RamaUDPSender_SendString(FString ToSend)
{
if (!SenderSocket)
{
ScreenMsg("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);
ScreenMsg(Str);
return false;
}
return true;
}
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->SetBroadcastAddress();
RemoteAddr->SetPort(ThePort);
if (!bIsValid)
{
return false;
}
SenderSocket = FUdpSocketBuilder(*YourChosenSocketName)
.AsReusable()//使绑定的地址可以被其他套接字重用
.WithBroadcast()
.WithSendBufferSize(2 * 1024 * 1024)
/*.BoundToEndpoint(Endpoint)*/
;
check(SenderSocket->GetSocketType() == SOCKTYPE_Datagram);
int32 SendSize = 2 * 1024 * 1024;
SenderSocket->SetSendBufferSize(SendSize, SendSize);
SenderSocket->SetReceiveBufferSize(SendSize, SendSize);
if (bIsValid)
{
bIsValid = true;
}
return bIsValid;
}
void AUDPSend::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
Super::EndPlay(EndPlayReason);
if (SenderSocket)
{
SenderSocket->Close();
ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(SenderSocket);
}
}
FString AUDPSend::GetIP()
{
bool canBind = false;
TSharedRef<FInternetAddr> localIP = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->GetLocalHostAddr(*GLog, canBind);
// 获得本机的IP
FString ipStr = (localIP->IsValid() ? localIP->ToString(false) : "");
return ipStr;
}
bool AUDPSend::SendGameTime( FString gametime)
{
if (!SenderSocket)
{
ScreenMsg("No sender socket");
return false;
}
int32 BytesSent = 0;
FString serialized = gametime;
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);
ScreenMsg(Str);
return false;
}
return true;
}
Send创建完毕后再UE4编辑器内蓝图继承后,
调用c++函数输入广播地址,广播地址为255.255.255.255,如果需要单播发送信息,输入指定IP即可,端口可以直接设置,前提是没有被占用,勾选UPD,用Tick发送信息。
接下来是Recive的代码
Recive.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Runtime/Networking/Public/Networking.h"
#include "UDPRecive.generated.h"
UCLASS()
class GAME_API AUDPRecive : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AUDPRecive();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
public:
FSocket* ListenSocket;
FUdpSocketReceiver* UDPReceiver = nullptr;
TSharedPtr<FInternetAddr> RemoteAddr;
UFUNCTION(BlueprintCallable, Category = "UDP")
void StartUDPReceiver(const FString& YourChosenSocketName, const FString& TheIP, const int32 ThePort, bool& success);
//接受消息
UFUNCTION(BlueprintPure, Category = "UDP")
void DataRecv(FString& str, bool& success);
FORCEINLINE void ScreenMsg(const FString& Msg)
{
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, *Msg);
}
FORCEINLINE void ScreenMsg(const FString& Msg, const float Value)
{
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, FString::Printf(TEXT("%s %f"), *Msg, Value));
}
FORCEINLINE void ScreenMsg(const FString& Msg, const FString& Msg2)
{
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, FString::Printf(TEXT("%s %s"), *Msg, *Msg2));
}
public:
/** Called whenever this actor is being removed from a level */
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
};
Recive.cpp
#include "UDPRecive.h"
// Sets default values
AUDPRecive::AUDPRecive()
{
// 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;
ListenSocket = NULL;
}
// Called when the game starts or when spawned
void AUDPRecive::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void AUDPRecive::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
//结束时出发事件
void AUDPRecive::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
Super::EndPlay(EndPlayReason);
//UDPReceiver置空
delete UDPReceiver;
UDPReceiver = nullptr;
//Clear all sockets!
if (ListenSocket)
{
ListenSocket->Close();
ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(ListenSocket);
}
}
//初始化Receiver
void AUDPRecive::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);
//使用指定的NetID和端口创建并初始化新的IPv4端点。
// Any: Defines the wild card endpoint, which is 0.0.0.0:0
FIPv4Endpoint Endpoint(FIPv4Address::Any, ThePort); //所有ip地址本地
//FUdpSocketBuilder: Implements a fluent builder for UDP sockets.
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)
{
success = true;
}
else {
ScreenMsg("No socket");
success = false;
}
//return true;
}
void AUDPRecive::DataRecv(FString & str, bool & success)
{
if (!ListenSocket)
{
ScreenMsg("No sender socket");
success = false;
}
TSharedRef<FInternetAddr> targetAddr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();
TArray<uint8> ReceivedData;//定义一个接收器
uint32 Size;
//ListenSocket->HasPendingData(Size) 查询套接字以确定队列中是否有挂起的数据,如果套接字有数据,则为true,否则为false Size参数指示单个recv调用的管道上有多少数据
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;
}
}
创建完成后一样的蓝图继承,
注意,端口设置要和发送端的端口一样,否则无法接受到消息,
设置完成后分别拖入场景中实例化,运行,就能收到消息了。
(这篇文章也是为了自己学习所用,如果发现侵权,还请告知)