Sessions and connections
A connection is one client's link to the server, as INetConnection.
A session is your name for a player, a value such as a Guid. The server doesn't tie the two
together by itself: your code binds a session to a connection when a player logs in, and unbinds
them when the player leaves. The session services keep that map, and the packet and send methods
below use it.
Turning it on
Call AddSessionManagement<TSessionId>() in a bootstrapper, or in Configure:
using BSGameLib.Server;
using Microsoft.Extensions.DependencyInjection;
public class Bootstrapper : IServiceBootstrapper
{
public void Configure(IServiceCollection services) => services.AddSessionManagement<Guid>();
}
This registers IConnectionRegistry<TSessionId> and
IConnectionManager<TSessionId> as singletons, for the session type you name. For
Guid, it also registers GuidSessionIdFactory as the
ISessionIdFactory<TSessionId>. For any other session type, register your own
ISessionIdFactory<TSessionId>.
Binding a session to a connection
IConnectionManager<TSessionId> changes the map. Bind throws if either the session or
the connection is already bound. TryBind returns false in that case instead. UnbindBySessionId
and UnbindByConnection remove a binding, and return false if there was none.
Nothing unbinds a connection when it drops, so unbind it yourself when the server reports a
disconnect. Subscribe to the adapter's events, as this example does in an IInitializer:
using BSGameLib.Networking;
using BSGameLib.Server;
public class SessionCleanup : IInitializer
{
private readonly IServerAdapter _adapter;
private readonly IConnectionManager<Guid> _sessions;
public SessionCleanup(IServerAdapter adapter, IConnectionManager<Guid> sessions)
{
_adapter = adapter;
_sessions = sessions;
}
public void Initialize()
{
_adapter.Disconnected += (_, e) => _sessions.UnbindByConnection(e.Client);
_adapter.ForciblyDisconnected += (_, e) => _sessions.UnbindByConnection(e.Client);
}
}
A login handler makes a session ID for the new connection, and binds it:
using BSGameLib.Networking;
using BSGameLib.Server;
public class LoginPacket : IDataPacket
{
public string Name { get; set; } = "";
}
public class LoginHandler(
ISessionIdFactory<Guid> ids,
IConnectionManager<Guid> sessions,
IListenSocket socket) : IDataPacketHandler<LoginPacket>
{
public void Handle(INetConnection connection, LoginPacket packet)
{
var sessionId = ids.Create(connection);
if (!sessions.TryBind(sessionId, connection))
return;
socket.SendReliableTo(connection, new LoginPacket { Name = packet.Name });
}
}
Looking up
IConnectionRegistry<TSessionId> answers the other way round:
| Member | Returns |
|---|---|
TryGetConnection(sessionId, out connection) |
true and the connection, if the session is bound. |
TryGetSessionId(connection, out sessionId) |
true and the session, if the connection is bound. |
GetAllBindings() |
Every session and its connection, as a list taken at that moment. |
ConnectionExtensions adds TryGet, which turns a connection into your own
object through a lookup function, and session-based versions of the send and disconnect methods.
The session versions do nothing when the session isn't bound. This handler sends a private message
to a session:
using BSGameLib.Networking;
using BSGameLib.Server;
public class WhisperPacket : IDataPacket
{
public Guid To { get; set; }
public string Message { get; set; } = "";
}
public class WhisperHandler(IListenSocket socket, IConnectionRegistry<Guid> sessions)
: IDataPacketHandler<WhisperPacket>
{
public void Handle(INetConnection connection, WhisperPacket packet) =>
socket.SendReliable(packet.To, sessions, packet);
}
Sending and disconnecting
IListenSocket sends packets and closes connections. The methods come in three delivery modes, each with a version for one connection, one for everyone, and one for everyone except a connection:
| Mode | To one connection | To everyone | To everyone but one |
|---|---|---|---|
| Reliable: ordered, and never lost | SendReliableTo |
SendReliableToAll |
SendReliableToAllBut |
| Unreliable: a datagram, which may be lost | SendUnreliableTo |
SendUnreliableToAll |
SendUnreliableToAllBut |
| Unreliable, sequenced: an older datagram that arrives late is dropped | SendUnreliableSequencedTo |
SendUnreliableSequencedToAll |
SendUnreliableSequencedToAllBut |
For the guarantees in detail, see Delivery modes.
The other members are:
Disconnect(connection, message)closes a connection. With a message, the client'sDisconnectedcarries it as itsDisconnectReason, and the close waits up to two seconds for the client to read it. Without one, the close is straight away.DisconnectWith(connection, packet, message)sends a last packet, then the message, and closes the connection once the client has read them, waiting up to two seconds. Use it when the client needs more than the message. ASendReliableTofollowed byDisconnectcan lose the packet.AllConnectionsis a snapshot of the open connections. It is updated as connections open and close.IsListeningtells whether the server is accepting connections.
The host starts and stops listening itself. Don't call StartListening or StopListening from
your code.