WitRPC separates what you say from how it travels. The service contract stays the same while you choose a transport (how bytes move between client and server) and a serializer (how objects become bytes). This page walks through the options and how to combine them. Four transports carry WitRPC's own protocol; REST stands apart as a compatibility layer for callers that are not WitRPC at all, and is covered last.

Supported transports

Memory-mapped files

Two processes on the same machine exchange data through a shared memory region, bypassing the network stack entirely. This is the fastest option for local IPC and suits one-to-one links moving large volumes of data, such as a host process feeding a compute worker. The limits follow from the design: single machine only, and best used for one server with one client. When several local clients must connect, use named pipes instead. 3.0 reworked the channel internally (two directional regions with an atomic handoff, chunked transfers, and peer death detected through an abandoned mutex), making it lossless in both directions. The API is unchanged, but both ends must run 3.0.

Named pipes

The operating system's IPC channel: a server creates a pipe by name, and multiple local clients connect to it. Named pipes are fast, reliable, and stay inside the OS without touching the network stack. They are the default choice for local IPC with more than one client, or whenever you want OS-level access control on the channel. Cross-machine pipes exist on Windows but are rarely worth the configuration effort; for anything beyond one machine, use TCP.

TCP

The general-purpose network transport. The server binds a port, clients connect by host and port, and the connection carries reliable, ordered, bidirectional traffic. Use it for anything cross-machine: services on a LAN, a desktop application talking to a remote server, machine-to-machine links over the internet. Plain TCP carries no security of its own; on untrusted networks enable TLS (WithTcpSecure) or WitRPC's message-layer encryption, described in Security & Authentication →. Since 3.1.1 both ends disable Nagle's algorithm and write each frame in a single call, which removes the ~200 ms per-message stall the write-write-read pattern used to hit on Windows.

WebSocket

A full-duplex channel that starts as an HTTP request, which makes it the transport for browsers and restrictive networks. Traffic flows through standard HTTP(S) ports, so it passes proxies and corporate firewalls that would block arbitrary TCP. After the handshake, performance is close to raw TCP. This is the transport for Blazor WebAssembly clients and for any environment where opening custom ports is not an option. Use wss:// (with an https:// listener and a certificate on the server) in production.

REST

REST is a compatibility layer outward, with its own host, rather than a transport of the WitRPC protocol. The persistent transports carry WitRPC's own protocol (a handshake, encryption, a session with server-to-client events) and need WitRPC on both ends. REST deliberately does not: every method of the contract becomes one HTTP endpoint, each call is one stateless request with the arguments as plain JSON, and the reply is the return value as plain JSON. Nothing WitRPC-specific travels on the wire, so the caller can be curl, a browser, a Python script, or the .NET REST client, interchangeably.

That is why REST is its own server, WitServerRest, built by WitServerRestBuilder, rather than a transport plugged into WitServer: a transport would wrap each request in the envelope and the handshake, and the readable contract (the whole point) would be gone. What is shared is everything above the wire: the same service interface, the same implementation, the same request processor, and the same IAccessTokenValidator as the persistent server. One implementation can be hosted over WebSocket for WitRPC clients and over REST for everyone else, side by side.

The trade-offs are fundamental: no persistent connection means no server push (events never reach REST callers), and no message-layer encryption, with TLS (https://) as the transport protection. Use it to give external systems request-reply access to your service. Between two .NET ends that could both run WitRPC, REST buys nothing and gives those things up for free; connect them over a persistent transport instead.

Configuring transports

Each transport ships as its own NuGet package with builder extension methods.

Memory-mapped file. Server: options.WithMemoryMappedFile("MapName", size) (size in bytes; an overload without size uses the default). Client: options.WithMemoryMappedFile("MapName"). Names must match.

Named pipe. Server: options.WithNamedPipe("PipeName", maxNumberOfClients). Client: options.WithNamedPipe("PipeName").

TCP. Server: options.WithTcp(port, maxNumberOfClients). Client: options.WithTcp("host", port).

TCP with TLS. Server: options.WithTcpSecure(port, maxNumberOfClients, certificate) with an X509Certificate. Client: options.WithTcpSecure("host", port, targetHost, sslValidationCallback), where targetHost must match the certificate name and the callback can be null to use standard validation (or a custom RemoteCertificateValidationCallback for self-signed certificates in development).

WebSocket. Server: options.WithWebSocket("http://localhost:5000/path", maxNumberOfClients); the server upgrades HTTP requests at that URL. Client: options.WithWebSocket("ws://localhost:5000/path"). Schemes pair up: http:// with ws://, https:// with wss://.

REST. REST uses dedicated builders rather than the standard ones. The server:

csharp
var server = WitServerRestBuilder.Build(options =>
{
    options.WithUrl("http://localhost:5000/api/example/");
    options.WithService<IExampleService>(new ExampleService());
    options.WithAccessToken("MySecretToken");   // optional: require a Bearer token
});
server.StartWaitingForConnection();

Every method of the contract is one endpoint under the base URL: POST {base}/{MethodName} with a JSON body of the arguments (an object of named arguments or an array of positional ones), or GET {base}/{MethodName}?name=value for simple arguments. The reply is the return value as plain JSON (204 for void); errors carry an HTTP status and a small JSON error object. Several contracts can share one host through WithServices(), mirroring the persistent server. Generic methods are not callable over REST.

The host enforces the limits a public HTTP endpoint needs: a per-call processing timeout (WithTimeout), a body-size cap (WithMaxBodyBytes, 64 MB by default), and a concurrency bound (WithMaxConcurrentRequests); a slow or throwing call neither blocks the next request nor takes the listener down. Every option also has an open form that takes your own implementation: WithRequestProcessor(processor, contracts), WithAccessTokenValidator(validator), WithLogger(logger).

External systems are the audience: a non-.NET client simply issues HTTP requests against the endpoint, and the full wire contract with request and response examples is in the Server.Rest package README. A .NET REST client exists too (WitClientRestBuilder in OutWit.Communication.Client.Rest, giving the usual proxy over the interface), and its main role is narrow: verifying, typically from a test, that the REST host answers the contract correctly. The door does swing both ways, though. Since nothing on the wire is WitRPC-specific, the same client can front an external service that implements the compatible protocol; in a coordinated system with parts on different platforms, agreeing on this plain-JSON shape lets the .NET side consume a foreign service through the same typed proxy. That is the rarer case. What has no reason to exist is RestServer↔RestClient between two .NET ends inside the system: that pair could run a persistent transport and keep events and message-layer encryption.

Serialization formats

The serializer applies to your payloads (parameters, results, event arguments); WitRPC's own message envelope always travels as MemoryPack. Client and server must use the same payload serializer, set with one builder call on each side.

Two formats live in the core packages:

JSON (WithJson()) is the default: human-readable, universally understood, ideal while developing and debugging. Payloads are larger and parsing is slower than any binary option.

MemoryPack (WithMemoryPack()) is usually the fastest option: a source-generated, near-zero-overhead binary serializer. It requires marking data types as [MemoryPackable] and partial, so it fits internal systems where you control every DTO and want maximum speed.

Three more ship as opt-in plugin packages (since 3.1.0), so nobody who does not use them carries their dependencies, which matters most in Blazor WebAssembly bundles. Add the matching OutWit.Communication.Serializers.* package on both ends:

MessagePack (WithMessagePack(), package ...Serializers.MessagePack) is a compact binary format, substantially faster and smaller than JSON. Models annotated for MessagePack-CSharp (the same attributes SignalR's MessagePack protocol uses) move over WitRPC unchanged.

ProtoBuf (WithProtoBuf(), package ...Serializers.ProtoBuf, via protobuf-net) brings Protocol Buffers' compact payloads with code-first annotations: [ProtoContract] and [ProtoMember(n)], as used with protobuf-net.Grpc. Choose it when your ecosystem already standardizes on protobuf-net.

Google.Protobuf (WithGoogleProtobuf(), package ...Serializers.GoogleProtobuf) carries protoc-generated IMessage types as protobuf wire bytes, exactly as gRPC would send them; everything else in a signature (primitives, Guid, plain DTOs) goes through a fallback serializer, JSON by default. This is the path for proto-first gRPC migrations, which protobuf-net cannot read.

Choosing the right combination

High-performance IPC on one machine. Memory-mapped file + MemoryPack for a single client at maximum speed; named pipes + MemoryPack when several local clients connect to one server. Both avoid the network stack entirely and outperform any TCP-based setup locally.

Cross-machine, LAN or WAN. TCP + MemoryPack is the workhorse; MessagePack (plugin) when the DTOs cannot take MemoryPack annotations. Add security to match the network: WitRPC encryption inside a trusted perimeter, TLS (WithTcpSecure) or WebSocket over wss:// across untrusted networks.

Browser and web clients. WebSocket, with wss:// in production. The Blazor channel factory speaks MemoryPack by default; JSON is the natural format when hand-written JavaScript is on the other end.

External integrations. REST opens the service to any language for request-reply calls. The wire is plain JSON by contract, whatever serializer the persistent transports use. If external parties need real-time events, point them at a WebSocket endpoint instead; if they already live on Protocol Buffers, the ProtoBuf or Google.Protobuf plugin over TCP or WebSocket keeps the schemas shared.

Whatever the combination, the rule is symmetry: transport and serializer must match on both ends, and security settings along with them. Measured comparisons of these transports and serializers, against each other and against other frameworks, are in Comparing RPC Frameworks in .NET Applications →.