UE4 TCP协议连接服务器与客户端

B站教学链接:https://space.bilibili.com/449549424?spm_id_from=333.1007.0.0


一、TCP原理简介

       TCP是传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793  定义。

       TCP旨在适应支持多网络应用的分层协议层次结构。 连接到不同但互连的计算机通信网络的主计算机中的成对进程之间依靠TCP提供可靠的通信服务。TCP假设它可以从较低级别的协议获得简单的,可能不可靠的数据报服务。 原则上,TCP应该能够在从硬线连接到分组交换或电路交换网络的各种通信系统之上操作。

主要特点是:

       TCP是一种面向广域网的通信协议,目的是在跨越多个网络通信时,为两个通信端点之间提供一条具有下列特点的通信方式: 

(1)基于流的方式;

(2)面向连接;

(3)可靠通信方式;

(4)在网络状况不佳的时候尽量降低系统由于重传带来的带宽开销;

(5)通信连接维护是面向通信的两个端点的,而不考虑中间网段和节点。

工作方式:TCP是因特网中的传输层协议,使用三次握手协议建立连接。当主动方发出SYN连接请求后,等待对方回答SYN+ACK,并最终对对方的 SYN 执行 ACK 确认。这种建立连接的方法可以防止产生错误的连接,TCP使用的流量控制协议是可变大小的滑动窗口协议。 

二、UE4多线程原理

       线程:是操作系统能够进行运行调度的最小单位。大部分情况下,它被包含在进程之中,是进程中的实际运作单位。一条线程指的的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

       UE4是跨平台的引擎,对各个平台线程实现进行了封装,抽象出了FRunable。引擎中大部分的需要多线程执行逻辑都是继承这个类实现的多线程。

     UE4的FRunable多线程执行任务逻辑逻辑如下:

#include "HAL/Runnable.h"
UCLASS()
class TCPPROJECT_API USocketRSThread : public UObject,public FRunnable
{
	GENERATED_BODY()
public:
	virtual bool Init() override { return true; }
	virtual uint32 Run() override  { return 0; }
	virtual void Stop() override {}
	virtual void Exit() override {}
}

调用顺序是 Init()Run()Exit()。Runnable对象初始化操作在 Init() 函数中完成,并通过返回值确定是否成功。初始化失败,则该线程停止执行,并返回一个错误码;成功,则会执行 Run() ;执行完毕后,则会调用 Exit() 执行清理操作。

三、案例介绍    

 本次案例使用的是UE4引擎的Sockets模块和Networking模块,利用Socket进行通信,利用UE4的多线程来处理分发任务。

前期准备,在你的项目的build.cs中添加两个模块,

第一步:先创建一个Object类,继承自UObject,FRunable,命名为SocketRSThread

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

#pragma once

#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "Runtime/Sockets/Public/Sockets.h"
#include "HAL/Runnable.h"
#include "Sockets/Public/SocketSubsystem.h"
#include "SocketRSThread.generated.h"

/**
 * 
 */

//声明两个带参数的动态多播代理
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FReceiveSocketDataDelegate, FString, Data);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FLostConnectionDelegate, USocketRSThread*, Thread);


UCLASS()
class TCPPROJECT_API USocketRSThread : public UObject,public FRunnable
{
	GENERATED_BODY()
public:
	virtual bool Init() override { return true; }
	virtual uint32 Run() override;
	virtual void Stop() override;
	virtual void Exit() override {}

	void Start(FSocket* Socket, uint32 SendDataSize, uint32 RecDataSize);
	//发送数据
	void Send(FString Message);
	//接收数据的代理通知
	FReceiveSocketDataDelegate ReceiveSocketDataDelegate;
	//断开连接的代理通知
	FLostConnectionDelegate	LostConnectionDelegate;

protected:
	FSocket* ConnectSocket;
	uint32 SendDataSize1;
	uint32 RecDataSize1;
	TArray<uint8> ReceiveData;
	/** 线程相关 */
	FRunnableThread* pThread;
	bool bThreadStop;

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


#include "SocketRSThread.h"

uint32 USocketRSThread::Run()
{
	while (!bThreadStop)
	{
		//这个地方是之前将socket设置为阻塞模式 在这里5s内判断是否断开连接
		uint32 Size;
		bool LostConnect = false;
		ConnectSocket->HasPendingConnection(LostConnect);
		ConnectSocket->Wait(ESocketWaitConditions::WaitForReadOrWrite, FTimespan(0, 0, 5));
		if (LostConnect)
		{
			//UE_LOG(LogTemp, Warning, TEXT(" doesn't Connect "));
			Stop();
			LostConnectionDelegate.Broadcast(this);
			continue;
		}

		/** 处理接收数据 */
		if (ConnectSocket && ConnectSocket->HasPendingData(Size))
		{
			ReceiveData.Init(0, FMath::Min(Size, RecDataSize1));
			int32 Readed;
			ConnectSocket->Recv(ReceiveData.GetData(), RecDataSize1, Readed);
			FString ReceivedString = FString(ANSI_TO_TCHAR(reinterpret_cast<const char*>(ReceiveData.GetData())));
			ReceiveSocketDataDelegate.Broadcast(ReceivedString);
		}
	}
	return 0;
}

void USocketRSThread::Stop()
{
	bThreadStop = true;
	ConnectSocket->Close();
	ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(ConnectSocket);
	ConnectSocket = nullptr;
}

void USocketRSThread::Start(FSocket* Socket, uint32 SendDataSize, uint32 RecDataSize)
{
	this->ConnectSocket = Socket;
	this->SendDataSize1 = SendDataSize;
	this->RecDataSize1 = RecDataSize;
	FRunnableThread::Create(this, TEXT("Receive Threald"));
}

void USocketRSThread::Send(FString Message)
{
	///** 处理发送数据 */
	TCHAR* SendMessage = Message.GetCharArray().GetData();
	int32 size = FCString::Strlen(SendMessage) + 1;
	int32 sent = 0;
	if (size >= (int32)SendDataSize1)
	{
		UE_LOG(LogTemp, Error, TEXT("Send Data Size is Larger than Max Size for set"));
	}
	else
	{
		if (ConnectSocket && ConnectSocket->Send((uint8*)TCHAR_TO_UTF8(SendMessage), size, sent))
		{
			UE_LOG(LogTemp, Warning, TEXT("___Send Succeed!"));
			
		}
		else
		{
			UE_LOG(LogTemp, Error, TEXT("___Send Failed!"));
		}
	}
}

 第二步:创建TCPServerActor

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

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include"SocketRSThread.h"
#include"TimerManager.h"
#include "Runtime/Sockets/Public/Sockets.h"
#include "TCPServer.generated.h"



DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FServerSocketCreateDelegate, bool, bSuccess);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FConnectReceiveDelegate, FString, RemoteIP, int32, RemotePort);

UCLASS(BlueprintType, Blueprintable)
class TCPPROJECT_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;

	//发送和接受的数据大小
	int32 SendDataSize;
	int32 RecDataDize;
	//服务器Socket
	FSocket* serverSocket;

	UPROPERTY(BlueprintAssignable, VisibleAnywhere, Category = Network)
		FServerSocketCreateDelegate SocketCreateDelegate;
	UPROPERTY(BlueprintAssignable, VisibleAnywhere, Category = Network)
		FConnectReceiveDelegate ConnectReceiveDelegate;
	UPROPERTY(BlueprintAssignable, VisibleAnywhere, Category = Network)
		FReceiveSocketDataDelegate ReceiveSocketDataDelegate;

	FTimerHandle ConnectCheckHandler;
	TArray<USocketRSThread*> RecThreads;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;
	//创建服务器
	UFUNCTION(BlueprintCallable, Category = Network)
		void CreateServer(const FString& ServerIP, int32 ServerPort, int32 ReceiveBufferSize = 1024, int32 SendBufferSize = 1024);
	/** 关闭Server */
	UFUNCTION(BlueprintCallable, Category = Network)
		void CloseServer();
	/** 检测是否有客户端连入 */
	void ConnectCheck();
	//发送数据到客户端
	UFUNCTION(BlueprintCallable, Category = Network)
		void SendToClient(FString Message);
	//客户端断开连接
	UFUNCTION(Category = Network)
		void OnClientDisconnect(class USocketRSThread* pThread);

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


#include "TCPServer.h"
#include "SocketRSThread.h"
#include "Sockets/Public/SocketSubsystem.h"
#include "Networking/Public/Interfaces/IPv4/IPv4Address.h"
#include "Runtime/Networking/Public/Common/TcpSocketBuilder.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;

}

// Called when the game starts or when spawned
void ATCPServer::BeginPlay()
{
	Super::BeginPlay();
	
}

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

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

}

void ATCPServer::CreateServer(const FString& ServerIP, int32 ServerPort, int32 ReceiveBufferSize, int32 SendBufferSize)
{
	this->RecDataDize = ReceiveBufferSize;
	this->SendDataSize = SendBufferSize;
	FIPv4Address ServerAddr;
	if (!FIPv4Address::Parse(ServerIP, ServerAddr))
	{
		UE_LOG(LogTemp, Error, TEXT("Server Ip %s is illegal"), *ServerIP);
	}
	serverSocket = FTcpSocketBuilder(TEXT("Socket Listener"))
		.AsReusable()
		.AsBlocking()
		.BoundToAddress(ServerAddr)
		.BoundToPort(ServerPort)
		.Listening(8)
		.WithReceiveBufferSize(ReceiveBufferSize)
		.WithSendBufferSize(SendBufferSize);
	if (serverSocket)
	{
		UE_LOG(LogTemp, Warning, TEXT("Server Create Success!"), *ServerIP);
		SocketCreateDelegate.Broadcast(true);
		GetWorld()->GetTimerManager().SetTimer(ConnectCheckHandler, this, &ATCPServer::ConnectCheck, 1, true);
	}
}

void ATCPServer::CloseServer()
{
	if(serverSocket)
	{
		serverSocket->Close();
		for (auto RecThreald : RecThreads)
		{
			RecThreald->Stop();
		}
		ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(serverSocket);
		UE_LOG(LogTemp, Warning, TEXT("Close is Succeed"));
	}
}

void ATCPServer::ConnectCheck()
{
	bool bPending = false;
	if (serverSocket->HasPendingConnection(bPending) && bPending)
	{
		//有新的socket连接进来
		TSharedRef<FInternetAddr> RemoteAddress = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();
		FSocket* RecSocket = serverSocket->Accept(*RemoteAddress, TEXT("Receive Socket"));
		USocketRSThread* RSThread = NewObject<USocketRSThread>();
		RecThreads.Add(RSThread);
		RSThread->ReceiveSocketDataDelegate = ReceiveSocketDataDelegate;
		RSThread->LostConnectionDelegate.AddDynamic(this, &ATCPServer::OnClientDisconnect);
		RSThread->Start(RecSocket, SendDataSize, RecDataDize);
		ConnectReceiveDelegate.Broadcast(RemoteAddress->ToString(false), RemoteAddress->GetPort());
	}
}

void ATCPServer::SendToClient(FString Message)
{
	for (auto SocketThread : RecThreads)
	{
		SocketThread->Send(Message);
	}

}

void ATCPServer::OnClientDisconnect(USocketRSThread* pThread)
{
	UE_LOG(LogTemp, Warning, TEXT("Client lost"));
	RecThreads.Remove(pThread);
}

第三步:创建TCPClientActor

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

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "SocketRSThread.h"
#include "Runtime/Sockets/Public/Sockets.h"
#include "TCPClient.generated.h"

UCLASS(BlueprintType, Blueprintable)
class TCPPROJECT_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;

	UFUNCTION()
		void OnServerDisconnect(class USocketRSThread* pThread);
public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;



	UFUNCTION(BlueprintCallable, Category = Network)
		void CreateClientAndConnect(FString ServerIP, int32 Port, int32 ReceiveSize = 1024, int32 SendSize = 1024);

	UFUNCTION(Category = Network)
		bool ConnectServer(FString ServerIP, int32 Port);

	UFUNCTION(BlueprintCallable, Category = Network)
		void SendToServer(FString Message);

	UPROPERTY(BlueprintAssignable, VisibleAnywhere, Category = Network)
		FReceiveSocketDataDelegate ReceiveSocketDataDelegate;

protected:
	//客户端Sokcet
	class FSocket* ClientSocket;
	//发送的数据大小
	int32 SendDataSize;
	//接收的数据大小
	int32 RecDataDize;
	//多线程
	TArray<class USocketRSThread*> RecThreads;

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


#include "TCPClient.h"
#include "Runtime/Networking/Public/Common/TcpSocketBuilder.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;

}

// Called when the game starts or when spawned
void ATCPClient::BeginPlay()
{
	Super::BeginPlay();
	
}

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

}
void ATCPClient::CreateClientAndConnect(FString ServerIP, int32 Port, int32 ReceiveSize, int32 SendSize)
{
	this->SendDataSize = SendSize;
	this->RecDataDize = ReceiveSize;

	ClientSocket = FTcpSocketBuilder(TEXT("Client Socket"))
		.AsReusable()
		.AsBlocking()
		.WithReceiveBufferSize(ReceiveSize)
		.WithSendBufferSize(SendSize);

	if (!ClientSocket)
	{
		UE_LOG(LogTemp, Error, TEXT("Create Client Socket Error!"));
	}
	else
	{
		ConnectServer(ServerIP, Port);
	}

}
bool ATCPClient::ConnectServer(FString ServerIP, int32 Port)
{
	FIPv4Endpoint ServerEndpoint;
	FIPv4Endpoint::Parse(ServerIP, ServerEndpoint);
	TSharedPtr<FInternetAddr> addr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();
	bool Success = true;
	addr->SetIp(*ServerIP, Success);
	if (!Success)
	{
		return false;
	}
	addr->SetPort(Port);

	if (ClientSocket->Connect(*addr))
	{
		USocketRSThread* RSThread = NewObject<USocketRSThread>();
		RecThreads.Add(RSThread);
		RSThread->ReceiveSocketDataDelegate = ReceiveSocketDataDelegate;
		RSThread->LostConnectionDelegate.AddDynamic(this, &ATCPClient::OnServerDisconnect);
		RSThread->Start(ClientSocket, SendDataSize, RecDataDize);
		UE_LOG(LogTemp, Warning, TEXT("Client Connect Success"));
		return true;
	}
	else
	{
		ESocketErrors LastErr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->GetLastErrorCode();

		UE_LOG(LogTemp, Warning, TEXT("Connect failed with error code (%d) error (%s)"), LastErr, ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->GetSocketError(LastErr));
		ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(ClientSocket);
	}
	return false;
}
void ATCPClient::SendToServer(FString Message)
{
	for (auto SocketThread : RecThreads)
	{
		SocketThread->Send(Message);
	}
}

void ATCPClient::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
	//Super::EndPlay(EndPlayReason);
	if (ClientSocket)
	{
		for (auto RecThreald : RecThreads)
		{
			RecThreald->Stop();
		}
	}
}

void ATCPClient::OnServerDisconnect(USocketRSThread* pThread)
{
	UE_LOG(LogTemp, Warning, TEXT("Server lost"));
	RecThreads.Remove(pThread);
}

第四步:创建基于TCPServer,TCPClient蓝图

 

 第五步:打开蓝图,绑定通知

 

第六步:创建UMG,ServerMain,ClientMain

 

第七步:打开关卡蓝图,创建widget显示到屏幕上

 第八步:打包测试,我这里复制了一份项目,一个作为客户端,一个作为服务器

服务器设置:场景中加入BP_TCPServer.,关卡蓝图widget为ServerMain

 

 客户端设置:场景中加入BP_TCPClient.,关卡蓝图widget为ClientMain

 

测试结果如下:先运行服务器,在开启客户端

服务器创建成功

客户端连接成功

 

 客户端向服务器发送数据

 服务器向客户端发送数据

 服务器关闭

 

客户端显示服务器丢失 

 

 服务器显示客户端丢失

 

  • 14
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 24
    评论
### 回答1: UE4(Unreal Engine 4)是一款强大的游戏开发引擎,它提供了多种网络功能,包括TCP客户端。 在UE4中,可以通过蓝图或C++代码实现TCP客户端。首先需要创建一个Socket对象并将其连接服务器。可以使用Socket子系统来处理与TCP服务器的通信。 在连接建立之后,可以使用Socket对象发送和接收数据。可以使用Socket的Send()函数向服务器发送数据,使用Recv()函数接收服务器的响应数据。 在客户端代码中,可以根据游戏逻辑和需求,编写相应的处理逻辑。例如,可以使用线程来异步接收服务器的响应数据,并在主线程中处理接收到的数据。 同时,需要在客户端代码中处理TCP连接的错误和异常情况,以确保网络通信的稳定性和可靠性。可以使用Try/Catch块来捕获可能发生的异常,并进行相应的处理。 除了基本的TCP客户端功能,UE4还提供了其他更高级的网络功能,如RPC(Remote Procedure Call)和Replication(复制),用于处理远程函数调用和多人游戏中的数据同步等问题。 总的来说,UE4提供了方便易用的TCP客户端功能,开发人员可以根据自己的需求使用蓝图或C++代码编写相应的网络逻辑,实现与服务器的通信。 ### 回答2: UE4是一款流行的游戏引擎,它提供了强大的功能和工具,用于开发高质量的游戏和应用程序。UE4通过使用蓝图和C++编程语言来创建游戏逻辑和功能。 UE4中的TCP客户端是一种用于建立基于TCP/IP协议的网络通信的工具。TCP(传输控制协议)是一种可靠的连接协议,它能够确保数据的可靠传输和顺序传递。TCP客户端可以与远程服务器进行通信,发送和接收数据。 在UE4中创建TCP客户端需要以下步骤: 1. 导入网络模块:首先,需要在UE4项目中导入网络模块,以在蓝图或代码中使用相应的网络功能。 2. 创建TCP套接字:通过使用UE4提供的套接字类创建一个TCP套接字对象。套接字是一种网络通信的接口,它可以在客户端服务器之间建立连接。 3. 连接服务器:使用套接字对象的Connect函数来连接到远程服务器。在连接之前,需要提供服务器IP地址和端口号。 4. 发送和接收数据:一旦连接建立成功,可以使用套接字对象的Send和Receive函数来发送和接收数据。可以通过字符串或字节数组的方式发送和接收数据。 5. 关闭连接:当通信结束时,需要使用套接字对象的Close函数来关闭连接,并释放相关资源。 通过以上步骤,我们就可以在UE4中创建一个TCP客户端,实现与远程服务器的通信。这样可以打开许多可能性,比如实时多人游戏中的玩家之间的通信、在线排行榜的更新等等。TCP客户端在网络通信中具有可靠性和稳定性,因此在很多场景下都是一个重要的组件。 ### 回答3: UE4是一款强大的游戏开发引擎,支持使用TCP协议进行网络通信。下面是关于UE4 TCP客户端的详细说明: 首先,UE4提供了一系列的网络编程功能来实现TCP客户端。要创建一个TCP客户端,首先需要在项目中添加网络功能模块。然后,在游戏中创建一个Socket对象,该对象将用于与服务器进行通信。 接下来,需要通过Socket对象连接服务器。在连接之前,需要指定服务器IP地址和端口号。一旦连接成功,就可以通过Socket对象发送和接收消息。 要发送消息,可以使用UE4的Send方法,通过Socket对象将消息数据发送给服务器。要接收消息,可以使用UE4的Recv方法,通过Socket对象从服务器接收消息数据。使用这些方法可以实现与服务器的双向通信。 在实际应用中,可以根据游戏的需求和设计来使用TCP客户端。例如,可以在游戏中实现多人联机功能,通过TCP客户端来与其他玩家进行通信。还可以使用TCP客户端在游戏中获取服务器提供的数据,例如排行榜信息或者游戏状态更新等。 总结来说,UE4提供了丰富的网络编程功能,可以轻松实现TCP客户端。通过连接服务器、发送和接收消息,可以与服务器进行双向通信。这为游戏开发者提供了很多可能性,可以实现各种有趣的网络功能和游戏玩法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 24
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

飞起的猪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值