前言: 最近在学习UE4网络相关知识,尝试学习使用 UDP做一个分布式服务器(后面会出相关文章),这里简单记录一下如何使用 UE4的“Sockets”模块和“Networking”模块进行UDP广播。

操作步骤: 1、首先创建两个空的UE4C++工程(我这里使用的是UE426版本引擎),一个用来广播数据,一个用来接收数据,在两个工程分别创建继承自UObject的类,分别命名为“ UDPSend”和“ UDPRecive”,还需在".build.cs"文件中添加模块“Sockets", “Networking”,示例如下代码所示。 2、为了清晰可见,直接上代码(部分代码是网上粘贴的,我这里只是想记录的同时再复习一遍,也方便自己后续查阅)。 3、编译后启动引擎,将两个基于C++的蓝图类,分别拖入到场景中(用来广播的工程只用创建UDPSend,用来接收的工程只用创建“UDPRecive”)。拖入场景中后,在UDPSend中调用函数“StartUDPSender()”,传入网络套接字、 IP、端口号即可。UDP开启后,可以TICK执行广播数据(调用函数RamaUDPSender_SendString());接收数据的工程也是一样,将对应类拖入场景中,打开蓝图编辑器,先调用函数StartUDPReceiver(),再调用函数DataRecv()。两个工程要同时运行,这样就能广播和接收数据了。 using UnrealBuildTool;

public class UDPTest426 : ModuleRules { public UDPTest426(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160

PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "Sockets", "Networking" });

PrivateDependencyModuleNames.AddRange(new string[] {  });

// Uncomment if you are using Slate UI
// PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });

// Uncomment if you are using online features
// PrivateDependencyModuleNames.Add("OnlineSubsystem");

// To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
}
}

//UDPSend.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Runtime/Networking/Public/Networking.h"
//#include <iostream>
//#include <list>
//#include <numeric>
//#include <algorithm>
#include "UDPSend.generated.h"

UCLASS()
class UDPTEST426_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;

};

//UDPSend.cpp
#include "UDPSend.h"
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::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;
}

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

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
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;
}

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;
}

//UDPRecive.h
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Runtime/Networking/Public/Networking.h"
#include "UDPRecive.generated.h"

UCLASS()
class UDPTEST426_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;

};

//UDPRecive.cpp
// Fill out your copyright notice in the Description page of Project Settings.

#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; }

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
void AUDPRecive::DataRecv(FString& str, bool& success)
{
if (!ListenSocket)
{
ScreenMsg("No sender socket");
success = false;

}

TSharedRef<FInternetAddr> targetAddr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
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; //判断数据结束

1
2
3
4
5
6
7
8
9
FString debugData = ANSI_TO_TCHAR(ansiiData);         //字符串转换
str = debugData;
// memset(ansiiData,0,1024);//清空
}
else
{
success = false;
}
}