Skip to content

Transport Layer Architecture

Unturned's network transport layer abstracts the low-level connection and message delivery behind the ITransportConnection interface and paired IClientTransport / IServerTransport interfaces. There are five implementations: SteamNetworkingSockets (the default), SteamNetworking (legacy), SystemSockets (raw TCP/UDP fallback), Loopback (singleplayer dummy), and UNetLLAPI (deprecated Unity LLAPI). The NetTransportFactory selects the implementation at startup based on command-line arguments or defaults.

This article covers the transport interface, connection lifecycle, message framing, reliability semantics, the SteamNetworkingSockets configuration system, SystemSockets message layer framing, and the trade-offs between each implementation.

Source code location: NetTransport_SteamNetworkingSockets/, NetTransport_SteamNetworking/, NetTransport_SystemSockets/, NetTransport_Loopback/, NetTransport_UNetLLAPI/, Unturned/Provider/NetTransportFactory.cs

Transport Interface

The transport layer is split into three roles:

csharp
public interface ITransportConnection
{
    bool TryGetIPv4Address(out uint address);
    bool TryGetPort(out ushort port);
    bool TryGetSteamId(out ulong steamId);
    IPAddress GetAddress();
    string GetAddressString(bool withPort);
    void CloseConnection();
    void Send(byte[] buffer, long size, ENetReliability reliability);
}

ITransportConnection represents a single remote endpoint. It is a struct-like handle (for Loopback) or a class wrapper (for SNS) that identifies a specific peer. The Send() method delivers a pre-serialized byte buffer with a reliability hint. The TryGet* methods allow querying connection properties without throwing — useful when the transport doesn't support the query (e.g., Loopback has no IP address).

csharp
public interface IClientTransport
{
    void Send(byte[] buffer, long size, ENetReliability reliability);
}

public interface IServerTransport
{
    bool startListening();
    void stopListening();
}

IClientTransport is a simplified interface for the client-side send path. IServerTransport provides startListening() / stopListening() for the server's accept loop. When the server receives incoming data, it calls Provider.receiveTransportConnection() or Provider.receiveTransportData() directly — there is no IServerTransport.Receive() method because the servers receive path is a callback-based integration with the Provider.

ENetReliability

csharp
public enum ENetReliability
{
    Reliable,
    Unreliable
}

Transport implementations map this to their native reliability flags:

  • Reliable — guarantees in-order delivery and retransmission on loss. Used for critical state changes (spawn, inventory, damage, config).
  • Unreliable — fire-and-forget with no retransmission. Used for position updates, voice, and other loss-tolerant data where newer data supersedes older data.

The SNS implementation maps directly to Steam's constants:

csharp
internal int ReliabilityToSendFlags(ENetReliability reliability)
{
    switch (reliability)
    {
        default:
        case ENetReliability.Reliable:
            return Constants.k_nSteamNetworkingSend_Reliable;
        case ENetReliability.Unreliable:
            return Constants.k_nSteamNetworkingSend_Unreliable;
    }
}

NetTransportFactory Selection

NetTransportFactory selects the transport implementation at startup:

csharp
internal static IServerTransport CreateServerTransport()
{
    if (clImpl.hasValue)
    {
        string value = clImpl.value;
        if (string.Equals(value, "SystemSockets", StringComparison.OrdinalIgnoreCase))
            return new ServerTransport_SystemSockets();
        else if (string.Equals(value, "SteamNetworkingSockets", StringComparison.OrdinalIgnoreCase))
            return new ServerTransport_SteamNetworkingSockets();
        else if (string.Equals(value, "SteamNetworking", StringComparison.OrdinalIgnoreCase))
        {
            if (clBypassEnableOldSteamNetworking)
                return new ServerTransport_SteamNetworking();
            else
                UnturnedLog.warn("Old Steam networking is no longer supported.");
        }
        else
            UnturnedLog.warn("Unknown net transport implementation \"{0}\"", value);
    }
    return new ServerTransport_SteamNetworkingSockets(); // Default
}

Priority:

  1. -NetTransport command-line argument (values: "SystemSockets", "SteamNetworkingSockets", "SteamNetworking").
  2. Default: ServerTransport_SteamNetworkingSockets.

Old SteamNetworking requires -BypassEnableOldSteamNetworking flag.

For client transports:

csharp
internal static IClientTransport CreateClientTransport(string tag)
{
    if (string.Equals(tag, SystemSocketsTag, StringComparison.OrdinalIgnoreCase))
        return new ClientTransport_SystemSockets();
    else if (string.Equals(tag, SteamNetworkingSocketsTag, StringComparison.OrdinalIgnoreCase))
        return new ClientTransport_SteamNetworkingSockets();
    else if (string.Equals(tag, SteamNetworkingTag, StringComparison.OrdinalIgnoreCase))
        return new ClientTransport_SteamNetworking();
    else
        return new ClientTransport_SteamNetworkingSockets(); // Default
}

Tags: "sns" (SteamNetworkingSockets), "def" (legacy SteamNetworking), "sys" (SystemSockets). The server advertises its transport tag via SteamGameServer.SetGameTags().

SteamNetworkingSockets (SNS) — Default

TransportBase_SteamNetworkingSockets wraps Valve's SteamNetworkingSockets API (ISteamNetworkingSockets interface via Steamworks.NET).

Configuration

csharp
protected virtual List<SteamNetworkingConfigValue_t> BuildDefaultConfig()
{
    List<SteamNetworkingConfigValue_t> configList = new List<SteamNetworkingConfigValue_t>();

    if (clAllowWithoutAuth)
    {
        SteamNetworkingConfigValue_t allowWithoutAuth = new SteamNetworkingConfigValue_t();
        allowWithoutAuth.m_eDataType = ESteamNetworkingConfigDataType.k_ESteamNetworkingConfig_Int32;
        allowWithoutAuth.m_eValue = ESteamNetworkingConfigValue.k_ESteamNetworkingConfig_IP_AllowWithoutAuth;
        allowWithoutAuth.m_val.m_int32 = 1;
        configList.Add(allowWithoutAuth);
    }

    if (clSendBufferSize.hasValue && clSendBufferSize.value > 0)
    {
        SteamNetworkingConfigValue_t sendBufferSize = new SteamNetworkingConfigValue_t();
        sendBufferSize.m_eValue = ESteamNetworkingConfigValue.k_ESteamNetworkingConfig_SendBufferSize;
        sendBufferSize.m_val.m_int32 = clSendBufferSize.value;
        configList.Add(sendBufferSize);
    }

    // TimeoutInitial: 30 seconds
    SteamNetworkingConfigValue_t timeoutInitial = new SteamNetworkingConfigValue_t();
    timeoutInitial.m_eValue = ESteamNetworkingConfigValue.k_ESteamNetworkingConfig_TimeoutInitial;
    timeoutInitial.m_val.m_int32 = 30 * 1000;
    configList.Add(timeoutInitial);

    // TimeoutConnected: 30 seconds
    SteamNetworkingConfigValue_t timeoutConnected = new SteamNetworkingConfigValue_t();
    timeoutConnected.m_eValue = ESteamNetworkingConfigValue.k_ESteamNetworkingConfig_TimeoutConnected;
    timeoutConnected.m_val.m_int32 = 30 * 1000;
    configList.Add(timeoutConnected);

    return configList;
}

Key config values:

  • IP_AllowWithoutAuth-SNS_AllowWithoutAuth flag. When 1, UDP connections skip certificate authentication (LAN/development).
  • SendBufferSize-SNS_SendBufferSize value. Overrides the per-connection send buffer size.
  • EnableDiagnosticsUI-SNS_EnableDiagnosticsUI flag. Enables the SteamNetworkingSockets debug overlay.
  • TimeoutInitial — 30 seconds (in milliseconds). Connection establishment timeout.
  • TimeoutConnected — 30 seconds. If no data is received within this window, the connection is dropped.

Debug Output

csharp
protected FSteamNetworkingSocketsDebugOutput GetDebugOutputFunction()
{
    debugOutputFunc = OnDebugOutput;
    return debugOutputFunc;
}

private void OnDebugOutput(ESteamNetworkingSocketsDebugOutputType nType, IntPtr pszMsg)
{
    string message = InteropHelp.PtrToStringUTF8(pszMsg);
    if (!string.IsNullOrEmpty(message))
    {
        DebugOutput debugOutput = new DebugOutput();
        debugOutput.type = nType;
        debugOutput.message = message;
        debugOutputQueue.Enqueue(debugOutput);
    }
}

SNS debug output is captured via callback and stored in a thread-safe ConcurrentQueue<DebugOutput>. The detail level is controlled by -LogSteamNetworkingSockets (integer mapping to ESteamNetworkingSocketsDebugOutputType). Default is k_ESteamNetworkingSocketsDebugOutputType_None because verbose logging generates significant noise. Types include Bug, Error, Important, Warning, and misc.

The callback may be called from a service thread — it must be fast and thread-safe. The queue is drained on the game thread in the update loop via LogDebugOutput().

ServerTransport_SteamNetworkingSockets

ServerTransport_SteamNetworkingSockets implements IServerTransport and manages the listen socket:

csharp
public class ServerTransport_SteamNetworkingSockets : TransportBase_SteamNetworkingSockets, IServerTransport
{
    private HSteamListenSocket listenSocket;
    private Dictionary<HSteamNetConnection, TransportConnection_SteamNetworkingSockets> connections;

    public bool startListening()
    {
        SteamNetworkingIPAddr localAddress = new SteamNetworkingIPAddr();
        localAddress.Clear();
        localAddress.m_port = Provider.PORT;
        List<SteamNetworkingConfigValue_t> config = BuildDefaultConfig();
        listenSocket = SteamNetworkingSockets.CreateListenSocketIP(
            ref localAddress, config.Count, config.ToArray());
        return listenSocket != HSteamListenSocket.Invalid;
    }

    public void stopListening()
    {
        SteamNetworkingSockets.CloseListenSocket(listenSocket);
    }
}

The server processes incoming connection state changes in its update loop:

  1. Polls SteamNetworkingSockets.ReceiveMessagesOnListenSocket() for new connection requests.
  2. On k_ESteamNetworkingConnectionState_Connecting:
    • Checks Provider.hasRoomForNewConnection.
    • If space available, calls SteamNetworkingSockets.AcceptConnection().
    • If full, rejects with a busy status.
  3. On k_ESteamNetworkingConnectionState_Connected:
    • Creates a TransportConnection_SteamNetworkingSockets wrapping the HSteamNetConnection.
    • Calls Provider.receiveTransportConnection() to begin game-level auth.
  4. On k_ESteamNetworkingConnectionState_ClosedByPeer or k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
    • Removes the connection and notifies Provider.
  5. Reads incoming messages via SteamNetworkingSockets.ReceiveMessagesOnConnection() for each active connection, releasing SteamNetworkingMessage_t handles after dispatching data to Provider.receiveTransportData().

ClientTransport_SteamNetworkingSockets

csharp
public class ClientTransport_SteamNetworkingSockets : TransportBase_SteamNetworkingSockets, IClientTransport
{
    private HSteamNetConnection connection;
    private SteamNetworkingIPAddr remoteAddress;

    public void Connect(SteamNetworkingIPAddr address)
    {
        remoteAddress = address;
        List<SteamNetworkingConfigValue_t> config = BuildDefaultConfig();
        connection = SteamNetworkingSockets.ConnectByIPAddress(
            ref address, config.Count, config.ToArray());
    }

    public void Send(byte[] buffer, long size, ENetReliability reliability)
    {
        int sendFlags = ReliabilityToSendFlags(reliability);
        SteamNetworkingSockets.SendMessageToConnection(connection,
            buffer, (int)size, sendFlags, out long sent);
    }
}

The client polls connection state changes and routes received messages to Provider.receiveTransportData().

TransportConnection_SteamNetworkingSockets

The connection object wraps the HSteamNetConnection handle and implements ITransportConnection:

csharp
public class TransportConnection_SteamNetworkingSockets : ITransportConnection
{
    private HSteamNetConnection connection;

    public bool TryGetIPv4Address(out uint address)
    {
        SteamNetworkingSockets.GetConnectionInfo(connection, out SteamNetConnectionInfo_t info);
        address = info.m_addrRemote.GetIPv4();
        return address != 0;
    }

    public void Send(byte[] buffer, long size, ENetReliability reliability)
    {
        int sendFlags = ReliabilityToSendFlags(reliability);
        SteamNetworkingSockets.SendMessageToConnection(connection, buffer, (int)size, sendFlags, out _);
    }

    public void CloseConnection()
    {
        SteamNetworkingSockets.CloseConnection(connection, 0, null, false);
    }
}

The TryGetIPv4Address() method queries the connection info to retrieve the remote address. Send() delegates directly to SteamNetworkingSockets, which handles fragmentation, retransmission (for reliable), and congestion control internally.

SteamNetworking (Legacy)

TransportBase_SteamNetworking is the legacy P2P transport using SteamNetworking (deprecated peer-to-peer system). It uses:

  • SteamNetworking.SendP2PPacket() with k_EP2PSendReliable for reliable delivery.
  • SteamNetworking.SendP2PPacket() with k_EP2PSendUnreliable for unreliable.
  • SteamNetworking.ReadP2PPacket() for receive.

Known issues:

  • No congestion control — can cause packet flooding under load.
  • No connection health metrics.
  • Limited to ~1200 bytes per packet (Steam P2P message size limit).
  • Deprecated by Valve (note: not to be confused with SteamNetworkingSockets, which is the actively supported replacement).

The base class is minimal — just a conditional logging decorator:

csharp
public abstract class TransportBase_SteamNetworking
{
    [Conditional("LOG_NETTRANSPORT_STEAMNETWORKING")]
    internal static void Log(string format, params object[] args)
    {
        UnturnedLog.info(format, args);
    }
}

Most logic is in the client/server subclasses. Disabled by default; requires -BypassEnableOldSteamNetworking to instantiate.

SystemSockets

TransportBase_SystemSockets wraps .NET's System.Net.Sockets for raw TCP/UDP without Steam dependency. Used for LAN servers or non-Steam environments.

SocketMessageLayer

SocketMessageLayer handles framing and connection tracking:

Message framing: Each message is prefixed with a 4-byte length header (big-endian int32). The receiver reads the header first, then the payload bytes:

[4 bytes: payload length][N bytes: payload]

Fragmentation: Messages larger than the socket's send buffer are split into chunks on send and reassembled on receive. The layer maintains per-connection receive buffers to handle partial reads.

Connection tracking: Maintains a Dictionary<EndPoint, TransportConnection_SystemSockets> mapping endpoints to connection objects. Connections are identified by their remote endpoint.

The SystemSockets implementation is single-threaded (runs on Unity's main thread), so all socket operations must be non-blocking or use Socket.Poll() with a short timeout.

ServerTransport_SystemSockets

csharp
public class ServerTransport_SystemSockets : TransportBase_SystemSockets, IServerTransport
{
    private TcpListener tcpListener;
    private SocketMessageLayer messageLayer;
    private Dictionary<EndPoint, TransportConnection_SystemSockets> connections;

    public bool startListening()
    {
        tcpListener = new TcpListener(IPAddress.Any, Provider.PORT);
        tcpListener.Start();
        return true;
    }

    public void stopListening()
    {
        tcpListener.Stop();
        foreach (var conn in connections.Values)
            conn.CloseConnection();
    }
}

The server's update loop:

  1. Accepts pending TCP connections via tcpListener.AcceptTcpClient() (non-blocking with Pending() check).
  2. Creates a TransportConnection_SystemSockets for each new client.
  3. Polls each connection's socket for available data via Socket.Poll().
  4. Reads length-prefixed frames via messageLayer.Receive().
  5. Dispatches complete frames to Provider.receiveTransportData().

SocketMessageLayer Internals

The SocketMessageLayer is the framing layer for SystemSockets:

Send path:

  1. Prepend a 4-byte big-endian length header to the payload.
  2. If payload + header exceeds the socket's send buffer, split into chunks.
  3. Queue chunks for async send via Socket.BeginSend().

Receive path:

  1. Read 4 bytes for the length header.
  2. Read the specified number of bytes for the payload.
  3. If the read is partial (TCP can fragment), buffer the partial data until the full message arrives.
  4. Dispatch the complete message to the registered handler.

Connection tracking:

csharp
public class SocketMessageLayer
{
    private Dictionary<EndPoint, ReceiveState> receiveBuffers;

    private class ReceiveState
    {
        public byte[] buffer;
        public int expectedLength;
        public int receivedSoFar;
        public bool readingHeader; // true = reading 4-byte length, false = reading payload
    }
}

Each connection has a ReceiveState tracking partial reads. The state machine toggles between readingHeader (4 bytes) and reading payload (expectedLength bytes). This ensures correct reassembly even when TCP segments arrive in arbitrary sizes.

Connection Flow

  • Server: startListening() creates a TcpListener on the configured port. Incoming connections are accepted in the update loop and added to the connection map.
  • Client: Connects via TcpClient.ConnectAsync() (to avoid blocking the game thread).
  • Send: Writes length-prefixed frames to the socket stream via Socket.Send().
  • Receive: Polls all connections for available data, reads frames from SocketMessageLayer.Receive(), and dispatches to Provider.receiveTransportData().

Loopback

TransportConnection_Loopback is a dummy connection struct for singleplayer:

csharp
public struct TransportConnection_Loopback : ITransportConnection
{
    public static TransportConnection_Loopback Create()
    {
        return new TransportConnection_Loopback(++counter);
    }

    public bool TryGetIPv4Address(out uint address)
    {
        address = default;
        return false;
    }

    public bool TryGetSteamId(out ulong steamId)
    {
        steamId = default;
        return false;
    }

    public void Send(byte[] buffer, long size, ENetReliability reliability)
    {
        throw new System.NotSupportedException();
    }

    public void CloseConnection() { }

    public override bool Equals(object obj)
    {
        return obj is TransportConnection_Loopback && id == ((TransportConnection_Loopback)obj).id;
    }
}

All methods are no-ops except Send() which throws NotSupportedException — loopback messages are handled through the net invokable system's direct call path, not through the transport layer.

The loopback connection is created via TransportConnection_Loopback.Create(), which increments a static counter and assigns a unique integer ID. Equality is based on this ID. The DedicatedServerLoopback singleton is used by dedicated servers for the host player (though dedicated servers typically have no local player).

Key design insight: singleplayer never uses the transport layer for message delivery. Instead, ClientStaticMethod.InvokeAndLoopback() directly calls the receive method on the calling thread. The loopback connection exists only to satisfy the ITransportConnection interface requirement for SteamPlayer construction.

UNetLLAPI (Deprecated)

Wraps Unity's deprecated NetworkTransport LLAPI:

  • ClientTransport_UNetLLAPI — connects via NetworkTransport.Connect() with a connection config.
  • ServerTransport_UNetLLAPI — listens via NetworkTransport.AddHost().
  • TransportConnection_UNetLLAPI — wraps the connection ID and host ID tuple.

This transport was used in early Unturned versions but is now superseded. It remains in the codebase for historical reference but is not selected by NetTransportFactory.

Trade-offs Summary

TransportSteam RequiredAuthEncryptionCongestion CtrlMax PacketFragmentation
SteamNetworkingSocketsYesCert-based (optional)DTLSYesUnlimitedBuilt-in
SteamNetworkingYesP2P sessionSteam P2PNo~1200 bytesApplication-level
SystemSocketsNoNoneNoneTCP onlyUnlimitedSocketMessageLayer
LoopbackNoN/AN/AN/AN/AN/A
UNetLLAPINoNoneNoneUnity LLAPI~1400 bytesUnity LLAPI

SteamNetworkingSockets is the recommended transport for all deployments. SystemSockets should be used only for LAN or non-Steam environments. The legacy SteamNetworking transport is retained only for backwards compatibility.

Selection Criteria

  • Public internet servers: SteamNetworkingSockets (DTLS encryption, congestion control, Steam auth).
  • LAN parties without internet access: SystemSockets (no Steam dependency, direct TCP connection).
  • Singleplayer: Loopback (no transport overhead, direct method invocation).
  • Development/testing: SystemSockets or SteamNetworkingSockets with -SNS_AllowWithoutAuth.

Transport Selection Examples

Unturned_Headless.x86_64 -NetTransport SystemSockets -BindAny
  → Uses raw TCP sockets, binds to all interfaces (LAN server).

Unturned_Headless.x86_64 -NetTransport SteamNetworkingSockets
  → Uses SteamNetworkingSockets (default, but explicit).

Unturned_Headless.x86_64 -SNS_SendBufferSize 131072
  → SteamNetworkingSockets with 128KB send buffer.

Unturned_Headless.x86_64 -SNS_AllowWithoutAuth
  → SteamNetworkingSockets without certificate auth (development).

Connection Lifecycle Detail

Server Connection Acceptance

When a new transport connection is established, the server follows this sequence:

  1. IServerTransport receives the connection event from the transport implementation.
  2. Provider.receiveTransportConnection(transportConnection) is called.
  3. A new SteamPending entry is created and added to the pending list.
  4. The pending player's _transportConnectionToPendingPlayerMap entry is set.
  5. If the server is full (clients + pending >= maxPlayers + queueSize), the connection is immediately closed.
  6. If the server is not full, the pending player stays in the queue until verifyNextPlayerInQueue() promotes them.

Connection Termination

Connections can terminate in several ways:

  1. Graceful disconnect: Client sends EServerMessage.GracefullyDisconnect. Server removes the player immediately.
  2. Timeout: No data received for CLIENT_TIMEOUT (30 seconds). Server detects the missing pings and removes the player.
  3. Transport failure: ITransportConnection.CloseConnection() is called. On SNS, this calls SteamNetworkingSockets.CloseConnection(). On SystemSockets, this closes the TCP socket.
  4. Server shutdown: Provider.shutdown() closes all connections and stops the transport listener.

Message Flow Diagram

Client (IClientTransport)                     Server (IServerTransport)
        │                                            │
        │── [Unreliable] PositionUpdate ──────────►  │
        │── [Reliable] EquipRequest ──────────────►  │
        │                                            │── Process, generate response
        │◄── [Reliable] EquipConfirmed ────────────  │
        │◄── [Reliable] HealthChanged ─────────────  │
        │◄── [Unreliable] OtherPlayerPosition ─────  │

The transport layer is agnostic to the message content — it only delivers byte buffers with a reliability hint. All message structure is handled by the NetMessaging and NetInvokable layers above.

Command-Line Flags Summary

FlagTransportEffect
-NetTransport SystemSocketsSystemSocketsUse raw TCP sockets instead of SteamNetworkingSockets
-NetTransport SteamNetworkingSocketsSNSExplicitly use SteamNetworkingSockets (default)
-NetTransport SteamNetworkingLegacy SNetUse deprecated P2P networking (requires -BypassEnableOldSteamNetworking)
-BypassEnableOldSteamNetworkingLegacy SNetAllow using old Steam networking transport
-SNS_AllowWithoutAuthSNSDisable certificate authentication for UDP connections
-SNS_SendBufferSize NSNSOverride the per-connection send buffer size
-SNS_EnableDiagnosticsUISNSEnable the SteamNetworkingSockets debug overlay
-LogSteamNetworkingSockets NSNSSet SNS debug output detail level (0=None, 1=Warning, 2=Important, 3=Error, 4=Bug)
-LogBadMessagesAllLog malformed or unexpected message indices

Security Considerations

SNS Certificate Authentication

By default, SteamNetworkingSockets uses DTLS with certificate authentication. Each peer presents a certificate signed by the Steam certificate authority. This provides:

  • Connection authentication (both sides verify identity).
  • Encryption of all traffic (protects against traffic analysis and packet injection).
  • Protection against man-in-the-middle attacks.

The -SNS_AllowWithoutAuth flag disables certificate checking for LAN and development environments.

SystemSockets No Encryption

SystemSockets sends data as raw TCP with no encryption or authentication. This is suitable for:

  • LAN servers where the network is trusted.
  • Development/testing environments.
  • Not suitable for internet-facing servers without additional encryption (VPN, IP whitelisting).

Legacy SteamNetworking P2P

Uses Steam P2P encryption but has known security limitations:

  • No denial-of-service protection for connection floods.
  • Limited connection state validation.
  • Relies on Steam's P2P relay for NAT traversal (adds latency).

Port Configuration

The server uses three ports:

  1. Game port (default 27015): The transport listening port. Used for game traffic.
  2. Steam query port (default 27016): Used by Steam's A2S server query protocol. Set via SteamGameServer.SetQueryPort().
  3. Steam master port (default 27017): Used for communication with Steam's master server list.

The CommandPort command can override the game port at runtime. The CommandBind command binds the server to specific IP addresses.

Multi-Home Support

The server can bind to specific IP addresses via CommandBind. This is used for servers with multiple network interfaces (e.g., a server with both a public IP and a LAN IP). The transport layer binds to the specified address, and SteamGameServer is configured to advertise the correct external address for the server browser listing.

Transport Layer Initialization Sequence

At server startup, the transport initialization follows this order:

  1. NetTransportFactory.CreateServerTransport() instantiates the selected transport.
  2. serverTransport.startListening() opens the socket/listener.
  3. If SNS: calls SteamNetworkingSockets.CreateListenSocketIP() with default config values.
  4. If SystemSockets: creates TcpListener on the server port.
  5. The transport polls for new connections and incoming data each frame via ServerTransportUpdate().
  6. Received data is dispatched to Provider.receiveTransportData(connection, buffer, size).
  7. Provider.receiveTransportData() reads the message enum and dispatches to the appropriate handler via NetMessages.ReceiveMessageFromClient().

On the client side:

  1. NetTransportFactory.CreateClientTransport(tag) reads the server's advertised transport tag.
  2. clientTransport.Connect(address) initiates connection.
  3. The client polls for connection state changes and received data each frame.
  4. Received data is dispatched to Provider.receiveTransportData()NetMessages.ReceiveMessageFromServer().

Transport Connection Pooling

The TransportConnectionListPool provides pooled lists for sending to multiple clients:

csharp
public static class TransportConnectionListPool
{
    public static PooledTransportConnectionList Get();
    public static void Release(PooledTransportConnectionList list);
}

Lists are obtained from the pool, populated with target connections, and released after the send completes. This avoids per-frame allocations for broadcast operations. Pooled lists are used by Provider.GatherClientConnections() and the various filtered gathering methods.

Error Handling Patterns

The transport layer uses consistent error handling:

  1. Send failures: Caught at the transport level. If SendMessageToConnection() fails in SNS, the error is logged and the connection is flagged for removal.
  2. Receive failures: Malformed packets (invalid message enum) result in the sending connection being terminated via Provider.refuseGarbageConnection().
  3. Connection drops: Handled via the connection state change callback in SNS, or socket exception in SystemSockets. The Provider is notified to clean up player state.
  4. Listen socket failures: If CreateListenSocketIP() fails, the server logs an error and shuts down.
  5. Buffer overflow: The NetPak writer's EErrorFlags.BufferOverflow is checked after each message write. Development builds log this condition; release builds silently discard the message.