UE进阶实例38(UE Socket网络编程)

本文档介绍了如何使用UE Socket进行网络编程,特别适用于小型和创业型公司的网络服务器,适用于VR和游戏场景。提供了纯绿色、高效能的代码,并针对客户端断开连接时服务器无法检测的问题提出解决方案,建议使用自定义Ping判定。此外,还分享了UPD Server和Client的参考资源及演示视频和源码链接。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


如果有遇到断开客户端,服务器无法检测的问题
Socket->GetConnectionState()判定有问题,可以使用自己的Ping来判定
https://forums.unrealengine.com/t/fsocket-getconnectionstate-always-returns-true/345349/3

UPD Server&Client参考
https://github.com/is-centre/udp-ue4-plugin-win64


#. 使用自定义参数加入Server

void AJoinPlayerController::JoinWithParams(int p1, FString& p2)
{
	const FString URLWithParams = FString::Printf(TEXT("127.0.0.1:7777?Param1=%d?Param2=%s"), p1, *p2);
	ClientTravel(URLWithParams, ETravelType::TRAVEL_Absolute);
}

FString AJoinServerParamsGameModeBase::InitNewPlayer(APlayerController* NewPlayerController, 
													 const FUniqueNetIdRepl& UniqueId, 
													 const FString& Options, 
													 const FString& Portal)
{
	GLog->Logf(TEXT("-------------Options:%s"), *Options);
	TArray<FString> Params;
	Options.ParseIntoArray(Params, TEXT("?"), true);

	int Count = 0;
	for (FString Param : Params)
	{
		FString Key, Value;
		Param.Split(TEXT("="), &Key, &Value);
		GLog->Logf(TEXT("-------------Param%d>>>[%s]:[%s]"), Count, *Key, *Value);
		Count++;
	}
	
	return Super::InitNewPlayer(NewPlayerController, UniqueId, Options, Portal);
}


#. UE4.27+新加入FUdpSocketReceiver

	int32 BytesSent = 0;

	FArrayWriter Writer;
	Writer << data;
	SenderSocket->SendTo(Writer.GetData(), Writer.Num(), BytesSent, *RemoteAddr);

	if (BytesSent <= 0)
	{
		UE_LOG(LogTemp, Error, TEXT("Socket exists, but receiver did not accept any packets."));
		return false;
	}
	Receiver = new FUdpSocketReceiver(ListenSocket, ThreadWaitTime, TEXT("UDP Receiver"));
	Receiver->OnDataReceived().BindUObject(this, &AUDPReceiver::Recv);
	Receiver->Start();
	
	FUDPData data;
	*ArrayReaderPtr << data;

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

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Networking.h"
#include "IPAddress.h"
#include "TCPServer.generated.h"

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FTCPEventSignature, int, Port);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FTCPMessageSignature, const TArray<uint8>&, Bytes);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FTCPClientSignture, const FString&, Client);

struct FTCPClient
{
	FSocket* Socket;
	FString Address;

	bool operator == (const FTCPClient& Other)
	{
		return Address == Other.Address;
	}
};

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

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;

	FTCPMessageSignature OnReceivedBytes;
	UPROPERTY(BlueprintAssignable, BlueprintReadWrite)
	FTCPEventSignature OnListenBegin;
	FTCPEventSignature OnListenEnd;

	FTCPClientSignture OnClientConnected;
	FTCPClientSignture OnClientDisconnected;

	int32 ListenPort;
	FString ListenSocketName;
	int32 BufferMaxSize;

	bool bShouldAutoListen;
	bool bReceiveDataOnGameThread;
	bool bDisconnectOnFailedEmit;
	bool bShouldPing;
	float PingInterval;
	FString PingMessage;
	bool bIsConnected;
	
	void StartListenServer(const FString Ipv4, const int32 InListenPort = 3001);
	void StopListenServer();
	UFUNCTION(BlueprintCallable)
	bool Emit(const TArray<uint8>& Bytes, const FString& ToClient = TEXT("All"));
	void DisconnectClient(FString ClientAddress = TEXT("All"), bool bDisconnectNextTick = false);

private:
	TMap<FString, TSharedPtr<FTCPClient>> Clients;
	FSocket* ListenSocket;
	FThreadSafeBool bShouldListen;
	TFuture<void> ServerFinishedFuture;
	TArray<uint8> PingData;

	FString SocketDescription;
	TSharedPtr<FInternetAddr> RemoteAddress;
	
};

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


#include "TCPServer.h"

#include "UESocket.h"

// Sets default values
ATCPServer::ATCPServer()
{
 	// 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;

	bShouldAutoListen = false;
	bReceiveDataOnGameThread = true;
	ListenPort = 3001;
	ListenSocketName = TEXT("ue4-tcp-server");
	bDisconnectOnFailedEmit = true;
	bShouldPing = false;
	PingInterval = 10.0f;
	PingMessage = TEXT("<Ping>");

	BufferMaxSize = 2 * 1024 * 1024;
}

// Called when the game starts or when spawned
void ATCPServer::BeginPlay()
{
	Super::BeginPlay();
	PingData.Append((uint8*)TCHAR_TO_UTF8(*PingMessage), PingMessage.Len());

	if (bShouldAutoListen)
	{
		StartListenServer(TEXT("0.0.0.0"), ListenPort);
	}
}

void ATCPServer::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
	Super::EndPlay(EndPlayReason);
	StopListenServer();
	
}

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

}

void ATCPServer::StartListenServer(const FString Ipv4, const int32 InListenPort)
{
	FIPv4Address Address;
	FIPv4Address::Parse(TEXT("0.0.0.0"), Address);

	FIPv4Endpoint Endpoint(Address, InListenPort);
	ListenSocket = FTcpSocketBuilder(*ListenSocketName)
			.AsReusable()
			.BoundToEndpoint(Endpoint)
			.WithReceiveBufferSize(BufferMaxSize);

	ListenSocket->SetReceiveBufferSize(BufferMaxSize, BufferMaxSize);
	ListenSocket->SetSendBufferSize(BufferMaxSize, BufferMaxSize);

	ListenSocket->Listen(8);

	OnListenBegin.Broadcast(InListenPort);
	bShouldListen = true;

	ServerFinishedFuture = ThreadUtility::RunLambdaOnBackgroundThread([&]()
	{
		uint32 BufferSize = 0;
		TArray<uint8> ReceiveBuffer;
		TArray<TSharedPtr<FTCPClient>> ClientDisconnected;

		FDateTime LastPing = FDateTime::Now();
		while (bShouldListen)
		{
			bool bHasPendingConnection;
			ListenSocket->HasPendingConnection(bHasPendingConnection);
			if (bHasPendingConnection)
			{
				TSharedPtr<FInternetAddr> Addr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();
				FSocket* Client = ListenSocket->Accept(*Addr, TEXT("tcp-client"));

				const FString AddressString = Addr->ToString(true);

				TSharedPtr<FTCPClient> ClientItem = MakeShareable(new FTCPClient());
				ClientItem->Address = AddressString;
				ClientItem->Socket = Client;

				Clients.Add(AddressString, ClientItem);

				AsyncTask(ENamedThreads::GameThread, [&, AddressString]()
				{
					OnClientConnected.Broadcast(AddressString);
				});
			}

			for (auto ClientPair : Clients)
			{
				TSharedPtr<FTCPClient> Client = ClientPair.Value;

				ESocketConnectionState ConnectionState = Client->Socket->GetConnectionState();
				if (ConnectionState != ESocketConnectionState::SCS_Connected)
				{
					ClientDisconnected.Add(Client);
					continue;
				}

				if (Client->Socket->HasPendingData(BufferSize))
				{
					ReceiveBuffer.SetNumUninitialized(BufferSize);
					int32 Read = 0;

					Client->Socket->Recv(ReceiveBuffer.GetData(), ReceiveBuffer.Num(), Read);

					if (bReceiveDataOnGameThread)
					{
						TArray<uint8> ReceiveBufferGT;
						ReceiveBufferGT.Append(ReceiveBuffer);

						AsyncTask(ENamedThreads::GameThread, [&, ReceiveBufferGT]()
						{
							OnReceivedBytes.Broadcast(ReceiveBufferGT);
						});
					}
					else
					{
						OnReceivedBytes.Broadcast(ReceiveBuffer);
					}
				}

				if (bShouldPing)
				{
					FDateTime Now = FDateTime::Now();
					float TimeSinceLastPing = (Now - LastPing).GetTotalSeconds();

					if (TimeSinceLastPing > PingInterval)
					{
						LastPing = Now;
						int32 BytesSend = 0;
						bool Send = Client->Socket->Send(PingData.GetData(), PingData.Num(), BytesSend);
						if (!Send)
						{
							Client->Socket->Close();
						}
					}
				}
			}

			if(ClientDisconnected.Num() > 0)
			{
				for(TSharedPtr<FTCPClient> ClientToRemove : ClientDisconnected)
				{
					const FString Address = ClientToRemove->Address;
					Clients.Remove(Address);
					AsyncTask(ENamedThreads::GameThread, [this, Address]()
					{
						OnClientDisconnected.Broadcast(Address);
					});
				}
				ClientDisconnected.Empty();
			}

			FPlatformProcess::Sleep(0.0001);
		}

		for(auto ClientPair : Clients)
		{
			ClientPair.Value->Socket->Close();
		}
		Clients.Empty();

		AsyncTask(ENamedThreads::GameThread, [&, InListenPort]()
		{
			Clients.Empty();
			OnListenEnd.Broadcast(InListenPort);
		});
	});
}

void ATCPServer::StopListenServer()
{
	if (ListenSocket)
	{
		bShouldListen = false;
		OnListenEnd.Broadcast(ListenSocket->GetPortNo());
		ServerFinishedFuture.Get();

		ListenSocket->Close();
		ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(ListenSocket);
		ListenSocket = nullptr;
	}
}

bool ATCPServer::Emit(const TArray<uint8>& Bytes, const FString& ToClient)
{
	if (Clients.Num() > 0)
	{
		int32 BytesSent = 0;
		if (ToClient == TEXT("All"))
		{
			bool bSuccess = true;
			TArray<TSharedPtr<FTCPClient>> AllClients;

			Clients.GenerateValueArray(AllClients);
			for (TSharedPtr<FTCPClient>& Client : AllClients)
			{
				if (Client.IsValid())
				{
					// FString str = TEXT("i am server!");
					// TCHAR* pSendData = str.GetCharArray().GetData();
					// int32 Strlen = FCString::Strlen(pSendData);
					// uint8* dst = (uint8*)TCHAR_TO_UTF8(pSendData);
					// bool bSend = Client->Socket->Send(dst, Strlen, BytesSent);
					bool bSend = Client->Socket->Send(Bytes.GetData(), Bytes.Num(), BytesSent);
					if (!bSend && bDisconnectOnFailedEmit)
					{
						Client->Socket->Close();
					}
					bSuccess = bSend && bSuccess;
				}
			}
			return bSuccess;
		}
		else
		{
			TSharedPtr<FTCPClient> Client = Clients[ToClient];
			if (Client.IsValid())
			{
				bool bSend = Client->Socket->Send(Bytes.GetData(), Bytes.Num(), BytesSent);
				if (bSend && bDisconnectOnFailedEmit)
				{
					Client->Socket->Close();
				}
				return bSend;
			}
		}
	}
	
	return false;
}

void ATCPServer::DisconnectClient(FString ClientAddress, bool bDisconnectNextTick)
{
	TFunction<void()> DisconnectFunction = [&, ClientAddress]
	{
		bool bDisconnectAll = ClientAddress == TEXT("All");
		if (!bDisconnectAll)
		{
			TSharedPtr<FTCPClient> Client = Clients[ClientAddress];
			if (Client.IsValid())
			{
				Client->Socket->Close();
				Clients.Remove(Client->Address);
				OnClientDisconnected.Broadcast(ClientAddress);
			}
		}
		else
		{
			for (auto ClientPair : Clients)
			{
				TSharedPtr<FTCPClient> Client = ClientPair.Value;
				Client->Socket->Close();
				Clients.Remove(Client->Address);
				OnClientDisconnected.Broadcast(ClientAddress);
			}
		}
	};

	if (bDisconnectNextTick)
	{
		AsyncTask(ENamedThreads::GameThread, DisconnectFunction);
	}
	else
	{
		DisconnectFunction();
	}
}


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

#pragma once

#include "CoreMinimal.h"

#include "TCPServer.h"
#include "GameFramework/Actor.h"
#include "TCPClient.generated.h"

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

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;

	FTCPMessageSignature OnReceivedBytes;
	FTCPEventSignature OnConnected;
	FString ConnectionIP;
	int32 ConnectionPort;
	FString ClientSocketName;
	int32 BufferMaxSize;
	bool bShouldAutoConnectOnBeginPlay;
	bool bReceiveDataOnGameThread;
	bool bIsConnected;

	void ConnectToSocketAsClient(const FString& InIP = TEXT("127.0.0.1"), const int32 InPort = 3001);
	void CloseSocket();
	UFUNCTION(BlueprintCallable)
	bool Emit(const TArray<uint8>& Bytes);
private:
	FSocket* ClientSocket;
	FThreadSafeBool bShouldReceiveData;
	TFuture<void> ClientConnectionFinishedFuture;

	FString SocketDescription;
	TSharedPtr<FInternetAddr> RemoteAddress;
	
};

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


#include "TCPClient.h"

#include "UESocket.h"

// Sets default values
ATCPClient::ATCPClient()
{
	// 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;

	bShouldAutoConnectOnBeginPlay = false;
	bReceiveDataOnGameThread = true;
	ConnectionIP = FString(TEXT("127.0.0.1"));
	ConnectionPort = 3001;
	ClientSocketName = FString(TEXT("ue4-tcp-client"));
	ClientSocket = nullptr;

	BufferMaxSize = 2 * 1024 * 1024;
}

// Called when the game starts or when spawned
void ATCPClient::BeginPlay()
{
	Super::BeginPlay();
	if (bShouldAutoConnectOnBeginPlay)
	{
		ConnectToSocketAsClient(ConnectionIP, ConnectionPort);
	}
}

void ATCPClient::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
	Super::EndPlay(EndPlayReason);
	CloseSocket();
}

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

void ATCPClient::ConnectToSocketAsClient(const FString& InIP, const int32 InPort)
{
	RemoteAddress = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();

	bool bIsValid;
	RemoteAddress->SetIp(*InIP, bIsValid);
	RemoteAddress->SetPort(InPort);

	if (!bIsValid)
	{
		UE_LOG(LogTemp, Error, TEXT("TCP address is invalid <%s:%d>"), *InIP, InPort);
		return;
	}

	ClientSocket = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateSocket(NAME_Stream, ClientSocketName, false);

	ClientSocket->SetSendBufferSize(BufferMaxSize, BufferMaxSize);
	ClientSocket->SetReceiveBufferSize(BufferMaxSize, BufferMaxSize);

	bIsConnected = ClientSocket->Connect(*RemoteAddress);
	if (bIsConnected)
	{
		OnConnected.Broadcast(InPort);
	}
	bShouldReceiveData = true;

	ClientConnectionFinishedFuture = ThreadUtility::RunLambdaOnBackgroundThread([&]()
	{
		uint32 BufferSize = 0;
		TArray<uint8> ReceiveBuffer;
		while (bShouldReceiveData)
		{
			if (ClientSocket->HasPendingData(BufferSize))
			{
				ReceiveBuffer.SetNumUninitialized(BufferSize);
				int32 Read = 0;
				ClientSocket->Recv(ReceiveBuffer.GetData(), ReceiveBuffer.Num(), Read);

				if (bReceiveDataOnGameThread)
				{
					TArray<uint8> ReceiveBufferGT;
					ReceiveBufferGT.Append(ReceiveBuffer);

					AsyncTask(ENamedThreads::GameThread, [&, ReceiveBufferGT]()
					{
						OnReceivedBytes.Broadcast(ReceiveBufferGT);
			
					});
				}
				else
				{
					OnReceivedBytes.Broadcast(ReceiveBuffer);
				}
			}
			ClientSocket->Wait(ESocketWaitConditions::WaitForReadOrWrite, FTimespan(1));
		}
		
	});
	
}

void ATCPClient::CloseSocket()
{
	if (ClientSocket)
	{
		bShouldReceiveData = false;
		ClientConnectionFinishedFuture.Get();

		ClientSocket->Close();
		ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(ClientSocket);
		ClientSocket = nullptr;
	}
}

bool ATCPClient::Emit(const TArray<uint8>& Bytes)
{
	if (ClientSocket && ClientSocket->GetConnectionState() == SCS_Connected)
	{
		int32 BytesSent = 0;
		return ClientSocket->Send(Bytes.GetData(), Bytes.Num(), BytesSent);
	}
	return false;
}


#1. 演示视频
链接:https://pan.baidu.com/s/1UJ8zn0fBIW4eNxYqRo-Mqw
提取码:hy4s

#2. 源码白给
链接:https://pan.baidu.com/s/1BRUfE6U7_2GceVLt1tsRNQ?pwd=0sx4
提取码:0sx4


### UE5中的UDP和TCP Socket通信 在Unreal Engine 5 (UE5) 中,可以通过使用 `FSocket` 和其他网络类来实现简单的 UDP 或 TCP 套接字通信。这些功能位于引擎的核心库中,并提供了创建客户端和服务端所需的工具。 #### 创建一个基本的TCP服务器 要设置一个基础的TCP服务器,可以按照以下方式操作: 1. **初始化套接字子系统** 需要在应用程序启动时调用 `ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)` 来获取当前平台支持的套接字子系统实例[^1]。 2. **绑定监听地址** 使用 `CreateListenerSocket` 方法创建并绑定到指定IP和端口上的侦听器套接字[^2]。 3. **接受连接请求** 当有新连接到来时,通过异步线程处理传入的数据流或者直接读取数据缓冲区的内容[^3]。 以下是用于构建简单TCP Server的一个代码片段示例: ```cpp void StartTCPServer(const FString& InAddress, int32 Port) { ISocketSubsystem* SocketSubSystem = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM); // Create a new IPv4 address from the given string representation. TSharedRef<FInternetAddr> LocalAddr = SocketSubSystem->CreateInternetAddr(); bool bIsValid; uint8 IpVersion = EIpVersion::IPv4; FIPv4Address IPValue; FIPv4Address::Parse(InAddress, IPValue); LocalAddr->SetIp(IPValue.Value, &bIsValid); check(bIsValid); LocalAddr->SetPort(Port); ListenerSocket = SocketSubSystem->CreateSocket(NAME_Stream, TEXT("Default"), false); if (!ListenerSocket.IsValid()) return; ListenerSocket->Bind(*LocalAddr); } ``` #### 实现UDP广播消息发送/接收 对于UDP协议来说,则不需要建立持久性的链接关系即可互相传递信息包。下面是如何配置以及收发UDP数据报的方法概述: - 调用 `CreateSocket` 函数生成一个新的无连接模式下的socket对象。 - 设置该socket为非阻塞状态以便于多任务并发执行效率更高。 - 利用SendTo函数向目标主机投递特定长度的消息体;而ReceiveFrom则负责捕获来自外部源节点的信息帧。 这里给出一段关于如何利用C++编写简易版UDP Client的例子: ```cpp bool SendUDPMessage(FString MessageToSend, const FString& TargetIPAddress, int32 TargetPort) { ISocketSubsystem* SocketSubsystem = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM); if(!SocketSubsystem){ return false; } TUniquePtr<FSocket> UDPSocket = nullptr; UDPSocket.Reset(SocketSubsystem->CreateSocket(NAME_DGram,"TempUDPSocket",false)); if(!UDPSocket || !UDPSocket->IsValid()){ return false; } TArray<uint8> OutgoingData; OutgoingData.AddZeroed(MessageToSend.Len()); FCStringAnsi::Strncpy((ANSICHAR*)OutgoingData.GetData(),TCHAR_TO_UTF8(*MessageToSend),MessageToSend.Len()); FInetAddress RecipientAddress; RecipientAddress.SetIp(TargetIPAddress); RecipientAddress.SetPort(TargetPort); int32 BytesSent=0; bool SuccessFlag = UDPSocket->SendTo(RecipientAddress,(uint8*)&OutgoingData[0],OutgoingData.Num(),BytesSent); return SuccessFlag && (BytesSent == OutgoingData.Num()); } ``` 以上就是有关于UE5环境下搭建起基础形式下两种不同类型的sockets解决方案介绍[^4].
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值