Network Application¶
Nalix.Hosting provides a Microsoft-style builder and host for Nalix servers. It simplifies the setup of protocols, listeners, dispatchers, and dependency injection into a single fluent flow.
Why use the Hosting Builder?¶
While you can instantiate listeners and protocols manually, the NetworkApplicationBuilder is highly recommended for production applications.
- Unified Lifecycle: Ensures that the memory pool, handler registry, dispatcher, and listeners are activated and deactivated in the correct order.
- Automatic Service Injection: Automatically registers critical shared services (Logger, ConnectionHub, BufferPool) into the
InstanceManager. - Handler Discovery: Scans assemblies for
[PacketController]classes and performs high-performance Handler Compilation (Expression Trees) to eliminate reflection overhead. - Coexistence: Easily manages multiple listeners (e.g., TCP and UDP) within the same application process.
The public API surface revolves around two main types:
NetworkApplicationis the runnable host.INetworkApplicationBuilderis the fluent configuration contract.
Source mapping¶
src/Nalix.Hosting/NetworkApplication.cssrc/Nalix.Hosting/INetworkApplicationBuilder.cssrc/Nalix.Hosting/NetworkApplicationBuilder.cs
Startup Flow¶
graph LR
subgraph Configuration ["Phase 1: Configuration"]
Create["CreateBuilder()"] --> Config["Configure Loggers, Options, Hubs"]
Config --> Discover["ScanPackets()/AddPacketNamespace() & ScanHandlers()"]
Discover --> Bind["BindTcp() / BindUdp()"]
end
subgraph Composition ["Phase 2: Composition"]
Bind --> Build["Build()"]
Build --> Prep["Initialize Context"]
end
subgraph Activation ["Phase 3: Activation"]
Prep --> Services["Register logger, options, registry, hub, buffers, identity"]
Services --> Dispatch["Create and activate packet dispatch"]
Dispatch --> Listeners["Create and start TCP/UDP listeners"]
Listeners --> Hosted["Activate hosted services"]
end
Public members at a glance¶
| Type | Public members |
|---|---|
NetworkApplication |
CreateBuilder(), ActivateAsync(...), DeactivateAsync(...), RunAsync(...), Dispose() |
INetworkApplicationBuilder |
ConfigureLogging(...), ConfigureConnectionHub(...), ConfigureBufferPoolManager(...), ConfigureObjectPoolManager(...), ConfigureCertificate(...), Configure<TOptions>(...), ConfigurePacketRegistry(...), ScanPackets(...), AddPacketNamespace(...), ScanHandlers(...), AddHandler(...), AddMetadataProvider(...), ConfigureDispatch(...), BindTcp<T>().Bind(), BindUdp<T>().Bind(), Build() |
Builder composition details¶
Build() captures the fluent configuration into activation-time callbacks. The host does not create listener instances during fluent configuration; listener bindings are materialized during ActivateAsync(...) after packet dispatch has been created.
During activation, the builder preparation callback performs this source-defined setup order:
- register the configured
ILoggerinInstanceManager - apply all
Configure<TOptions>(...)delegates, then call publicValidate()when the options type exposes one - create or register the packet registry
- ensure an
IConnectionHubexists, creatingConnectionHub(logger)when missing - ensure a
BufferPoolManagerexists and bindBufferLease.ByteArrayPoolto it - configure
HandshakeHandlerswithConfigureCertificate(...)when provided, otherwise initialize the default identity path - register metadata providers once
Listener factories then resolve the shared IConnectionHub from InstanceManager and construct TcpServerListener or UdpServerListener with the protocol instance, optional explicit port, and optional UDP authentication predicate.
NetworkApplication¶
NetworkApplication manages the lifecycle of the server runtime. It handles the activation and deactivation of the packet dispatcher, protocols, and listeners in the correct order.
The hosted pipeline remains generic-friendly, so the same builder flow works for built-in packets and custom packet types.
Lifecycle methods¶
ActivateAsync(...): Acquires the lifecycle gate, runs builder preparation callbacks, creates packet dispatch, best-effort registers it asIPacketDispatch, activates packet dispatch, creates and starts each TCP/UDP listener, activates hosted services, then marks the application started.
Note
Middleware is registered globally but executed inside the sharded dispatch loop. Ensure your custom middleware is thread-safe or uses localized state.
RunAsync(...): CallsActivateAsync(...), waits until cancellation, then callsDeactivateAsync(CancellationToken.None)in afinallyblock.DeactivateAsync(...): Stops and disposes listeners in reverse order, disposes protocols in reverse order, deactivates hosted services in reverse order, deactivates the packet dispatcher, waits for background task groups (net/*andtime/*) viaITaskManager.WaitGroupAsync, clears runtime lists, then marks the application stopped.Dispose(): StartsDeactivateAsync(CancellationToken.None), logs deferred failures, disposes the lifecycle gate, and suppresses finalization.
INetworkApplicationBuilder¶
The builder uses a fluent API to configure the host before it is built.
Logging and Options¶
ConfigureLogging(ILogger): Registers the logger into theInstanceManagerimmediately and stores it for later host construction.ConfigureConnectionHub(IConnectionHub): Registers the shared connection hub into theInstanceManager. If omitted, the builder creates a defaultConnectionHubduring activation.ConfigureBufferPoolManager(BufferPoolManager): Explicitly registers a custom buffer pool manager and bindsBufferLease.ByteArrayPoolto that manager for pooled receive/send paths. If omitted, the builder creates and binds a default manager during activation.ConfigureObjectPoolManager(ObjectPoolManager): Explicitly registers a custom object pool manager for pooled object paths.ConfigureCertificate(string path): Stores the certificate path for activation; the path is passed toHandshakeHandlers.SetCertificatePath(...)during builder preparation.Configure<TOptions>(Action<TOptions>): Configures a specific options type during activation by mutatingConfigurationManager.Instance.Get<TOptions>().
Note
The builder automatically registers built-in SessionHandlers, HandshakeHandlers, and SystemControlHandlers in its constructor before user-defined handler discovery runs.
Configure<TOptions>(...) applies delegates during host activation, not at the fluent call site. Use it for options that must be loaded into ConfigurationManager before dispatch and listeners start.
Packet and Handler Discovery¶
ScanPackets(assembly, requirePacketAttribute): Scans an assembly for packet types.ScanPackets(assemblyPath, requirePacketAttribute): Loads a.dllpath and scans it for packet types.ScanPackets<TMarker>(...): Marker-type shortcut for scanning packets.AddPacketNamespace(packetNamespace, recursive): Scans currently loaded assemblies and includes matching packet namespaces.AddPacketNamespace(assemblyPath, packetNamespace, recursive): Scopes namespace discovery to one assembly path.ConfigurePacketRegistry(IPacketRegistry): Uses a pre-built registry and skips hosting auto-registration.ScanHandlers(assembly): Scans an assembly for[PacketController]classes. Handler-scanned assemblies are also registered for packet discovery withrequireAttribute: false.ScanHandlers<TMarker>(): Marker-type shortcut for scanning handlers.AddHandler<THandler>(): Manually registers a handler type.AddHandler<THandler>(Func<THandler> factory): Registers a handler type with a custom factory.
Manual handler registrations override assembly-scanned registrations for the same handler type because the builder resolves handlers into a type-keyed dictionary before configuring dispatch.
Metadata and Dispatch¶
AddMetadataProvider<TProvider>(): Registers a packet metadata provider.AddMetadataProvider<TProvider>(Func<TProvider> factory): Registers a metadata provider with a custom factory.ConfigureDispatch(Action<PacketDispatchOptions<IPacket>>): Configures thePacketDispatchChanneloptions, including middleware and custom logic for built-in and custom packet pipelines.
When dispatch is created, the builder applies logging first, then all ConfigureDispatch(...) callbacks, then resolved handler registrations.
Server Bindings¶
The Nalix.Hosting namespace provides a built-in DefaultProtocol that can be used instead of creating a custom protocol class. Use .BindTcp<DefaultProtocol>().Bind() for simple scenarios where custom protocol logic is not needed.
BindTcp<TProtocol>().Bind(): Registers a TCP server for the specified protocol using the configuredNetworkSocketOptions.Port.BindTcp<TProtocol>().WithFactory(factory).Bind(): Registers a TCP server with a custom protocol factory.BindTcp<TProtocol>().OnPort(port).Bind(): Registers a TCP server for an explicit port, overriding the socket option for that binding.BindTcp<TProtocol>().OnPort(port).WithFactory(factory).Bind(): Registers an explicit-port TCP server with a custom protocol factory.BindUdp<TProtocol>().Bind(): Registers a UDP server for the specified protocol using the configuredNetworkSocketOptions.Port.BindUdp<TProtocol>().WithAuthentication(authen).Bind(): Registers a UDP server with a custom authentication predicate.BindUdp<TProtocol>().WithFactory(factory).Bind(): Registers a UDP server with a custom protocol factory.BindUdp<TProtocol>().WithFactory(factory).WithAuthentication(authen).Bind(): Registers a UDP server with both a custom factory and an authentication predicate.BindUdp<TProtocol>().OnPort(port).Bind(): Registers a UDP server for an explicit port.BindUdp<TProtocol>().OnPort(port).WithAuthentication(authen).Bind(): Registers an explicit-port UDP server with an authentication predicate.BindUdp<TProtocol>().OnPort(port).WithFactory(factory).Bind(): Registers an explicit-port UDP server with a custom protocol factory.BindUdp<TProtocol>().OnPort(port).WithFactory(factory).WithAuthentication(authen).Bind(): Registers an explicit-port UDP server with both a custom factory and authentication predicate.
Protocol types can expose a constructor that accepts IPacketDispatch; otherwise the builder uses the default activator path.
Basic usage¶
var app = NetworkApplication.CreateBuilder()
.ConfigureLogging(logger)
.ConfigureConnectionHub(new ConnectionHub(logger: logger))
.ConfigureBufferPoolManager(new BufferPoolManager(logger))
.Configure<NetworkSocketOptions>(options =>
{
options.Port = 57206;
})
.ScanPackets<Handshake>()
.ScanHandlers<SampleHandlers>()
.BindTcp<SampleProtocol>().Bind()
.Build();
await app.RunAsync(cancellationToken);
Zero-allocation receive checklist¶
To keep message reading allocation-free on the server hot path:
- register a
BufferPoolManagerwithConfigureBufferPoolManager(...)(or let the builder create one) - keep protocol
ProcessMessage(...)lease-based (args.Lease) and forward directly to dispatch - avoid copying raw payload into new
byte[]in middleware/protocol unless required by business logic