Keywords: UE4, Networking

The previous article explained how to connect Dedicated Server via IP address:
[UE4]Networking in Basic - Simple Replication Example
https://dawnarc.com/2017/02/ue4networking-in-basic-simple-replication-example/

Current article explained how to connect Server via Sessions(without IP).

Engine API

OnlineSubsystem

BP Node CreateSession in native:
Source Path:

\Engine\Plugins\Online\OnlineSubsystemUtils\Source\OnlineSubsystemUtils\Private\CreateSessionCallbackProxy.cpp

Native Function:

// Creates a session with the default online subsystem
UCreateSessionCallbackProxy* UCreateSessionCallbackProxy::CreateSession(UObject* WorldContextObject, class APlayerController* PlayerController, int32 PublicConnections, bool bUseLAN)
{
    UCreateSessionCallbackProxy* Proxy = NewObject<UCreateSessionCallbackProxy>();
    Proxy->PlayerControllerWeakPtr = PlayerController;
    Proxy->NumPublicConnections = PublicConnections;
    Proxy->bUseLAN = bUseLAN;
    Proxy->WorldContextObject = WorldContextObject;
    return Proxy;
}

BP Node FindSessions in native:
Source Path:

\Engine\Plugins\Online\OnlineSubsystemUtils\Source\OnlineSubsystemUtils\Private\FindSessionsCallbackProxy.cpp

Native Function:

// Searches for advertised sessions with the default online subsystem
UFindSessionsCallbackProxy* UFindSessionsCallbackProxy::FindSessions(UObject* WorldContextObject, class APlayerController* PlayerController, int MaxResults, bool bUseLAN)
{
    UFindSessionsCallbackProxy* Proxy = NewObject<UFindSessionsCallbackProxy>();
    Proxy->PlayerControllerWeakPtr = PlayerController;
    Proxy->bUseLAN = bUseLAN;
    Proxy->MaxResults = MaxResults;
    Proxy->WorldContextObject = WorldContextObject;
    return Proxy;
}

BP Node JoinSession in native:
Source Path:

\Engine\Plugins\Online\OnlineSubsystemUtils\Source\OnlineSubsystemUtils\Private\JoinSessionCallbackProxy.cpp

Native Function:

UJoinSessionCallbackProxy* UJoinSessionCallbackProxy::JoinSession(UObject* WorldContextObject, class APlayerController* PlayerController, const FBlueprintSessionResult& SearchResult)
{
    UJoinSessionCallbackProxy* Proxy = NewObject<UJoinSessionCallbackProxy>();
    Proxy->PlayerControllerWeakPtr = PlayerController;
    Proxy->OnlineSearchResult = SearchResult.OnlineResult;
    Proxy->WorldContextObject = WorldContextObject;
    return Proxy;
}

Other BP Nodes of Session:

\Engine\Plugins\Online\OnlineSubsystemUtils\Source\OnlineSubsystemUtils\Private\ConnectionCallbackProxy.cpp
\Engine\Plugins\Online\OnlineSubsystemUtils\Source\OnlineSubsystemUtils\Private\DestroySessionCallbackProxy.cpp

Usage

How to create session using C++
// Fill out your copyright notice in the Description page of Project Settings.

#include "UNWGameInstance.h"
#include "EngineGlobals.h"
#include "Engine/Engine.h"
#include "Kismet/GameplayStatics.h"

UNWGameInstance::UNWGameInstance(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)

{
    /** Bind function for CREATING a Session */
    OnCreateSessionCompleteDelegate = FOnCreateSessionCompleteDelegate::CreateUObject(this, &UNWGameInstance::OnCreateSessionComplete);
    OnStartSessionCompleteDelegate = FOnStartSessionCompleteDelegate::CreateUObject(this, &UNWGameInstance::OnStartOnlineGameComplete);
    /** Bind function for FINDING a Session */
    OnFindSessionsCompleteDelegate = FOnFindSessionsCompleteDelegate::CreateUObject(this, &UNWGameInstance::OnFindSessionsComplete);
    /** Bind function for JOINING a Session */
    OnJoinSessionCompleteDelegate = FOnJoinSessionCompleteDelegate::CreateUObject(this, &UNWGameInstance::OnJoinSessionComplete);
    /** Bind function for DESTROYING a Session */
    OnDestroySessionCompleteDelegate = FOnDestroySessionCompleteDelegate::CreateUObject(this, &UNWGameInstance::OnDestroySessionComplete);
}

bool UNWGameInstance::HostSession(TSharedPtr<const FUniqueNetId> UserId, FName SessionName, bool bIsLAN, bool bIsPresence, int32 MaxNumPlayers)
{
    // Get the Online Subsystem to work with
    IOnlineSubsystem* const OnlineSub = IOnlineSubsystem::Get();
    GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, FString::Printf(TEXT("Called Host Session")));
    if (OnlineSub)
    {
        // Get the Session Interface, so we can call the "CreateSession" function on it
        IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();

        if (Sessions.IsValid() && UserId.IsValid())
        {
            /*
            Fill in all the Session Settings that we want to use.

            There are more with SessionSettings.Set(...);
            For example the Map or the GameMode/Type.
            */
            SessionSettings = MakeShareable(new FOnlineSessionSettings());

            SessionSettings->bIsLANMatch = bIsLAN;
            SessionSettings->bUsesPresence = bIsPresence;
            SessionSettings->NumPublicConnections = MaxNumPlayers;
            SessionSettings->NumPrivateConnections = 0;
            SessionSettings->bAllowInvites = true;
            SessionSettings->bAllowJoinInProgress = true;
            SessionSettings->bShouldAdvertise = true;
            SessionSettings->bAllowJoinViaPresence = true;
            SessionSettings->bAllowJoinViaPresenceFriendsOnly = false;

            SessionSettings->Set(SETTING_MAPNAME, FString("Red Hot"), EOnlineDataAdvertisementType::ViaOnlineService);

            // Set the delegate to the Handle of the SessionInterface
            OnCreateSessionCompleteDelegateHandle = Sessions->AddOnCreateSessionCompleteDelegate_Handle(OnCreateSessionCompleteDelegate);

            // Our delegate should get called when this is complete (doesn't need to be successful!)
            return Sessions->CreateSession(*UserId, SessionName, *SessionSettings);
        }
    }
    else
    {
        GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, TEXT("No OnlineSubsytem found!"));
    }

    return false;
}

void UNWGameInstance::OnCreateSessionComplete(FName SessionName, bool bWasSuccessful)
{
    GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, FString::Printf(TEXT("OnCreateSessionComplete %s, %d"), *SessionName.ToString(), bWasSuccessful));

    // Get the OnlineSubsystem so we can get the Session Interface
    IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get();
    if (OnlineSub)
    {
        // Get the Session Interface to call the StartSession function
        IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();

        if (Sessions.IsValid())
        {
            // Clear the SessionComplete delegate handle, since we finished this call
            Sessions->ClearOnCreateSessionCompleteDelegate_Handle(OnCreateSessionCompleteDelegateHandle);
            if (bWasSuccessful)
            {
                // Set the StartSession delegate handle
                OnStartSessionCompleteDelegateHandle = Sessions->AddOnStartSessionCompleteDelegate_Handle(OnStartSessionCompleteDelegate);

                // Our StartSessionComplete delegate should get called after this
                Sessions->StartSession(SessionName);
            }
        }

    }
}

void UNWGameInstance::OnStartOnlineGameComplete(FName SessionName, bool bWasSuccessful)
{
    GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, FString::Printf(TEXT("OnStartSessionComplete %s, %d"), *SessionName.ToString(), bWasSuccessful));

    // Get the Online Subsystem so we can get the Session Interface
    IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get();
    if (OnlineSub)
    {
        // Get the Session Interface to clear the Delegate
        IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();
        if (Sessions.IsValid())
        {
            // Clear the delegate, since we are done with this call
            Sessions->ClearOnStartSessionCompleteDelegate_Handle(OnStartSessionCompleteDelegateHandle);
        }
    }

    // If the start was successful, we can open a NewMap if we want. Make sure to use "listen" as a parameter!
    if (bWasSuccessful)
    {
        UGameplayStatics::OpenLevel(GetWorld(), "StageLevel", true, "listen");
    }
}

void UNWGameInstance::FindSessions(TSharedPtr<const FUniqueNetId> UserId, bool bIsLAN, bool bIsPresence)
{
    UE_LOG(LogTemp, Log, TEXT("Finding sessions"));
    // Get the OnlineSubsystem we want to work with
    IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get();

    if (OnlineSub)
    {
        // Get the SessionInterface from our OnlineSubsystem
        IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();

        if (Sessions.IsValid() && UserId.IsValid())
        {
            /*
            Fill in all the SearchSettings, like if we are searching for a LAN game and how many results we want to have!
            */
            SessionSearch = MakeShareable(new FOnlineSessionSearch());

            SessionSearch->bIsLanQuery = bIsLAN;
            SessionSearch->MaxSearchResults = 20;
            SessionSearch->PingBucketSize = 1000;

            // We only want to set this Query Setting if "bIsPresence" is true
            if (bIsPresence)
            {
                SessionSearch->QuerySettings.Set(SEARCH_PRESENCE, bIsPresence, EOnlineComparisonOp::Equals);
            }

            TSharedRef<FOnlineSessionSearch> SearchSettingsRef = SessionSearch.ToSharedRef();

            // Set the Delegate to the Delegate Handle of the FindSession function
            OnFindSessionsCompleteDelegateHandle = Sessions->AddOnFindSessionsCompleteDelegate_Handle(OnFindSessionsCompleteDelegate);

            // Finally call the SessionInterface function. The Delegate gets called once this is finished
            Sessions->FindSessions(*UserId, SearchSettingsRef);
        }
    }
    else
    {
        // If something goes wrong, just call the Delegate Function directly with "false".
        OnFindSessionsComplete(false);
    }
}

void UNWGameInstance::OnFindSessionsComplete(bool bWasSuccessful)
{
    GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, FString::Printf(TEXT("OFindSessionsComplete bSuccess: %d"), bWasSuccessful));
    GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Blue, FString::Printf(TEXT("OFindSessionsComplete bSuccess: %d"), bWasSuccessful));
    GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Green, FString::Printf(TEXT("OFindSessionsComplete bSuccess: %d"), bWasSuccessful));
    GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, FString::Printf(TEXT("Ping Bucket size: %d"), SessionSearch->PingBucketSize));

    // Get OnlineSubsystem we want to work with
    IOnlineSubsystem* const OnlineSub = IOnlineSubsystem::Get();
    if (OnlineSub)
    {
        // Get SessionInterface of the OnlineSubsystem
        IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();
        if (Sessions.IsValid())
        {
            // Clear the Delegate handle, since we finished this call
            Sessions->ClearOnFindSessionsCompleteDelegate_Handle(OnFindSessionsCompleteDelegateHandle);

            // Just debugging the Number of Search results. Can be displayed in UMG or something later on
            GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, FString::Printf(TEXT("Num Search Results: %d"), SessionSearch->SearchResults.Num()));

            // If we have found at least 1 session, we just going to debug them. You could add them to a list of UMG Widgets, like it is done in the BP version!
            if (SessionSearch->SearchResults.Num() > 0)
            {
                // "SessionSearch->SearchResults" is an Array that contains all the information. You can access the Session in this and get a lot of information.
                // This can be customized later on with your own classes to add more information that can be set and displayed
                for (int32 SearchIdx = 0; SearchIdx < SessionSearch->SearchResults.Num(); SearchIdx++)
                {
                    // OwningUserName is just the SessionName for now. I guess you can create your own Host Settings class and GameSession Class and add a proper GameServer Name here.
                    // This is something you can't do in Blueprint for example!
                    GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, FString::Printf(TEXT("Session Number: %d | Sessionname: %s "), SearchIdx + 1, *(SessionSearch->SearchResults[SearchIdx].Session.OwningUserName)));
                }
            }
        }
    }
}

bool UNWGameInstance::JoinSession(TSharedPtr<const FUniqueNetId> UserId, FName SessionName, const FOnlineSessionSearchResult& SearchResult)
{
    // Return bool
    bool bSuccessful = false;

    // Get OnlineSubsystem we want to work with
    IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get();

    if (OnlineSub)
    {
        // Get SessionInterface from the OnlineSubsystem
        IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();

        if (Sessions.IsValid() && UserId.IsValid())
        {
            // Set the Handle again
            OnJoinSessionCompleteDelegateHandle = Sessions->AddOnJoinSessionCompleteDelegate_Handle(OnJoinSessionCompleteDelegate);

            // Call the "JoinSession" Function with the passed "SearchResult". The "SessionSearch->SearchResults" can be used to get such a
            // "FOnlineSessionSearchResult" and pass it. Pretty straight forward!
            bSuccessful = Sessions->JoinSession(*UserId, SessionName, SearchResult);
        }
    }

    return bSuccessful;
}

void UNWGameInstance::OnJoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result)
{
    GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, FString::Printf(TEXT("OnJoinSessionComplete %s, %d"), *SessionName.ToString(), static_cast<int32>(Result)));

    // Get the OnlineSubsystem we want to work with
    IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get();
    if (OnlineSub)
    {
        // Get SessionInterface from the OnlineSubsystem
        IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();

        if (Sessions.IsValid())
        {
            // Clear the Delegate again
            Sessions->ClearOnJoinSessionCompleteDelegate_Handle(OnJoinSessionCompleteDelegateHandle);

            // Get the first local PlayerController, so we can call "ClientTravel" to get to the Server Map
            // This is something the Blueprint Node "Join Session" does automatically!
            APlayerController * const PlayerController = GetFirstLocalPlayerController();

            // We need a FString to use ClientTravel and we can let the SessionInterface contruct such a
            // String for us by giving him the SessionName and an empty String. We want to do this, because
            // Every OnlineSubsystem uses different TravelURLs
            FString TravelURL;

            if (PlayerController && Sessions->GetResolvedConnectString(SessionName, TravelURL))
            {
                // Finally call the ClienTravel. If you want, you could print the TravelURL to see
                // how it really looks like
                PlayerController->ClientTravel(TravelURL, ETravelType::TRAVEL_Absolute);
            }
        }
    }
}

void UNWGameInstance::OnDestroySessionComplete(FName SessionName, bool bWasSuccessful)
{
    GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, FString::Printf(TEXT("OnDestroySessionComplete %s, %d"), *SessionName.ToString(), bWasSuccessful));

    // Get the OnlineSubsystem we want to work with
    IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get();
    if (OnlineSub)
    {
        // Get the SessionInterface from the OnlineSubsystem
        IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();

        if (Sessions.IsValid())
        {
            // Clear the Delegate
            Sessions->ClearOnDestroySessionCompleteDelegate_Handle(OnDestroySessionCompleteDelegateHandle);

            // If it was successful, we just load another level (could be a MainMenu!)
            if (bWasSuccessful)
            {
                UGameplayStatics::OpenLevel(GetWorld(), "MenuLevel", true);
            }
        }
    }
}

void UNWGameInstance::StartOnlineGame()
{
    UE_LOG(LogTemp, Log, TEXT("Inside Start Online Game"));
    // Creating a local player where we can get the UserID from
    ULocalPlayer* const Player = GetFirstGamePlayer();
    GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, FString::Printf(TEXT("Called StartOnlineGame")));

    // Call our custom HostSession function. GameSessionName is a GameInstance variable
    HostSession(Player->GetPreferredUniqueNetId(), "RedHot", true, true, 4);
}

void UNWGameInstance::FindOnlineGames()
{
    ULocalPlayer* const Player = GetFirstGamePlayer();

    FindSessions(Player->GetPreferredUniqueNetId(), true, true);
}

void UNWGameInstance::JoinOnlineGame()
{
    ULocalPlayer* const Player = GetFirstGamePlayer();

    // Just a SearchResult where we can save the one we want to use, for the case we find more than one!
    FOnlineSessionSearchResult SearchResult;

    // If the Array is not empty, we can go through it
    if (SessionSearch->SearchResults.Num() > 0)
    {
        for (int32 i = 0; i < SessionSearch->SearchResults.Num(); i++)
        {
            // To avoid something crazy, we filter sessions from ourself
            if (SessionSearch->SearchResults[i].Session.OwningUserId != Player->GetPreferredUniqueNetId())
            {
                SearchResult = SessionSearch->SearchResults[i];

                // Once we found sounce a Session that is not ours, just join it. Instead of using a for loop, you could
                // use a widget where you click on and have a reference for the GameSession it represents which you can use
                // here
                JoinSession(Player->GetPreferredUniqueNetId(), "RedHot", SearchResult);
                break;
            }
        }
    }
    else {
        UE_LOG(LogTemp, Log, TEXT("No session to join"));
    }
}

void UNWGameInstance::DestroySessionAndLeaveGame()
{
    IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get();
    if (OnlineSub)
    {
        IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();

        if (Sessions.IsValid())
        {
            Sessions->AddOnDestroySessionCompleteDelegate_Handle(OnDestroySessionCompleteDelegate);

            Sessions->DestroySession("RedHot");
        }
    }
}

You’d better test it in Launch game: Right click .uproject -> Launch game.

Reference:
https://forums.unrealengine.com/development-discussion/c-gameplay-programming/1445846-problem-findind-sessions-with-c-script

How to set customized GameSession Class

Reference
https://answers.unrealengine.com/questions/260999/creating-a-session-on-dedicated-server-launch.html

Docs Reference

Official Docs

Online Session Nodes
https://docs.unrealengine.com/en-US/Engine/Blueprints/UserGuide/OnlineNodes/index.html

How To Use Sessions In C++
https://wiki.unrealengine.com/How_To_Use_Sessions_In_C%2B%2B

YouTube Tutorials

Unreal Engine 4 | Server browser | Host & Join Session | SESSION SERIES
https://www.youtube.com/watch?v=NW4QHiK6YbQ

Steam Multiplayer - Advanced Steam Session - Unreal Engine
https://www.youtube.com/watch?v=ke3kmK6bvT0

Blueprint Multiplayer
https://www.youtube.com/watch?v=abmzWUWxy1U&list=PLZlv_N0_O1gYqSlbGQVKsRg6fpxWndZqZ
Blueprint Multiplayer: Game Instance | 03 | v4.11 Tutorial Series | Unreal Engine
https://www.youtube.com/watch?v=78XJYBfWXAA

[ue4] Setup Advanced Session - Steam Multiplayer
https://www.youtube.com/watch?v=rWs6SyyVpTE&list=PL3TrrCsmmxllnBvhucQ4c683a2VltXBx6

[UE4 - BP Tutorial] Multiplayer Session
https://www.youtube.com/watch?v=so0-0dafVsE&list=PLLiI_CRDNPL-FUUPcViHe6Ypabo1ghXh3


请原谅我不敢祝愿每一位毕业生都成功、都幸福;因为历史不幸地记载着:有人的成功代价是丧失良知;有人的幸福代价是损害他人。 ----饶毅教授2015北大毕业典礼演讲