TCP Request/Response Example¶
This example shows a complete TCP request/response flow using:
TcpListenerBaseProtocolPacketDispatchChannel- a request packet
- a response returned from a handler
The goal is clarity, not production completeness.
Scenario¶
Client sends a PingRequest.
Server replies with a PingResponse.
Server setup¶
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<PingResponse> Handle(PingRequest request, IConnection connection)
{
PingResponse response = new()
{
Message = $"pong:{request.Message}"
};
return ValueTask.FromResult(response);
}
}
3. Create dispatcher¶
PacketDispatchChannel dispatch = new(options =>
{
options.WithLogging(logger)
.WithHandler(() => new SamplePingHandlers());
});
dispatch.Activate();
4. Create protocol¶
public sealed class SampleProtocol : Protocol
{
private readonly PacketDispatchChannel _dispatch;
public SampleProtocol(PacketDispatchChannel dispatch) => _dispatch = dispatch;
public override void ProcessMessage(object sender, IConnectEventArgs args)
=> _dispatch.HandlePacket(args.Lease, args.Connection);
}
5. Start listener¶
public sealed class SampleTcpListener : TcpListenerBase
{
public SampleTcpListener(ushort port, IProtocol protocol) : base(port, protocol) { }
}
SampleTcpListener listener = new(57206, new SampleProtocol(dispatch));
listener.Activate();
Client flow¶
The exact client implementation depends on your SDK/session abstraction, but the request/response shape is:
PingRequest request = new()
{
Message = "hello"
};
await client.SendAsync(request.Serialize());
// Your client-side read loop / awaiter resolves PingResponse here
PingResponse response = await WaitForPingResponseAsync();
Console.WriteLine(response.Message);
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 PingRequest
Dispatch->>Handler: Handle(request, connection)
Handler-->>Dispatch: PingResponse
Dispatch-->>Client: serialized response
Variant: send manually from handler¶
Instead of returning a response, you can send manually:
[PacketOpcode(0x1001)]
public async ValueTask Handle(PacketContext<PingRequest> context, CancellationToken ct)
{
PingResponse response = new() { Message = "pong" };
await context.Sender.SendAsync(response, 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
PingResponseis the simplest normal request/response model Protocoljust forwards frames into dispatchPacketDispatchChannelowns middleware, deserialization, handler invocation, and result handling