以下内容仅对此次工程做解析。UE4.15.1 VS2015
实现效果:
客户端与服务器端的socket连接之后。
客户端设定一个timer去发送消息
服务端每个timer去检查是否接受到了消息,如果接收到了就讲消息打印
原理解析:
想要实现不同工程下的通讯。需要在同一个局域网内(外网还未测)。两个工程需要一个为客户端一个为服务端。在这里实现的是一个客户端发送数据而服务端接收数据。
实现步骤:
客户端:
1.创建一个socket:socket()。
2.新建一个FInternetAddr共享变量并设置ip与端口号。
3.socket发送到FInternetAddr指定ip端口号。
服务端:
1.创建一个socket:socket()。
2.创建一个FInternetAddr共享变量并设置ip与端口号。
IPv4Endpoint Endpoint(FIPv4Address::Any, ThePort); //所有ip地址 本地的。any:0.0.0.0 任何主机ip都可用
//FIPv4Endpoint Endpoint(Addr, ThePort); //指定ip地址
3.定义一个接收器,接收数据。并返回结果
具体实现:
客户端:
.h
#include "Runtime/Networking/Public/Networking.h"
UCLASS()
class SOCKET_ALL_API ARamaUDPSender : public AActor
{
//新建isudp的bool变量
bool IsUDP;
//新建函数RamaUDPSender_SendString(),用于发送消息
UFUNCTION(BlueprintCallable, Category = "UDP")
bool RamaUDPSender_SendString(FString ToSend);
public:
//TSharedPtr是一个非侵入性引用计数的权威对象指针。 当可选模式模板参数设置为ThreadSafe时,此共享指针将有条件地线程安全。
//TSharedPtr(OtherType * InObject) 构造一个拥有指定对象的共享指针。
//新建RemoteAddr变量
TSharedPtr<FInternetAddr> RemoteAddr;
//FSocket 特定套接字实现的抽象基类
//新建 SenderSocket指针变量
FSocket* SenderSocket;
//新建StartUDPSender()函数,并传入socketname,ip,port与是否使用udp的变量
UFUNCTION(BlueprintCallable, Category = "UDP")
bool StartUDPSender(const FString& YourChosenSocketName, const FString& TheIP, const int32 ThePort, bool UDP);
public:
//UPROPERTY 定义属性
//新建ShowOnScreenDebugMessages的bool变量。是否显示调试信息
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UDP")
bool ShowOnScreenDebugMessages;
//ScreenMsg
// FORCEINLINE 它是非标准关键字,它覆盖编译器的启发式和强制内联当前函数。
FORCEINLINE void ScreenMsg(const FString& Msg)
{
if (!ShowOnScreenDebugMessages) return;
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, *Msg);
}
FORCEINLINE void ScreenMsg(const FString& Msg, const float Value)
{
if (!ShowOnScreenDebugMessages) return;
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, FString::Printf(TEXT("%s %f"), *Msg, Value));
}
FORCEINLINE void ScreenMsg(const FString& Msg, const FString& Msg2)
{
if (!ShowOnScreenDebugMessages) return;
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, FString::Printf(TEXT("%s %s"), *Msg, *Msg2));
}
public:
/** Called whenever this actor is being removed from a level */
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
public:
// Sets default values for this actor's properties
ARamaUDPSender();
}
.cpp
// Sets default values
ARamaUDPSender::ARamaUDPSender()
{
// 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;
//初始化变量
// SenderSocket 为 null ,默认显示调试信息
SenderSocket = NULL;
ShowOnScreenDebugMessages = true;
}
//结束时调用函数
void ARamaUDPSender::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
Super::EndPlay(EndPlayReason);
//~~~~~~~~~~~~~~~~
//结束之后,判断是否存在sendersocket,如果有,就把其关闭,并且销毁PLATFORM_SOCKETSUBSYSTE的这个sendersocket。
if (SenderSocket) //Clear all sockets!
{
SenderSocket->Close();
ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(SenderSocket);
}
}
//开始发送初始化,传入socketname,ip与port和是否使用udp,默认使用。后面可以进行扩展
bool ARamaUDPSender::StartUDPSender(const FString & YourChosenSocketName, const FString & TheIP, const int32 ThePort, bool UDP)
{
//FIPv4Endpoint Endpoint(FIPv4Address::Any, 6789);
//Create Remote Address.
//ISocketSubsystem::Get 获取给定的命名子系统的单例套接字子系统
//创建PLATFORM_SOCKETSUBSYSTEM的InternetAddr地址
RemoteAddr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();
//创建bIsValid
bool bIsValid;
//FInternetAddr::SetIp void SetIp( const TCHAR * InAddr, bool & bIsValid ) 结果对bIsValid进行修改。
RemoteAddr->SetIp(*TheIP, bIsValid);
RemoteAddr->SetPort(ThePort);
if (!bIsValid)
{
ScreenMsg("Rama UDP Sender>> IP address was not valid!", TheIP);
return false;
}
//FUdpSocketBuilder: Implements a fluent builder for UDP sockets.
SenderSocket = FUdpSocketBuilder(*YourChosenSocketName)
.AsReusable()//使绑定的地址可以被其他套接字重用。 这个实例(用于方法链)。
.WithBroadcast()/广播
.WithSendBufferSize(2 * 1024 * 1024)//指定发送缓冲区的所需大小(以字节为单位)(0 = 默认值)。
//.BoundToEndpoint(Endpoint)
;
//check(SenderSocket->GetSocketType() == SOCKTYPE_Datagram);
//定义发送和接收的文件size范围
//Set Send Buffer Size
int32 SendSize = 2 * 1024 * 1024;
SenderSocket->SetSendBufferSize(SendSize, SendSize);
SenderSocket->SetReceiveBufferSize(SendSize, SendSize);
if (bIsValid)
{
bIsValid = true;
}
return bIsValid;
}
//发送消息
bool ARamaUDPSender::RamaUDPSender_SendString(FString ToSend)
{
//判断是否存在SenderSocket。若没有直接不发送。返回false
if (!SenderSocket)
{
ScreenMsg("No sender socket");
return false;
}
//~~~~~~~~~~~~~~~~
//发送消息
int32 BytesSent = 0;
FString serialized = ToSend;
TCHAR *serializedChar = serialized.GetCharArray().GetData();
int32 size = FCString::Strlen(serializedChar);
int32 sent = 0;
//SenderSocket->SendTo(Writer.GetData(), Writer.Num(), BytesSent, *RemoteAddr);
//FSocket::SendTo virtual bool SendTo(const uint8 * Data,int32 Count,int32 & BytesSent,const FInternetAddr & Destination)
// Data:需要发送的数据 Count:要发送数据长度 BytesSent:发送数据返回值,发送了多少 Destination:要发送的网络字节有序地址,在.h文件里面新建的ip地址变量,在StartUDPSender()中有赋值
SenderSocket->SendTo((uint8*)TCHAR_TO_UTF8(serializedChar), size, BytesSent, *RemoteAddr);//发送给远端地址
//判断是否发送了数据
if (BytesSent <= 0)
{
const FString Str = "Socket is valid but the receiver received 0 bytes, make sure it is listening properly!";
UE_LOG(LogTemp, Error, TEXT("%s"), *Str);
ScreenMsg(Str);
return false;
}
ScreenMsg("UDP Send Succcess! INFO Sent = ", ToSend);
return true;
}
服务端:
.h
#include "Runtime/Networking/Public/Networking.h"
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
public:
//新建ListenSocket指针
//FSocket特定套接字实现的抽象基类
FSocket* ListenSocket;
//新建空的UDPReceiver指针
//FUdpSocketReceiver 从UDP套接字异步接收数据。
FUdpSocketReceiver* UDPReceiver = nullptr;
UFUNCTION(BlueprintCallable, Category = "UDP")
//新建初始化Receiver函数
void StartUDPReceiver(const FString& YourChosenSocketName, const FString& TheIP, const int32 ThePort, bool& success);
UFUNCTION(BlueprintPure, Category = "UDP")
//DataRecv返回函数
void DataRecv(FString& str, bool& success);
//ScreenMsg
//FORCEINLINE 它是非标准关键字,它覆盖编译器的启发式和强制内联当前函数。
FORCEINLINE void ScreenMsg(const FString& Msg)
{
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, *Msg);
}
FORCEINLINE void ScreenMsg(const FString& Msg, const float Value)
{
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, FString::Printf(TEXT("%s %f"), *Msg, Value));
}
FORCEINLINE void ScreenMsg(const FString& Msg, const FString& Msg2)
{
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, FString::Printf(TEXT("%s %s"), *Msg, *Msg2));
}
public:
/** Called whenever this actor is being removed from a level */
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
.cpp
// Sets default values
ARamaUDPReceiver::ARamaUDPReceiver()
{
// 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;
//初始化ListenSocket
ListenSocket = NULL;
}
//结束时出发事件
void ARamaUDPReceiver::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
Super::EndPlay(EndPlayReason);
//~~~~~~~~~~~~~~~~
//UDPReceiver置空
delete UDPReceiver;
UDPReceiver = nullptr;
//Clear all sockets!
// makes sure repeat plays in Editor dont hold on to old sockets!
//若ListenSocket不为空
if (ListenSocket)
{
//关闭,销毁
ListenSocket->Close();
ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(ListenSocket);
}
}
//初始化Receiver
void ARamaUDPReceiver::StartUDPReceiver(const FString & YourChosenSocketName, const FString & TheIP, const int32 ThePort, bool & success)
{
//TSharedRef是不可空的非侵入性引用计数的权威对象引用。
//TSharedPtr(OtherType * InObject) 构造一个拥有指定对象的共享指针。
//新建RemoteAddr变量
//ISocketSubsystem::Get 获取给定的命名子系统的单例套接字子系统
// ISocketSubsystem::CreateInternetAddr TSharedRef < FInternetAddr > CreateInternetAddr ( uint32 Address, uint32 Port )
TSharedRef<FInternetAddr> targetAddr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();
//FIPv4Address Implements an IPv4 address.,实现了ipv4地址
FIPv4Address Addr;
//Parse() 将字符串转换为IPv4地址。
FIPv4Address::Parse(TheIP, Addr);
//Create Socket
// FIPv4Endpoint::FIPv4Endpoint FIPv4Endpoint ( const FIPv4Address & InAddress, uint16 InPort ) InAddress:The endpoint's IP address. InPort:The endpoint's port number.
//使用指定的NetID和端口创建并初始化新的IPv4端点。
// Any: Defines the wild card endpoint, which is 0.0.0.0:0
FIPv4Endpoint Endpoint(FIPv4Address::Any, ThePort); //所有ip地址本地
//FIPv4Endpoint Endpoint(Addr, ThePort); //指定ip地址
//FUdpSocketBuilder: Implements a fluent builder for UDP sockets.
ListenSocket = FUdpSocketBuilder(*YourChosenSocketName)
.AsNonBlocking()//将套接字操作设置为非阻塞。 这个实例(用于方法链)。
.AsReusable()//使绑定的地址可以被其他套接字重用。 这个实例(用于方法链)。
.BoundToEndpoint(Endpoint)//设置将端口绑定到本地端点。 这个实例(用于方法链)。
.WithReceiveBufferSize(2 * 1024 * 1024)//设置接收数据大小
;
//BUFFER SIZE
int32 BufferSize = 2 * 1024 * 1024;
ListenSocket->SetSendBufferSize(BufferSize, BufferSize);
ListenSocket->SetReceiveBufferSize(BufferSize, BufferSize);
if (!ListenSocket)
{
ScreenMsg("No socket");
success = false;
}
if (ListenSocket)
{
ScreenMsg("The receiver is initialized");
success = true;
}
//return true;
}
void ARamaUDPReceiver::DataRecv(FString & str, bool & success)
{
if (!ListenSocket)
{
ScreenMsg("No sender socket");
success = false;
//return success;
}
//TSharedRef是不可空的非侵入性引用计数的权威对象引用。
//TSharedPtr(OtherType * InObject) 构造一个拥有指定对象的共享指针。
//新建RemoteAddr变量
//ISocketSubsystem::Get 获取给定的命名子系统的单例套接字子系统
// ISocketSubsystem::CreateInternetAddr TSharedRef < FInternetAddr > CreateInternetAddr ( uint32 Address, uint32 Port )
TSharedRef<FInternetAddr> targetAddr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();
TArray<uint8> ReceivedData;//定义一个接收器
uint32 Size;
//ListenSocket->HasPendingData(Size) 查询套接字以确定队列中是否有挂起的数据,如果套接字有数据,则为true,否则为false Size参数指示单个recv调用的管道上有多少数据
if (ListenSocket->HasPendingData(Size))
{
success = true;
str = "";
uint8 *Recv = new uint8[Size];
int32 BytesRead = 0;
//将数组调整到给定数量的元素。 新元素将被初始化。
ReceivedData.SetNumUninitialized(FMath::Min(Size, 65507u));
ListenSocket->RecvFrom(ReceivedData.GetData(), ReceivedData.Num(), BytesRead, *targetAddr);//创建远程接收地址
char ansiiData[1024];
memcpy(ansiiData, ReceivedData.GetData(), BytesRead);//拷贝数据到接收器
ansiiData[BytesRead] = 0; //判断数据结束
FString debugData = ANSI_TO_TCHAR(ansiiData); //字符串转换
str = debugData;
// memset(ansiiData,0,1024);//清空
}
else
{
success = false;
}
//return success;
}
调用:
客户端:
服务端: