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


非常感谢匿名大哥一直对我的支持,本文内容由他赞助
在这里插入图片描述
该Socket可以用于小型或创业型公司的网络服务器,可以用于VR、游戏等领域,代码纯绿色、效率一手自测验证,直接贴代码


如果有遇到断开客户端,服务器无法检测的问题
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
提取码:0sx0



版权声明:本文为inspironx原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。