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