How to Get Host By Name?

I’m using this function to connect to my server. I’m trying to avoid using an ip address, because it’s easier to change where my domain points, than it is to change a line of code and submit a patch to my userbase. When it is run, the program breaks where “…GetResolvedAddress().GetIp(ip)” is called. I’ve found that “…GetResolvedAddress().isValid()” causes it to break as well.

void ULoginUI::connect()
{
	Socket = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateSocket(NAME_Stream, TEXT("default"), false);
	Domain = "some.host.com";
	port = 1234;

	uint32 ip;

	ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->GetHostByName(Domain)->GetResolvedAddress().GetIp(ip);

	TSharedRef<FInternetAddr> addr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();
	addr->SetIp(ip);
	addr->SetPort(port);

	Connected = Socket->Connect(*addr);
	if (Connected)
	{
		GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Green, TEXT("connected to server"));
	}
	else
	{
		GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("failed to connect to server"));
	}
}

I have the same problem. Have you found a solution to the problem?

It’s asyncronous! You have to wait for the ResolveInfo to, well, resolve! I figured it out!

    ISocketSubsystem* const SocketSubSystem = ISocketSubsystem::Get();
	

	if (SocketSubSystem)
	{
		auto ResolveInfo = SocketSubSystem->GetHostByName("renpure.com");
		while (!ResolveInfo->IsComplete());

		if (ResolveInfo->GetErrorCode() == 0)
		{
			const FInternetAddr* Addr = &ResolveInfo->GetResolvedAddress();
			uint32 OutIP = 0;
			Addr->GetIp(OutIP);
			
			UE_LOG(UBLog, Warning, TEXT("IP is %d.%d.%d.%d: "), 0xff & (OutIP >> 24), 0xff & (OutIP >> 16), 0xff & (OutIP >> 8), 0xff & OutIP);
		}
	}

I just got back around to see this, Thanks!

Does not work if steam is enabled.

I think if you modify steam to only passthrough it will work. By default steam oss was using steam p2p. We changed it to use ipnetdriver and later we changed it so steam was only used for server list and parties.

Yes, I used the default settings from the documentation. I always get error 21 when steam is activated. Does anyone know where I can read the meaning of the error number?

I’m thinking about doing the DNS query myself via UDP.

Great find @Cobryis !
I would however recommend putting a failsafe on that while() loop since that’s going to be a blocking operation which could freeze your game if GetHostByName() never completes.
Something like:

FString UMySpiffyGameInstance::RequestIPAddressFromURL(const FString& URL)
{
	FString ReturnValue;
	ISocketSubsystem* const SocketSubSystem = ISocketSubsystem::Get();
	if (ensure(SocketSubSystem))
	{
		GetHostByNameTimeout = GetWorld()->GetTimeSeconds() + 5.0f;
		auto ResolveInfo = SocketSubSystem->GetHostByName(TCHAR_TO_ANSI(*URL));
		while (!ResolveInfo->IsComplete() && GetWorld()->GetTimeSeconds() < GetHostByNameTimeout);	// #TODO: BLOCKING OPERATION - Can we bind a delegate to this response?
 
		if (ResolveInfo->IsComplete() && ResolveInfo->GetErrorCode() == 0)
		{
			const FInternetAddr* Addr = &ResolveInfo->GetResolvedAddress();
			uint32 OutIP = 0;
			Addr->GetIp(OutIP);
 
			UE_LOG(LogMySpiffyGame, Log, TEXT("%s Found IP address for URL <%s>: %d.%d.%d.%d: ")
				, *GetName()
				, *URL, 0xff & (OutIP >> 24), 0xff & (OutIP >> 16), 0xff & (OutIP >> 8), 0xff & OutIP);
			ReturnValue = Addr->ToString(false);
		}
	}
	return ReturnValue;
}

Update: ISocketSubsystem::GetHostByName() is deprecated in Unreal 5.0 and should be replaced by a call to ISocketSubsystem::GetAddressInfoAsync(). Because this is an asynchronous call you’ll need to specify a delegate to be called when it returns a value, and because that delegate is likely to run on another thread than the game thread, it’ll be a good idea to use an AsyncTask call to relay it to the game thread. Here’s an example:


static void OnAsyncAddressInfoReceived(ISocketSubsystem* SocketSub, UMyGameGameInstance* GameInstance, const FString& HostStr, const FAddressInfoResult& GAIResult)
{
	UE_LOG(LogMyGame, Log, TEXT("Got %d GAI Results for hostname %s. Error Code: %s [%d]"),
		GAIResult.Results.Num(),
		*GAIResult.QueryHostName,
		SocketSub->GetSocketError(GAIResult.ReturnCode),
		GAIResult.ReturnCode);
 
	for (int i = 0; i < GAIResult.Results.Num(); ++i)
	{
		const FAddressInfoResultData& Result{ GAIResult.Results[i] };
		const FString AddressString{ Result.Address->ToString(false) };
		const FString ProtocolTypeString{ Result.Address->GetProtocolType().ToString() };
		UE_LOG(LogMyGame, Log, TEXT("Result #%d Address: %s Type: %s"), i, *AddressString, *ProtocolTypeString);
 
		GameInstance->BlueprintOnAsyncAddressInfoReceived(HostStr, AddressString, ProtocolTypeString);
		GameInstance->OnAddressInfoReceivedDelegate.Broadcast(HostStr, AddressString, ProtocolTypeString);
	}
}

void UMyGameGameInstance::GetAddressInfoAsync(const FString& HostStr)
{
	if (HostStr.IsEmpty())
	{
		UE_LOG(LogMyGame, Warning, TEXT("UMyGameGameInstance::GetAddressInfoAsync requires a valid host string."));
		return;
	}
 
	ISocketSubsystem* SocketSub{ ISocketSubsystem::Get() };
	if (ensure(SocketSub))
	{
		const double AsyncRequestStartTime{ FPlatformTime::Seconds() };
		UMyGameGameInstance* GameInstance{ this };
		FAsyncGetAddressInfoCallback CallbackFunc = [SocketSub, GameInstance, HostStr, AsyncRequestStartTime](FAddressInfoResult Results)
		{
			UE_LOG(LogMyGame, Log, TEXT("Async GAI Request returned after %f seconds, started at %f"), FPlatformTime::Seconds() - AsyncRequestStartTime, AsyncRequestStartTime);
			if (IsInGameThread())
			{
				OnAsyncAddressInfoReceived(SocketSub, GameInstance, HostStr, Results);
			}
			else
			{
				AsyncTask(ENamedThreads::GameThread, [SocketSub, GameInstance, HostStr, Results] {
					OnAsyncAddressInfoReceived(SocketSub, GameInstance, HostStr, Results);
					});
			}
		};
		SocketSub->GetAddressInfoAsync(CallbackFunc, *HostStr, nullptr, EAddressInfoFlags::Default, NAME_None);
		return;
	}
 
	UE_LOG(LogMyGame, Warning, TEXT("Failed to get socket subsystem!"));
}

In the example above, I’m handling the call inside the gameinstance, and using an inline lambda to define the callback function. The callback function captures info we’re planning to use inside it, and then uses AsyncTask() to make sure the static function it calls runs on the game thread.
Asynchronous calls are more complicated to use than their synchronous counterparts but they’re much safer in a case like this where we can’t be sure how long it would take for the DNS to resolve the IP address or even whether it would ever succeed at all.
Also be sure to accommodate in your code the possibility that multiple async calls could be kicked off, and decide how you want to make sure you’re pairing the right response with the right call. In my case I just stash the URL I’m looking for and accept the response if the HostStr argument I carry along with it matches the stashed value.

1 Like