You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
The issue was that when the udp socket builder got a socket error (e.g. bind failed), prior to calling GetLastError it was calling GLog->Logf, which under certain circumstances can also cause an error which GetLastError would then return (tends to happen when -log is in the command line), which may not even be a valid socket error code, therefore being flagged as an unhandled socket error and ultimately crashing with check(0). The fix is to call GetLastError before calling any other function that can clobber the error code that GetLastError returns. Also added a warning message when the receive or send buffer sizes fail to be set, but it will still return the otherwise configured socket. #rb simon.therriault #jira #preflight 61f1d845fd5285142b4fae3a #ROBOMERGE-AUTHOR: alejandro.arango #ROBOMERGE-SOURCE: CL 18747425 in //UE5/Release-5.0/... via CL 18747447 via CL 18747646 #ROBOMERGE-BOT: UE5 (Release-Engine-Test -> Main) (v903-18687472) [CL 18747665 by alejandro arango in ue5-main branch]
449 lines
12 KiB
C++
449 lines
12 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#pragma once
|
|
|
|
#include "CoreMinimal.h"
|
|
#include "Misc/OutputDeviceRedirector.h"
|
|
#include "Sockets.h"
|
|
#include "SocketSubsystem.h"
|
|
#include "Interfaces/IPv4/IPv4Address.h"
|
|
#include "Interfaces/IPv4/IPv4Endpoint.h"
|
|
|
|
class Error;
|
|
|
|
/**
|
|
* Implements a fluent builder for UDP sockets.
|
|
*/
|
|
class FUdpSocketBuilder
|
|
{
|
|
public:
|
|
|
|
/**
|
|
* Creates and initializes a new instance.
|
|
*
|
|
* @param InDescription Debug description for the socket.
|
|
*/
|
|
FUdpSocketBuilder(const FString& InDescription)
|
|
: AllowBroadcast(false)
|
|
, Blocking(false)
|
|
, Bound(false)
|
|
, BoundEndpoint(FIPv4Address::Any, 0)
|
|
, Description(InDescription)
|
|
, MulticastInterface(FIPv4Address::Any)
|
|
, MulticastLoopback(false)
|
|
, MulticastTtl(1)
|
|
, ReceiveBufferSize(0)
|
|
, Reusable(false)
|
|
, SendBufferSize(0)
|
|
{ }
|
|
|
|
public:
|
|
|
|
/**
|
|
* Sets socket operations to be blocking.
|
|
*
|
|
* @return This instance (for method chaining).
|
|
* @see AsNonBlocking, AsReusable
|
|
*/
|
|
FUdpSocketBuilder& AsBlocking()
|
|
{
|
|
Blocking = true;
|
|
|
|
return *this;
|
|
}
|
|
|
|
/**
|
|
* Sets socket operations to be non-blocking.
|
|
*
|
|
* @return This instance (for method chaining).
|
|
* @see AsBlocking, AsReusable
|
|
*/
|
|
FUdpSocketBuilder& AsNonBlocking()
|
|
{
|
|
Blocking = false;
|
|
|
|
return *this;
|
|
}
|
|
|
|
/**
|
|
* Makes the bound address reusable by other sockets.
|
|
*
|
|
* @return This instance (for method chaining).
|
|
* @see AsBlocking, AsNonBlocking
|
|
*/
|
|
FUdpSocketBuilder& AsReusable()
|
|
{
|
|
Reusable = true;
|
|
|
|
return *this;
|
|
}
|
|
|
|
/**
|
|
* Sets the local address to bind the socket to.
|
|
*
|
|
* Unless specified in a subsequent call to BoundToPort(), a random
|
|
* port number will be assigned by the underlying provider.
|
|
*
|
|
* @param Address The IP address to bind the socket to.
|
|
* @return This instance (for method chaining).
|
|
* @see BoundToEndpoint, BoundToPort
|
|
*/
|
|
FUdpSocketBuilder& BoundToAddress(const FIPv4Address& Address)
|
|
{
|
|
BoundEndpoint = FIPv4Endpoint(Address, BoundEndpoint.Port);
|
|
Bound = true;
|
|
|
|
return *this;
|
|
}
|
|
|
|
/**
|
|
* Sets the local endpoint to bind the socket to.
|
|
*
|
|
* @param Endpoint The IP endpoint to bind the socket to.
|
|
* @return This instance (for method chaining).
|
|
* @see BoundToAddress, BoundToPort
|
|
*/
|
|
FUdpSocketBuilder& BoundToEndpoint(const FIPv4Endpoint& Endpoint)
|
|
{
|
|
BoundEndpoint = Endpoint;
|
|
Bound = true;
|
|
|
|
return *this;
|
|
}
|
|
|
|
/**
|
|
* Sets the local port to bind the socket to.
|
|
*
|
|
* Unless specified in a subsequent call to BoundToAddress(), the local
|
|
* address will be determined automatically by the underlying provider.
|
|
*
|
|
* @param Port The local port number to bind the socket to.
|
|
* @return This instance (for method chaining).
|
|
* @see BoundToAddress
|
|
*/
|
|
FUdpSocketBuilder& BoundToPort(int32 Port)
|
|
{
|
|
BoundEndpoint = FIPv4Endpoint(BoundEndpoint.Address, Port);
|
|
Bound = true;
|
|
|
|
return *this;
|
|
}
|
|
|
|
/**
|
|
* Joins the socket to the specified multicast group.
|
|
*
|
|
* @param GroupAddress The IP address of the multicast group to join.
|
|
* @return This instance (for method chaining).
|
|
* @see WithMulticastLoopback, WithMulticastTtl
|
|
*/
|
|
FUdpSocketBuilder& JoinedToGroup(const FIPv4Address& GroupAddress)
|
|
{
|
|
return JoinedToGroup(GroupAddress, FIPv4Address::Any);
|
|
}
|
|
|
|
/**
|
|
* Joins the socket to the specified multicast group.
|
|
*
|
|
* @param GroupAddress The IP address of the multicast group to join.
|
|
* @param InterfaceAddress The IP address of the interface to join the multicast group on.
|
|
* @return This instance (for method chaining).
|
|
* @see WithMulticastLoopback, WithMulticastTtl
|
|
*/
|
|
FUdpSocketBuilder& JoinedToGroup(const FIPv4Address& GroupAddress, const FIPv4Address& InterfaceAddress)
|
|
{
|
|
JoinedGroups.Add({ GroupAddress, InterfaceAddress });
|
|
|
|
return *this;
|
|
}
|
|
|
|
/**
|
|
* Enables broadcasting.
|
|
*
|
|
* @return This instance (for method chaining).
|
|
*/
|
|
FUdpSocketBuilder& WithBroadcast()
|
|
{
|
|
AllowBroadcast = true;
|
|
|
|
return *this;
|
|
}
|
|
|
|
/**
|
|
* Enables multicast loopback.
|
|
*
|
|
* @param AllowLoopback Whether to allow multicast loopback.
|
|
* @param TimeToLive The time to live.
|
|
* @return This instance (for method chaining).
|
|
* @see JoinedToGroup, WithMulticastTtl
|
|
*/
|
|
FUdpSocketBuilder& WithMulticastLoopback()
|
|
{
|
|
MulticastLoopback = true;
|
|
|
|
return *this;
|
|
}
|
|
|
|
/**
|
|
* Sets the multicast time-to-live.
|
|
*
|
|
* @param TimeToLive The time to live.
|
|
* @return This instance (for method chaining).
|
|
* @see JoinedToGroup, WithMulticastLoopback
|
|
*/
|
|
FUdpSocketBuilder& WithMulticastTtl(uint8 TimeToLive)
|
|
{
|
|
MulticastTtl = TimeToLive;
|
|
|
|
return *this;
|
|
}
|
|
|
|
/**
|
|
* Sets the multicast outgoing interface.
|
|
*
|
|
* @param InterfaceAddress The interface to use to send multicast datagrams.
|
|
* @return This instance (for method chaining).
|
|
* @see JoinedToGroup, WithMulticastLoopback
|
|
*/
|
|
FUdpSocketBuilder& WithMulticastInterface(const FIPv4Address& InterfaceAddress)
|
|
{
|
|
MulticastInterface = InterfaceAddress;
|
|
|
|
return *this;
|
|
}
|
|
|
|
|
|
/**
|
|
* Specifies the desired size of the receive buffer in bytes (0 = default).
|
|
*
|
|
* The socket creation will not fail if the desired size cannot be set or
|
|
* if the actual size is less than the desired size.
|
|
*
|
|
* @param SizeInBytes The size of the buffer.
|
|
* @return This instance (for method chaining).
|
|
* @see WithSendBufferSize
|
|
*/
|
|
FUdpSocketBuilder& WithReceiveBufferSize(int32 SizeInBytes)
|
|
{
|
|
ReceiveBufferSize = SizeInBytes;
|
|
|
|
return *this;
|
|
}
|
|
|
|
/**
|
|
* Specifies the desired size of the send buffer in bytes (0 = default).
|
|
*
|
|
* The socket creation will not fail if the desired size cannot be set or
|
|
* if the actual size is less than the desired size.
|
|
*
|
|
* @param SizeInBytes The size of the buffer.
|
|
* @return This instance (for method chaining).
|
|
* @see WithReceiveBufferSize
|
|
*/
|
|
FUdpSocketBuilder& WithSendBufferSize(int32 SizeInBytes)
|
|
{
|
|
SendBufferSize = SizeInBytes;
|
|
|
|
return *this;
|
|
}
|
|
|
|
public:
|
|
|
|
/**
|
|
* Implicit conversion operator that builds the socket as configured.
|
|
*
|
|
* @return The built socket.
|
|
*/
|
|
operator FSocket*() const
|
|
{
|
|
return Build();
|
|
}
|
|
|
|
/**
|
|
* Builds the socket as configured.
|
|
*
|
|
* @return The built socket.
|
|
*/
|
|
FSocket* Build() const
|
|
{
|
|
// load socket subsystem
|
|
ISocketSubsystem* SocketSubsystem = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM);
|
|
|
|
if (SocketSubsystem == nullptr)
|
|
{
|
|
GLog->Log(TEXT("FUdpSocketBuilder: Failed to load socket subsystem"));
|
|
return nullptr;
|
|
}
|
|
|
|
// create socket
|
|
TSharedRef<FInternetAddr> RemoteAddr = BoundEndpoint.ToInternetAddr();
|
|
FSocket* Socket = SocketSubsystem->CreateSocket(NAME_DGram, *Description, RemoteAddr->GetProtocolType());
|
|
|
|
if (Socket == nullptr)
|
|
{
|
|
GLog->Logf(TEXT("FUdpSocketBuilder: Failed to create socket %s"), *Description);
|
|
return nullptr;
|
|
}
|
|
|
|
// configure socket
|
|
|
|
if (!Socket->SetNonBlocking(!Blocking) ||
|
|
!Socket->SetReuseAddr(Reusable) ||
|
|
!Socket->SetBroadcast(AllowBroadcast) ||
|
|
!Socket->SetRecvErr())
|
|
{
|
|
const ESocketErrors SocketError = SocketSubsystem->GetLastErrorCode();
|
|
|
|
GLog->Logf(TEXT("FUdpSocketBuilder: Failed to configure %s (blocking: %i, reusable: %i, broadcast: %i). Error code %d."),
|
|
*Description, Blocking, Reusable, AllowBroadcast, int32(SocketError));
|
|
|
|
SocketSubsystem->DestroySocket(Socket);
|
|
return nullptr;
|
|
}
|
|
|
|
// bind socket
|
|
|
|
if (Bound && !Socket->Bind(*RemoteAddr))
|
|
{
|
|
const ESocketErrors SocketError = SocketSubsystem->GetLastErrorCode();
|
|
|
|
GLog->Logf(TEXT("FUdpSocketBuilder: Failed to bind %s to %s. Error code %d."),
|
|
*Description, *BoundEndpoint.ToString(), int32(SocketError));
|
|
|
|
SocketSubsystem->DestroySocket(Socket);
|
|
return nullptr;
|
|
}
|
|
|
|
// configure multicast
|
|
|
|
if (!Socket->SetMulticastLoopback(MulticastLoopback) || !Socket->SetMulticastTtl(MulticastTtl))
|
|
{
|
|
const ESocketErrors SocketError = SocketSubsystem->GetLastErrorCode();
|
|
|
|
GLog->Logf(TEXT("FUdpSocketBuilder: Failed to configure multicast for %s (loopback: %i, ttl: %i). Error code %d."),
|
|
*Description, MulticastLoopback, MulticastTtl, int32(SocketError));
|
|
|
|
SocketSubsystem->DestroySocket(Socket);
|
|
return nullptr;
|
|
}
|
|
|
|
// join multicast groups
|
|
|
|
TSharedRef<FInternetAddr> MulticastAddress = SocketSubsystem->CreateInternetAddr(RemoteAddr->GetProtocolType());
|
|
MulticastAddress->SetBroadcastAddress();
|
|
TSharedPtr<FInternetAddr> AddressToUse = nullptr;
|
|
|
|
for (const auto& Group : JoinedGroups)
|
|
{
|
|
// The Socket code no longer has the multicast address hack handled anymore, as such, we need to properly determine the address ourselves.
|
|
if (Group.GroupAddress.IsSessionFrontendMulticast() && MulticastAddress->GetProtocolType() != FNetworkProtocolTypes::IPv4)
|
|
{
|
|
// This will use the address protocol that we figured out earlier.
|
|
AddressToUse = MulticastAddress;
|
|
}
|
|
else
|
|
{
|
|
// Otherwise, we'll use the address we use all the time.
|
|
AddressToUse = FIPv4Endpoint(Group.GroupAddress, 0).ToInternetAddr();
|
|
}
|
|
|
|
if (!Socket->JoinMulticastGroup(*AddressToUse, *FIPv4Endpoint(Group.InterfaceAddress, 0).ToInternetAddr()))
|
|
{
|
|
const ESocketErrors SocketError = SocketSubsystem->GetLastErrorCode();
|
|
|
|
GLog->Logf(TEXT("FUdpSocketBuilder: Failed to subscribe %s to multicast group %s on interface %s. Error code %d."),
|
|
*Description, *Group.GroupAddress.ToString(), *Group.InterfaceAddress.ToString(), int32(SocketError));
|
|
|
|
SocketSubsystem->DestroySocket(Socket);
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
// set multicast outgoing interface
|
|
if (MulticastInterface != FIPv4Address::Any)
|
|
{
|
|
if (!Socket->SetMulticastInterface(*FIPv4Endpoint(MulticastInterface, 0).ToInternetAddr()))
|
|
{
|
|
const ESocketErrors SocketError = SocketSubsystem->GetLastErrorCode();
|
|
|
|
GLog->Logf(TEXT("FUdpSocketBuilder: Failed to set multicast outgoing interface for %s to %s. Error code %d."),
|
|
*Description, *MulticastInterface.ToString(), int32(SocketError));
|
|
|
|
SocketSubsystem->DestroySocket(Socket);
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
// set buffer sizes
|
|
{
|
|
int32 OutNewSize;
|
|
|
|
if (ReceiveBufferSize > 0)
|
|
{
|
|
if (!Socket->SetReceiveBufferSize(ReceiveBufferSize, OutNewSize))
|
|
{
|
|
const ESocketErrors SocketError = SocketSubsystem->GetLastErrorCode();
|
|
|
|
GLog->Logf(TEXT("FUdpSocketBuilder: Warning - could not set receive buffer size to %d for %s. Error code %d."),
|
|
ReceiveBufferSize, *Description, int32(SocketError));
|
|
}
|
|
}
|
|
|
|
if (SendBufferSize > 0)
|
|
{
|
|
if (!Socket->SetSendBufferSize(SendBufferSize, OutNewSize))
|
|
{
|
|
const ESocketErrors SocketError = SocketSubsystem->GetLastErrorCode();
|
|
|
|
GLog->Logf(TEXT("FUdpSocketBuilder: Warning - could not set send buffer size to %d for %s. Error code %d."),
|
|
SendBufferSize, *Description, int32(SocketError));
|
|
}
|
|
}
|
|
}
|
|
|
|
return Socket;
|
|
}
|
|
|
|
private:
|
|
|
|
/** Holds a flag indicating whether broadcasts will be enabled. */
|
|
bool AllowBroadcast;
|
|
|
|
/** Holds a flag indicating whether socket operations are blocking. */
|
|
bool Blocking;
|
|
|
|
/** Holds a flag indicating whether the socket should be bound. */
|
|
bool Bound;
|
|
|
|
/** Holds the IP address (and port) that the socket will be bound to. */
|
|
FIPv4Endpoint BoundEndpoint;
|
|
|
|
/** Holds the socket's debug description text. */
|
|
FString Description;
|
|
|
|
/** Holds the IP address of the interface to use for outgoing multicast datagrams. */
|
|
FIPv4Address MulticastInterface;
|
|
|
|
/** Holds the list of joined multicast groups. */
|
|
struct FMulticastGroup
|
|
{
|
|
FIPv4Address GroupAddress;
|
|
FIPv4Address InterfaceAddress;
|
|
};
|
|
TArray<FMulticastGroup> JoinedGroups;
|
|
|
|
/** Holds a flag indicating whether multicast loopback will be enabled. */
|
|
bool MulticastLoopback;
|
|
|
|
/** Holds the multicast time to live. */
|
|
uint8 MulticastTtl;
|
|
|
|
/** The desired size of the receive buffer in bytes (0 = default). */
|
|
int32 ReceiveBufferSize;
|
|
|
|
/** Holds a flag indicating whether the bound address can be reused by other sockets. */
|
|
bool Reusable;
|
|
|
|
/** The desired size of the send buffer in bytes (0 = default). */
|
|
int32 SendBufferSize;
|
|
};
|