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中添加两个模块,

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

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

UE4 TCP协议连接服务器与客户端
// 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;
    /** &#x7EBF;&#x7A0B;&#x76F8;&#x5173; */
    FRunnableThread* pThread;
    bool bThreadStop;

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

#include "SocketRSThread.h"

uint32 USocketRSThread::Run()
{
    while (!bThreadStop)
    {
        //&#x8FD9;&#x4E2A;&#x5730;&#x65B9;&#x662F;&#x4E4B;&#x524D;&#x5C06;socket&#x8BBE;&#x7F6E;&#x4E3A;&#x963B;&#x585E;&#x6A21;&#x5F0F; &#x5728;&#x8FD9;&#x91CC;5s&#x5185;&#x5224;&#x65AD;&#x662F;&#x5426;&#x65AD;&#x5F00;&#x8FDE;&#x63A5;
        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;
        }

        /** &#x5904;&#x7406;&#x63A5;&#x6536;&#x6570;&#x636E; */
        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)
{
    ///** &#x5904;&#x7406;&#x53D1;&#x9001;&#x6570;&#x636E; */
    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!"));
        }
    }
}

</const>

第二步:创建TCPServerActor

UE4 TCP协议连接服务器与客户端
// 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;

    //&#x53D1;&#x9001;&#x548C;&#x63A5;&#x53D7;&#x7684;&#x6570;&#x636E;&#x5927;&#x5C0F;
    int32 SendDataSize;
    int32 RecDataDize;
    //&#x670D;&#x52A1;&#x5668;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;
    //&#x521B;&#x5EFA;&#x670D;&#x52A1;&#x5668;
    UFUNCTION(BlueprintCallable, Category = Network)
        void CreateServer(const FString& ServerIP, int32 ServerPort, int32 ReceiveBufferSize = 1024, int32 SendBufferSize = 1024);
    /** &#x5173;&#x95ED;Server */
    UFUNCTION(BlueprintCallable, Category = Network)
        void CloseServer();
    /** &#x68C0;&#x6D4B;&#x662F;&#x5426;&#x6709;&#x5BA2;&#x6237;&#x7AEF;&#x8FDE;&#x5165; */
    void ConnectCheck();
    //&#x53D1;&#x9001;&#x6570;&#x636E;&#x5230;&#x5BA2;&#x6237;&#x7AEF;
    UFUNCTION(BlueprintCallable, Category = Network)
        void SendToClient(FString Message);
    //&#x5BA2;&#x6237;&#x7AEF;&#x65AD;&#x5F00;&#x8FDE;&#x63A5;
    UFUNCTION(Category = Network)
        void OnClientDisconnect(class USocketRSThread* pThread);

};
</usocketrsthread*>
// 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)
    {
        //&#x6709;&#x65B0;&#x7684;socket&#x8FDE;&#x63A5;&#x8FDB;&#x6765;
        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);
}

</usocketrsthread></finternetaddr>

第三步:创建TCPClientActor

UE4 TCP协议连接服务器与客户端
// 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:
    //&#x5BA2;&#x6237;&#x7AEF;Sokcet
    class FSocket* ClientSocket;
    //&#x53D1;&#x9001;&#x7684;&#x6570;&#x636E;&#x5927;&#x5C0F;
    int32 SendDataSize;
    //&#x63A5;&#x6536;&#x7684;&#x6570;&#x636E;&#x5927;&#x5C0F;
    int32 RecDataDize;
    //&#x591A;&#x7EBF;&#x7A0B;
    TArray<class usocketrsthread*> RecThreads;

};
</class>
// 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);
}
</usocketrsthread></finternetaddr>

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

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

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

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

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

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

第六步:创建UMG,ServerMain,ClientMain

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

服务器创建成功

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

客户端连接成功

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

客户端向服务器发送数据

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

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

服务器关闭

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

客户端显示服务器丢失

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

服务器显示客户端丢失

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

Original: https://blog.csdn.net/qq_43021038/article/details/126519570
Author: 飞起的猪
Title: UE4 TCP协议连接服务器与客户端

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/724570/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球