TCP Patterns Guide¶
Low-Level Implementation
This guide demonstrates the manual instantiation of TcpListenerBase and Protocol. While powerful, this path bypasses the automatic feature discovery and dependency injection provided by the Hosting Builder.
Use this only when embedding Nalix into existing engines or when building specialized transport layers.
Learning Signals
- Level: Advanced
- Time: 5–10 minutes
- Prerequisites: Quickstart
This manual flow demonstrates:
TcpListenerBaseProtocolPacketDispatchChannel- a request packet
- a response returned from a handler
The goal is clarity, not production completeness.
Use it when you want one canonical TCP sample before adding middleware, metadata, or more complex client behavior.
Scenario¶
Client sends a Control packet.
Server replies with a Control packet.
Choosing Your Initialization Strategy¶
Before starting, you must decide how to initialize your server. Nalix provides two distinct paths:
1. High-Level Hosting Builder (Recommended)¶
Use NetworkApplication.CreateBuilder() to bootstrap your server.
- When to use: 99% of production applications.
- Benefits: Automatic handler discovery, Dependency Injection, built-in logging, easier middleware configuration, and support for multi-protocol listeners in a single application.
2. Direct Transport Listener¶
Manually instantiate TcpListenerBase and Protocol (as shown in the example below).
- When to use: Building specialized transport libraries, low-level testing, or embedding Nalix into a non-standard server architecture.
- Benefits: Absolute control over the listener lifecycle; zero overhead from the hosting layer.
Server setup (Direct Transport Path)¶
1. Register shared services¶
InstanceManager.Instance.Register<ILogger>(logger);
InstanceManager.Instance.Register<IPacketRegistry>(packetRegistry);
2. Create handler¶
[PacketController("SamplePingHandlers")]
public sealed class SamplePingHandlers
{
[PacketOpcode(0x1001)]
public ValueTask<Control> HandleAsync(IPacketContext<Control> context)
{
context.Packet.Type = ControlType.PONG;
return ValueTask.FromResult(context.Packet);
}
}
3. Create dispatcher¶
PacketDispatchChannel dispatch = new(options =>
{
options.WithLogging(logger)
.WithHandler(() => new SamplePingHandlers());
});
dispatch.Activate();
4. Create protocol¶
The Protocol class is the glue between the raw listener and your message dispatcher. You must implement ProcessMessage and can optionally override lifecycle hooks for custom error handling.
public sealed class SampleProtocol : Protocol
{
private readonly PacketDispatchChannel _dispatch;
private readonly ILogger _logger;
public SampleProtocol(PacketDispatchChannel dispatch, ILogger logger)
{
_dispatch = dispatch;
_logger = logger;
}
public override void ProcessMessage(object? sender, IConnectEventArgs args)
=> _dispatch.HandlePacket(args.Lease, args.Connection);
protected override void OnConnectionError(IConnection connection, Exception ex)
{
_logger.LogError(ex, "Transport error on connection {ConnectionId}", connection.ID);
}
protected override bool ValidateConnection(IConnection connection)
{
// Add custom IP blacklisting or state checks here
return base.ValidateConnection(connection);
}
}
5. Start listener¶
When using the transport layer directly (outside of the Hosting builder), you must ensure that all required services are registered in the InstanceManager. This is the manual equivalent of what src/Nalix.Hosting/NetworkApplicationBuilder.cs wires for you automatically.
Required Dependencies (InstanceManager)¶
| Service | Type | Role |
|---|---|---|
ILogger |
ILogger |
Structured logging |
IConnectionHub |
IConnectionHub |
Connection tracking and batch operations |
TaskManager |
TaskManager |
Background worker management |
TimingWheel |
TimingWheel |
Lightweight timeout scheduling |
ObjectPoolManager |
ObjectPoolManager |
Recyclable buffer/context management |
// Setup dependencies
InstanceManager.Instance.Register<ILogger>(logger);
IConnectionHub hub = new ConnectionHub(logger);
InstanceManager.Instance.Register<IConnectionHub>(hub);
InstanceManager.Instance.Register<TaskManager>(new TaskManager());
// Initialize and Activate
SampleTcpListener listener = new(57206, new SampleProtocol(dispatch, logger), hub);
listener.Activate();
Client flow¶
The client uses the Nalix.SDK to send a typed request and cleanly await the response without manual loop management:
using Contracts;
using Nalix.SDK.Transport.Extensions;
// Orchestrated request/response in one line
Control response = await session.RequestAsync<Control>(
new Control { Type = ControlType.PING },
options: RequestOptions.Default.WithTimeout(3_000)
);
Console.WriteLine(response.Type); // PONG
End-to-end flow¶
sequenceDiagram
participant Client
participant Listener as TcpListenerBase
participant Protocol as Protocol
participant Dispatch as PacketDispatchChannel
participant Handler as SamplePingHandlers
Client->>Listener: TCP frame
Listener->>Protocol: ProcessMessage event
Protocol->>Dispatch: HandlePacket(lease, connection)
Dispatch->>Dispatch: Deserialize Control
Dispatch->>Handler: Handle(request, connection)
Handler-->>Dispatch: Control
Dispatch-->>Client: serialized response
Variant: send manually from handler¶
Instead of returning a response, you can send manually:
[PacketOpcode(0x1001)]
public async ValueTask Handle(IPacketContext<Control> context, CancellationToken ct)
{
await context.Sender.SendAsync(new Control { Type = ControlType.PONG }, ct);
}
Use this style when:
- you want multiple replies
- you need finer control over send timing
- you do not want to rely on return-type handling
What clients should remember¶
- returning
Controlis the simplest normal request/response model Protocol.ProcessMessage(...)is the message-level handoff into dispatchsrc/Nalix.Network/Protocols/Protocol.PublicMethods.csshows thatOnAccept(...)is where TCP receive starts afterValidateConnection(...)passes- the Listener owns the raw frame path before
Protocolsees a processed message PacketDispatchChannelowns middleware, deserialization, handler invocation, and result handling- the same pattern works for custom packet types if you swap
Controlfor your own packet contract
Recommended Next Pages¶
- Packet Dispatch — Dispatch API reference
- Handler Return Types — Supported return shapes
- TCP Listener — Listener API reference