UE4 UDP协议网络数据传输

一、UDP简介

       UDP 是User Datagram Protocol的简称, 中文名是用户数据报协议,是OSI(Open System Interconnection,开放式系统互联) 参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务。、

       UDP协议与TCP协议一样用于处理数据包,在OSI模型中,两者都位于传输层,处于IP协议的上一层。UDP有不提供数据包分组、组装和不能对数据包进行排序的缺点,也就是说,当报文发送之后,是无法得知其是否安全完整到达的。UDP用来支持那些需要在计算机之间传输数据的网络应用。包括网络视频会议系统在内的众多的客户/服务器模式的网络应用都需要使用UDP协议。UDP协议从问世至今已经被使用了很多年,虽然其最初的光彩已经被一些类似协议所掩盖,但即使在今天UDP仍然不失为一项非常实用和可行的网络传输层协议。

主要特点:

UDP是一个无连接协议,传输数据之前源端和终端不建立连接,当它想传送时就简单地去抓取来自应用程序的数据,并尽可能快地把它扔到网络上。在发送端,UDP传送数据的速度仅仅是受应用程序生成数据的速度、计算机的能力和传输带宽的限制;在接收端,UDP把每个消息段放在队列中,应用程序每次从队列中读一个消息段。

由于传输数据不建立连接,因此也就不需要维护连接状态,包括收发状态等,因此一台服务机可同时向多个客户机传输相同的消息。

UDP信息包的标题很短,只有8个字节,相对于TCP的20个字节信息包而言UDP的额外开销很小。

吞吐量不受拥挤控制算法的调节,只受应用软件生成数据的速率、传输带宽、源端和终端主机性能的限制。

UDP是面向报文的。发送方的UDP对应用程序交下来的报文,在添加首部后就向下交付给IP层。既不拆分,也不合并,而是保留这些报文的边界,因此,应用程序需要选择合适的报文大小。

虽然UDP是一个不可靠的协议,但它是分发信息的一个理想协议。例如,在屏幕上报告股票市场、显示航空信息等等。UDP也用在路由信息协议RIP(Routing Information Protocol)中修改路由表。在这些应用场合下,如果有一个消息丢失,在几秒之后另一个新的消息就会替换它。UDP广泛用在多媒体应用中。

二、UE4 案例

 前期准备:在项目工程的Build.cs中加入Networking,Sockets

using UnrealBuildTool;

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

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

第一步:C++创建Actor,名为UdpSender

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

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

public:
	bool IsUDP;

	UFUNCTION(BlueprintCallable, Category = "UDP")
		bool RamaUDPSender_SendString(FString ToSend);


};
// Fill out your copyright notice in the Description page of Project Settings.


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

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

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

}

第二步:创建Actor,名为UdpReceive

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

#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 "Runtime/Networking/Public/Common/UdpSocketReceiver.h"
#include "Networking/Public/Interfaces/IPv4/IPv4Address.h"
#include "UdpReceive.generated.h"


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


public:
	FSocket* ListenSocket;

	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);
};
// Fill out your copyright notice in the Description page of Project Settings.


#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;
	ListenSocket = NULL;
}

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

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

第三步:创建基于C++的蓝图,BP_MyUdpSender,BP_MyUdpReceive

 

第四步:复制一份项目工程

一份放入BP_MyUdpSender,打开BP_MyUdpSender蓝图

 

 一份放入BP_MyUdpReceive,打开蓝图

 

 第五步:测试运行

连接成功!!

 发送数据成功!!!

 到这里就成功了!!!

猜你喜欢

转载自blog.csdn.net/qq_43021038/article/details/126543122