TCP Patterns
On this page
- Scenario#
- Choosing Your Initialization Strategy#
- 1. High-Level Hosting Builder (Recommended)#
- 2. Direct Transport Listener#
- Server setup (Direct Transport Path)#
- 1. Register shared services#
- 2. Create handler#
- 3. Create dispatcher#
- 4. Create protocol#
- 5. Start listener#
- Client flow#
- End-to-end flow#
- Variant: send manually from handler#
- What clients should remember#
- Recommended Next Pages#
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);
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.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 FrameProcessor, OpCodeExtractor, and 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 IFrameProcessor FrameProcessor => DefaultFrameProcessor.Instance;
public override IOpCodeExtractor OpCodeExtractor => DefaultOpCodeExtractor.Instance;
public override void ProcessMessage(object? sender, IConnectionEventArgs args)
=> _dispatch.HandlePacket(args.Lease, args.Connection);
protected override void OnConnectionError(IConnection connection, Exception ex)
{
_logger.Error("Transport error on connection {0}", ex, connection.ConnectionId);
}
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 |
IConnectionGuard | IConnectionGuard | Admission rate/quota guard |
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();
IConnectionGuard guard = new ConnectionGuard();
InstanceManager.Instance.Register<IConnectionHub>(hub);
InstanceManager.Instance.Register<IConnectionGuard>(guard);
// Initialize and Activate
SampleTcpListener listener = new(57206, new SampleProtocol(dispatch, logger), hub, guard);
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
- Client calls
session.RequestAsync<Control>(...), assigning a non-zeroSequenceId. TcpListenerBaseaccepts the socket, frames the inbound payload, runsFrameProcessor, and callsSampleProtocol.ProcessMessage(...).PacketDispatchChanneldeserializesControl, invokesSamplePingHandlers.HandleAsync, and sends the returnedControl(PONG) back over the connection.
Variant: send manually from handler
Instead of returning a response, you can send manually:
[PacketOpcode(0x1001)]
public async ValueTask Handle(IPacketContext<Control> context)
{
await context.Sender.SendAsync(new Control { Type = ControlType.PONG }, context.CancellationToken);
}
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