UE4_UDPSocket进行不同工程的数据交互

来源:互联网 发布:网络语bug什么意思 编辑:程序博客网 时间:2024/05/21 06:00

以下内容仅对此次工程做解析。UE4.15.1 VS2015

实现效果:

客户端与服务器端的socket连接之后。客户端设定一个timer去发送消息服务端每个timer去检查是否接受到了消息,如果接收到了就讲消息打印

原理解析:

想要实现不同工程下的通讯。需要在同一个局域网内(外网还未测)。两个工程需要一个为客户端一个为服务端。在这里实现的是一个客户端发送数据而服务端接收数据。

实现步骤:
客户端:

1.创建一个socketsocket()。2.新建一个FInternetAddr共享变量并设置ip与端口号。3.socket发送到FInternetAddr指定ip端口号。

服务端:

1.创建一个socketsocket()。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 valuesARamaUDPSender::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 valuesARamaUDPReceiver::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);       }}//初始化Receivervoid 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;}

调用:

客户端:
这里写图片描述
服务端:
这里写图片描述