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:
-NetTransportcommand-line argument (values:"SystemSockets","SteamNetworkingSockets","SteamNetworking").- 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_AllowWithoutAuthflag. When 1, UDP connections skip certificate authentication (LAN/development).SendBufferSize—-SNS_SendBufferSizevalue. Overrides the per-connection send buffer size.EnableDiagnosticsUI—-SNS_EnableDiagnosticsUIflag. 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:
- Polls
SteamNetworkingSockets.ReceiveMessagesOnListenSocket()for new connection requests. - On
k_ESteamNetworkingConnectionState_Connecting:- Checks
Provider.hasRoomForNewConnection. - If space available, calls
SteamNetworkingSockets.AcceptConnection(). - If full, rejects with a busy status.
- Checks
- On
k_ESteamNetworkingConnectionState_Connected:- Creates a
TransportConnection_SteamNetworkingSocketswrapping theHSteamNetConnection. - Calls
Provider.receiveTransportConnection()to begin game-level auth.
- Creates a
- On
k_ESteamNetworkingConnectionState_ClosedByPeerork_ESteamNetworkingConnectionState_ProblemDetectedLocally:- Removes the connection and notifies Provider.
- Reads incoming messages via
SteamNetworkingSockets.ReceiveMessagesOnConnection()for each active connection, releasingSteamNetworkingMessage_thandles after dispatching data toProvider.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()withk_EP2PSendReliablefor reliable delivery.SteamNetworking.SendP2PPacket()withk_EP2PSendUnreliablefor 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:
- Accepts pending TCP connections via
tcpListener.AcceptTcpClient()(non-blocking withPending()check). - Creates a
TransportConnection_SystemSocketsfor each new client. - Polls each connection's socket for available data via
Socket.Poll(). - Reads length-prefixed frames via
messageLayer.Receive(). - Dispatches complete frames to
Provider.receiveTransportData().
SocketMessageLayer Internals
The SocketMessageLayer is the framing layer for SystemSockets:
Send path:
- Prepend a 4-byte big-endian length header to the payload.
- If payload + header exceeds the socket's send buffer, split into chunks.
- Queue chunks for async send via
Socket.BeginSend().
Receive path:
- Read 4 bytes for the length header.
- Read the specified number of bytes for the payload.
- If the read is partial (TCP can fragment), buffer the partial data until the full message arrives.
- 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 aTcpListeneron 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 toProvider.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 viaNetworkTransport.Connect()with a connection config.ServerTransport_UNetLLAPI— listens viaNetworkTransport.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
| Transport | Steam Required | Auth | Encryption | Congestion Ctrl | Max Packet | Fragmentation |
|---|---|---|---|---|---|---|
| SteamNetworkingSockets | Yes | Cert-based (optional) | DTLS | Yes | Unlimited | Built-in |
| SteamNetworking | Yes | P2P session | Steam P2P | No | ~1200 bytes | Application-level |
| SystemSockets | No | None | None | TCP only | Unlimited | SocketMessageLayer |
| Loopback | No | N/A | N/A | N/A | N/A | N/A |
| UNetLLAPI | No | None | None | Unity LLAPI | ~1400 bytes | Unity 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:
IServerTransportreceives the connection event from the transport implementation.Provider.receiveTransportConnection(transportConnection)is called.- A new
SteamPendingentry is created and added to the pending list. - The pending player's
_transportConnectionToPendingPlayerMapentry is set. - If the server is full (clients + pending >= maxPlayers + queueSize), the connection is immediately closed.
- 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:
- Graceful disconnect: Client sends
EServerMessage.GracefullyDisconnect. Server removes the player immediately. - Timeout: No data received for
CLIENT_TIMEOUT(30 seconds). Server detects the missing pings and removes the player. - Transport failure:
ITransportConnection.CloseConnection()is called. On SNS, this callsSteamNetworkingSockets.CloseConnection(). On SystemSockets, this closes the TCP socket. - 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
| Flag | Transport | Effect |
|---|---|---|
-NetTransport SystemSockets | SystemSockets | Use raw TCP sockets instead of SteamNetworkingSockets |
-NetTransport SteamNetworkingSockets | SNS | Explicitly use SteamNetworkingSockets (default) |
-NetTransport SteamNetworking | Legacy SNet | Use deprecated P2P networking (requires -BypassEnableOldSteamNetworking) |
-BypassEnableOldSteamNetworking | Legacy SNet | Allow using old Steam networking transport |
-SNS_AllowWithoutAuth | SNS | Disable certificate authentication for UDP connections |
-SNS_SendBufferSize N | SNS | Override the per-connection send buffer size |
-SNS_EnableDiagnosticsUI | SNS | Enable the SteamNetworkingSockets debug overlay |
-LogSteamNetworkingSockets N | SNS | Set SNS debug output detail level (0=None, 1=Warning, 2=Important, 3=Error, 4=Bug) |
-LogBadMessages | All | Log 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:
- Game port (default 27015): The transport listening port. Used for game traffic.
- Steam query port (default 27016): Used by Steam's A2S server query protocol. Set via
SteamGameServer.SetQueryPort(). - 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:
NetTransportFactory.CreateServerTransport()instantiates the selected transport.serverTransport.startListening()opens the socket/listener.- If SNS: calls
SteamNetworkingSockets.CreateListenSocketIP()with default config values. - If SystemSockets: creates
TcpListeneron the server port. - The transport polls for new connections and incoming data each frame via
ServerTransportUpdate(). - Received data is dispatched to
Provider.receiveTransportData(connection, buffer, size). Provider.receiveTransportData()reads the message enum and dispatches to the appropriate handler viaNetMessages.ReceiveMessageFromClient().
On the client side:
NetTransportFactory.CreateClientTransport(tag)reads the server's advertised transport tag.clientTransport.Connect(address)initiates connection.- The client polls for connection state changes and received data each frame.
- 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:
- Send failures: Caught at the transport level. If
SendMessageToConnection()fails in SNS, the error is logged and the connection is flagged for removal. - Receive failures: Malformed packets (invalid message enum) result in the sending connection being terminated via
Provider.refuseGarbageConnection(). - 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.
- Listen socket failures: If
CreateListenSocketIP()fails, the server logs an error and shuts down. - Buffer overflow: The NetPak writer's
EErrorFlags.BufferOverflowis checked after each message write. Development builds log this condition; release builds silently discard the message.
