RPC channels quick start
New to DotBoxD? Start with Getting started and the Tutorials. This page and the rest of this section are the production deep dive for the RPC channel layer - transports, codecs, and the generated registry. For choosing between transports, see Channels & transports.
Get an RPC service running over TCP in 5 minutes.
Reach for RPC channels when you want request/response host capabilities behind a shared contract: one C# interface compiles to a typed proxy plus dispatcher, so you get type-safe interop with no hand-written marshaling and no runtime reflection on the hot path. The interface is the single source of truth; AOT/IL2CPP deployments additionally require generated codec formatters, explicit registry rooting, and validation in the consumer build. Prefer an event pipeline instead when the host should receive only server-side filtered and projected data over a one-way push, or pushdown when you need to collapse N round-trips into one server-side batch.
1. Define Your Service Contract
Section titled “1. Define Your Service Contract”Create a shared library with your service interface:
using DotBoxD.Services.Attributes;using MessagePack;
[RpcService]public interface IMyService{ Task<GreetingResponse> GreetAsync(GreetingRequest request, CancellationToken ct = default);}
[MessagePackObject]public class GreetingRequest{ [Key(0)] public string Name { get; set; } = "";}
[MessagePackObject]public class GreetingResponse{ [Key(0)] public string Message { get; set; } = ""; [Key(1)] public DateTime ServerTime { get; set; }}2. Implement the Server
Section titled “2. Implement the Server”public class MyService : IMyService{ public Task<GreetingResponse> GreetAsync(GreetingRequest request, CancellationToken ct) { return Task.FromResult(new GreetingResponse { Message = $"Hello, {request.Name}!", ServerTime = DateTime.UtcNow }); }}
// Server/Program.csusing DotBoxD.Services;using DotBoxD.Services.Generated;using DotBoxD.Codecs.MessagePack;using DotBoxD.Transports.Tcp;
// A host turns every accepted connection into a peer.// Each peer provides your service; the generated ProvideMyService extension wires it up.await using var host = RpcHost .Listen(new TcpServerTransport(5050), new MessagePackRpcSerializer()) .ForEachPeer(peer => peer.ProvideMyService(new MyService()));
await host.StartAsync();Console.WriteLine("Server running on port 5050");Console.ReadLine();
await host.StopAsync(); // DisposeAsync also stops the host3. Create the Client
Section titled “3. Create the Client”using DotBoxD.Services;using DotBoxD.Services.Generated;using DotBoxD.Codecs.MessagePack;using DotBoxD.Transports.Tcp;
var transport = new TcpTransport("localhost", 5050);await transport.ConnectAsync();
// Over a connection, an RpcPeer can both provide and get services.// RejectInboundCalls signals a get-only intent (this side never serves calls).await using var peer = RpcPeer .Over(transport.Connection!, new MessagePackRpcSerializer(), new RpcPeerOptions { RejectInboundCalls = true }) .Start();
var service = peer.GetMyService();var response = await service.GreetAsync(new GreetingRequest { Name = "World" });
Console.WriteLine(response.Message); // "Hello, World!"Console.WriteLine(response.ServerTime);4. Run It
Section titled “4. Run It”# Terminal 1: Start serverdotnet run --project Server
# Terminal 2: Run clientdotnet run --project ClientProject References
Section titled “Project References”Your shared project needs these references:
These
ProjectReferences assume you are building inside the cloned DotBoxD repo; if you installed from NuGet (getting started), reference the packages instead - theDotBoxD.Servicespackage bundlesDotBoxD.Services.SourceGeneratoras an analyzer automatically, so you never add the generator as a standalone reference.
<ItemGroup> <PackageReference Include="MessagePack" Version="2.5.187" /> <ProjectReference Include="../DotBoxD.Services/DotBoxD.Services.csproj" /> <ProjectReference Include="../DotBoxD.Services.SourceGenerator/DotBoxD.Services.SourceGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /></ItemGroup>Server and client projects reference:
- Your shared project
DotBoxD.Transports.TcpDotBoxD.Codecs.MessagePack
For process-local IPC, use the dedicated named-pipe package instead of TCP:
dotnet add package DotBoxD.Transports.NamedPipes --prereleaseusing DotBoxD.Transports.NamedPipes;
var serverTransport = new NamedPipeServerTransport("my-app-rpc");var clientTransport = new NamedPipeClientTransport("my-app-rpc");What Gets Generated?
Section titled “What Gets Generated?”The source generator creates:
- Proxy (
MyServiceProxy) - Caller-side stub that serializes calls - Dispatcher (
MyServiceDispatcher) - Provider-side router that deserializes and invokes - Extensions (
peer.GetMyService(),peer.ProvideMyService(impl)) - Convenience methods onRpcPeer - Registry factory (
DotBoxDGenerated) - Typed proxy/dispatcher factory backed by generated delegates - Service catalog (
DotBoxDGenerated.Services) - Array-backedGeneratedServicedescriptors - Registration sink (
DotBoxDGenerated.RegisterServices(...)) - Direct generic calls for service/proxy registrations - Generated implementation sink (
DotBoxDGenerated.RegisterGeneratedServices(...)) - Direct generic calls for service/proxy/dispatcher registrations
You can use the generated factory directly when building framework-style APIs:
using DotBoxD.Services.Generated;
// CreateProxy takes an IRpcInvoker - pass an RpcPeer.var proxy = DotBoxDGenerated.CreateProxy<IMyService>(peer);var dispatcher = DotBoxDGenerated.CreateDispatcher<IMyService>(new MyService());
foreach (var service in DotBoxDGenerated.Services){ Console.WriteLine($"{service.ServiceType.Name}: {service.ProxyType.Name}");}For DI containers or host registries that need generic service/implementation pairs,
implement IRpcServiceRegistrationSink and pass it to the generated callback:
using DotBoxD.Services.Generated;
public sealed class MySink : IRpcServiceRegistrationSink{ public void AddService<TService, TImplementation>() where TService : class where TImplementation : TService { // Register TService -> TImplementation in the host container. }}
DotBoxDGenerated.RegisterServices(new MySink());If the host needs both generated implementation types, use
IRpcGeneratedServiceRegistrationSink:
using DotBoxD.Services.Generated;using DotBoxD.Services.Server;
public sealed class GeneratedSink : IRpcGeneratedServiceRegistrationSink{ public void AddService<TService, TProxy, TDispatcher>() where TService : class where TProxy : TService where TDispatcher : IServiceDispatcher { // Register TService -> TProxy and TDispatcher without scanning assemblies. }}
DotBoxDGenerated.RegisterGeneratedServices(new GeneratedSink());Next Steps
Section titled “Next Steps”- Unity Integration Guide - Full Unity setup
- WebSocket Transport Guide - WebSocket setup for browsers and WebGL
- Generated Service Registry - Factory and registry usage
- Named Pipe Transport - Local IPC setup
- Samples - Working examples
- API Reference - Detailed API docs