Hello, I am writing to ask a question because I encountered a problem while testing the Distributed Authority example.
In the example, I successfully connected to the multiplayer server by entering the Session Name and Player Name and pressing the Connect button.
Then, while connected to the multiplayer server, I disconnected the connection by pressing the Disconnect button or exiting the Editor Play mode. Afterward, I entered the exact same Session Name as before and pressed Connect. However, doing so occasionally resulted in the warning “[Multiplayer]: GameObjectsNetcodeNetworkHandler.StopAsync: Failed to stop session: session was never started.” (This occurred very rarely when I tried changing the SessionName.)
Even if I attempted to connect again, the player prefab was not created, accompanied by the warning “[Multiplayer]: NetworkManagerSession.StartAsync: Netcode.NetworkManager is already connected.”
How can I resolve this error?
I downloaded the example via MultiplayerCenter’s Quitstart → Hosting → Create and open scene with distributed authority setup.
For reference, I have not touched any code or settings in the example.
Below is my package version. MultiplayerCenter: 1.0.1 MultiplierPlayMode: 2.0.2 Multiplayer Services: 2.2.1 Netcode for GameObjects: 2.11.0 UnityTransport: 2.7.2
All of the services code runs asynchronous tasks and you should take this into consideration if you expect:
Clients will start and stop a session before it has created.
The back-end services could still be in the middle of spinning up the session when you abruptly disconnect.
If this happens, then the service will still think you are in the middle of connecting to a session.
Whether you want your users to be able to do this is really based on what your project’s goals are.
If you want to prevent a user from doing this, then you can disable any disconnect button the user might have access to in order to reduce this kind of scenario from happening.
Your users won’t have access to play mode or the like, so preventing NGO users (i.e. you) from doing this becomes a bit more problematic as the connection depends upon whether you are using the exact same profile name and session name to try to reconnect. You should also contemplate that there is a resource cost to spinning up a distributed authority service instance which there are mechanisms in place to prevent someone from (starting-stopping, starting-stopping, repeat that (n) times).
Understanding connection time-outs:
When you establish a connection (whether using NGO or something else… say opening a TCP socket…) if there is an ungraceful disconnect (i.e. closing out the application, exit play mode while in the middle of a session, exit play mode while in the middle of connecting, etc) then the service has a timeout period before it considers the connection “closed”.
Part of what you might be experiencing is this.
My recommendations for handling this scenario:
Try to avoid allowing your users an easy way to ungracefully disconnect.
i.e. if you have a disconnect button…disable it until the session is established or you get some other error connecting to the session.
Try to avoid allowing users to use the same lobby name.
While you can still provide users with the ability to create a name, you can also assign a unique identifier to the end of the name (could be as simple as a global counter) so each session created uses a unique name (relative to the user connecting) in order to handle the edge cases (i.e. a user starts to connect and then their internet goes down briefly or the like) where the session could be in the middle of spinning up when the user attempts to create the same session name again.
Optionally, you can do lobby searches for distributed authority sessions and allow users to connect that way as opposed to entering in a specific session name.
When you use the create or join session option, you will need to have some way for the players to connect to the session (i.e. they need to know the name of the session before attempting to connect). While there are ways to handle this (i.e. you have your own way of handling this process) =or= you have your users search for a session name in existing sessions…
Using an explicit name can yield conflicts (i.e. two users want to start their own session but use the exact same name).
Using lobby searching based on a session name and then creating a unique session name (i.e. the original name + some identifier) if a session containing that name does not exist…then you would use the create or join session path to create a session.
Whether you decide to leave the session name creation up to your script (i.e. auto-generate a name and create a session from that) or your users is really dependent upon which of the two meets your project’s goals (both paths lead to the same outcome…one path, you allow users to create their own names, is trickier to implement than the other…auto-generating so you don’t generate the same session name from within the same application instance).
Let me know if you have more questions or if the information provided above resolves your issue?
As I understand it, the issue occurred because the service did not recognize when a user terminated the connection abnormally. I understood that one way to resolve this is to create a session with a different name than the previously created session.
Therefore, I modified the “CreateOrJoinSessionAsync()” function to change the profile name in the AuthenticationService and changed the settings to create sessions automatically. (I am considering requiring other players to enter a code to join a room.)
using System;
using System.Threading.Tasks;
using Unity.Netcode;
using Unity.Services.Authentication;
using Unity.Services.Core;
using Unity.Services.Multiplayer;
using UnityEngine;
using Debug = UnityEngine.Debug;
namespace Unity.Multiplayer.Center.NetcodeForGameObjectsExample.DistributedAuthority
{
/// <summary>
/// Handles the connection to the Distributed Authority Backend Service.
/// If you want to modify this Script please copy it into your own project and add it to your Player Prefab.
/// </summary>
public class ConnectionManager : MonoBehaviour
{
[SerializeField]
int m_MaxPlayers = 10;
NetworkManager m_NetworkManager;
ISession m_Session;
/// <summary>
/// Status of the Connection.
/// </summary>
public ConnectionState State { get; private set; } = ConnectionState.Disconnected;
/// <summary>
/// The different states the connection can be in.
/// </summary>
public enum ConnectionState
{
Disconnected,
Connecting,
Connected,
}
async void Awake()
{
// Find the NetworkManager in the Scene
m_NetworkManager = FindFirstObjectByType<NetworkManager>();
m_NetworkManager.OnSessionOwnerPromoted += OnSessionOwnerPromoted;
m_NetworkManager.OnClientConnectedCallback += OnClientConnectedCallback;
await UnityServices.InitializeAsync();
}
/// <summary>
/// Leaves the current session and sets the <see cref="State"/> to Disconnected.
/// </summary>
public async void Disconnect()
{
if (m_Session != null)
{
await m_Session.LeaveAsync();
}
State = ConnectionState.Disconnected;
}
/// <summary>
/// Creates or joins an existing session.
/// </summary>
/// <param name="sessionName">The name of the session to join or create</param>
/// <param name="profileName">The name of the player</param>
public async Task CreateOrJoinSessionAsync(string sessionName, string profileName)
{
if (string.IsNullOrEmpty(profileName) || string.IsNullOrEmpty(sessionName))
{
Debug.LogError("Please provide a player and session name, to login.");
return;
}
string profileNameWithGuid = $"{profileName}_{Guid.NewGuid().ToString()[..8]}";
State = ConnectionState.Connecting;
try
{
AuthenticationService.Instance.SignOut(true);
AuthenticationService.Instance.SwitchProfile(profileNameWithGuid);
await AuthenticationService.Instance.SignInAnonymouslyAsync();
Debug.Log($"Signed in as {profileNameWithGuid}");
// Set the session options.
var options = new SessionOptions()
{
MaxPlayers = m_MaxPlayers
}.WithDistributedAuthorityNetwork();
// Join a session if it already exists, or create a new one.
m_Session = await MultiplayerService.Instance.CreateSessionAsync(options);
Debug.Log($"Created session {m_Session.Id}");
State = ConnectionState.Connected;
}
catch (Exception e)
{
State = ConnectionState.Disconnected;
Debug.LogException(e);
}
}
// Just for logging.
void OnClientConnectedCallback(ulong clientId)
{
if (m_NetworkManager.LocalClientId == clientId)
{
Debug.Log($"Client-{clientId} is connected and can spawn {nameof(NetworkObject)}s.");
}
}
// Just for logging.
void OnSessionOwnerPromoted(ulong sessionOwnerPromoted)
{
if (m_NetworkManager.LocalClient.IsSessionOwner)
{
Debug.Log($"Client-{m_NetworkManager.LocalClientId} is the session owner!");
}
}
}
}
However, even after changing the code, the error occurs similarly to before upon reconnecting, and the following new message also appeared:
[Wire - ws#]: [2026-04-23 1:38:26 PM] FATAL <>c__DisplayClass169_0.b__2:2577 The header part of a frame could not be read. UnityEngine.Logger:Log (string,object)
UnityWebSocketSharp.Logger:defaultOutput (UnityWebSocketSharp.LogData,string) (at ./Library/PackageCache/com.unity.services.wire@d50817c0adab/Runtime/websocket-sharp/Logger.cs:231)
UnityWebSocketSharp.Logger:output (string,UnityWebSocketSharp.LogLevel) (at ./Library/PackageCache/com.unity.services.wire@d50817c0adab/Runtime/websocket-sharp/Logger.cs:249)
UnityWebSocketSharp.Logger:Fatal(string)(at ./Library/PackageCache/com.unity.services.wire@d50817c0adab/Runtime/websocket-sharp/Logger.cs:316)
UnityWebSocketSharp.WebSocket/<>c__DisplayClass169_0:b__2 (System.Exception) (at ./Library/PackageCache/com.unity.services.wire@d50817c0adab/Runtime/websocket-sharp/WebSocket.cs:2577)
UnityWebSocketSharp.Ext/<>c__DisplayClass51_0:b__0 (System.IAsyncResult) (at ./Library/PackageCache/com.unity.services.wire@d50817c0adab/Runtime/websocket-sharp/Ext.cs:812)
System.Runtime.CompilerServices.AsyncTaskMethodBuilder1<int>:SetResult (int) Mono.Net.Security.MobileAuthenticatedStream/<StartOperation>d__57:MoveNext() System.Runtime.CompilerServices.AsyncTaskMethodBuilder1<Mono.Net.Security.AsyncProtocolResult>:SetResult (Mono.Net.Security.AsyncProtocolResult)
Mono.Net.Security.AsyncProtocolRequest/d__23:MoveNext()
System.Runtime.CompilerServices.AsyncTaskMethodBuilder:SetResult ()
Mono.Net.Security.AsyncProtocolRequest/d__24:MoveNext()
System.Runtime.CompilerServices.AsyncTaskMethodBuilder1<System.Nullable1>:SetResult (System.Nullable1<int>) Mono.Net.Security.AsyncProtocolRequest/<InnerRead>d__25:MoveNext() System.Runtime.CompilerServices.AsyncTaskMethodBuilder1:SetResult (int)
Mono.Net.Security.MobileAuthenticatedStream/d__66:MoveNext()
System.Threading._ThreadPoolWaitCallback:PerformWaitCallback()