WatsonTcp is the easiest way to build TCP-based clients and servers in C#.
WatsonTcp is the easiest way to build TCP-based clients and servers in C#.
WatsonTcp is the fastest, easiest, most efficient way to build TCP-based clients and servers in C# with integrated framing, reliable transmission, and fast disconnect detection.
IMPORTANT WatsonTcp provides framing to ensure message-level delivery which also dictates that you must either 1) use WatsonTcp for both the server and the client, or, 2) ensure that your client/server exchange messages with the WatsonTcp node using WatsonTcp's framing. Refer to FRAMING.md for a reference on WatsonTcp message structure.
This project is part of the .NET Foundation along with other projects like the .NET Runtime.
Special thanks to the following people for their support and contributions to this project!
@brudo @MrMikeJJ @mikkleini @pha3z @crushedice @marek-petak @ozrecsec @developervariety @NormenSchwettmann @karstennilsen @motridox @AdamFrisby @Job79 @Dijkstra-ru @playingoDEERUX @DuAell @syntacs @zsolt777 @broms95 @Antwns @MartyIX @Jyck @Memphizzz @nirajgenius @cee-sharp @jeverz @cbarraco @DenisBalan @Markonius @Ahmed310 @markashleybell @thechosensausage @JVemon @eatyouroats @bendablegears @Laiteux @fisherman6v6 @wesoos @YorVeX @tovich37 @sancheolz @lunedis @ShayanFiroozi @BrvSqr
If you'd like to contribute, please jump right into the source code and create a pull request, or, file an issue with your enhancement request.
WatsonTcp now emits standardized, vendor-neutral telemetry using only the .NET base class library, so it can be observed by Radiant, the OpenTelemetry SDK, Prometheus, or any compatible host with no dependency on any telemetry backend.
System.Diagnostics.Metrics.Meter named WatsonTcpSystem.Diagnostics.ActivitySource named WatsonTcpWatsonTcp.WatsonTcpMetrics class and are stable across releasesrole, protocol, outcome, reason); high-cardinality identifiers (client GUID, remote endpoint, conversation GUID) are placed on spans, never on metric tagsSettings.EnableMetrics and Settings.EnableTracingA telemetry host subscribes by name and nothing else changes in your WatsonTcp code:
// Radiant host
settings.Sources.AddMeter(WatsonTcp.WatsonTcpMetrics.MeterName); // "WatsonTcp"
settings.Sources.AddActivitySource(WatsonTcp.WatsonTcpMetrics.MeterName); // "WatsonTcp"
// or a raw OpenTelemetry host
Sdk.CreateMeterProviderBuilder().AddMeter("WatsonTcp").AddPrometheusHttpListener().Build();
Sdk.CreateTracerProviderBuilder().AddSource("WatsonTcp").AddOtlpExporter().Build();
See TELEMETRY.md for the full metric catalog, tag dictionaries, Prometheus series names, and integration walkthrough.
WatsonTcp now keeps the server listener running when the underlying TCP accept call hits a transient connection reset or abort before a client is fully established.
Key improvements include:
SocketError.ConnectionReset and SocketError.ConnectionAborted without stopping the accept loopExceptionEncountered and add warning-level listener loggingWatsonTcp now includes a performance-focused maintenance release aimed at lower latency, higher throughput, and faster steady-state operation without changing the public API surface for normal usage.
Key improvements include:
The repository now includes src/Test.PerformanceBenchmark, a console benchmark suite that measures:
Run it from the repository root with:
RunBenchmarks.bat
The benchmark writes timestamped summaries to benchmarks/.
WatsonTcp now supports awaited stream callbacks on both server and client:
server.Callbacks.StreamReceivedAsync = async (args, token) =>
{
await using var file = File.Create("payload.bin");
await args.DataStream.CopyToAsync(file, 81920, token);
};
client.Callbacks.StreamReceivedAsync = async (args, token) =>
{
await using var file = File.Create("server-payload.bin");
await args.DataStream.CopyToAsync(file, 81920, token);
};
Use Callbacks.StreamReceivedAsync when you need to await stream processing, especially for payloads at or above Settings.MaxProxiedStreamSize.
Receive mode selection is now:
Events.MessageReceivedCallbacks.StreamReceivedAsyncEvents.StreamReceivedWarnings for conflicting receive-mode configuration are emitted through Settings.Logger.
Shared Touchstone-backed coverage now includes:
WatsonTcp now supports an explicit server-side admission callback:
server.Callbacks.AuthorizeConnectionAsync = async (ctx, token) =>
{
if (!ctx.IpPort.StartsWith("127.0.0.1"))
return ConnectionAuthorizationResult.Reject("Local connections only.");
return ConnectionAuthorizationResult.Allow();
};
Rejected connections raise ConnectionRejected events and can surface ConnectionRejectedException on compatible clients.
WatsonTcp also supports framed pre-registration handshakes without exposing the raw stream:
server.Callbacks.HandshakeAsync = async (session, token) =>
{
HandshakeMessage msg = await session.ReceiveAsync(token);
string apiKey = Encoding.UTF8.GetString(msg.Data);
return apiKey == "valid-api-key-123"
? HandshakeResult.Succeed()
: HandshakeResult.Fail("Invalid API key.");
};
client.Callbacks.HandshakeAsync = async (session, token) =>
{
await session.SendAsync(new HandshakeMessage
{
Type = "api-key",
Data = Encoding.UTF8.GetBytes("valid-api-key-123")
}, token);
return HandshakeResult.Succeed();
};
Handshake-enabled servers require compatible clients that understand the new control-plane statuses.
Automated tests are now defined once in src/Test.Shared and exposed through:
src/Test.Automated for the Touchstone CLI runnersrc/Test.XUnit for dotnet test via xUnitsrc/Test.NUnit for dotnet test via NUnitRun them with:
dotnet run --project src/Test.Automated --framework net8.0 -- --results test-results/cli-results.json
dotnet test src/Test.XUnit/Test.XUnit.csproj --framework net8.0
dotnet test src/Test.NUnit/Test.NUnit.csproj --framework net8.0
MemoryStream accumulator with direct byte comparisonArrayPool pooling instead of allocating new buffers on every iterationClientMetadataManager from 5 independent ReaderWriterLockSlim instances to a single lock, eliminating race conditions during multi-dictionary operations (ReplaceGuid, Remove)GetClient() (ContainsKey then indexer across separate lock acquisitions); now uses TryGetValueAutoResetEvent + event-based sync response matching with ConcurrentDictionary> in both client and server, eliminating handler registration race conditions and signal lossWaitHandle resource leak in WatsonTcpClient.Connect() (was commented out, now properly closed)ClientMetadata.Dispose() and WatsonTcpClient.Disconnect() with Task.Wait(timeout)Settings.MaxHeaderSize (client and server, default 256KB) guards against memory exhaustion from oversized or malicious headersSettings.EnforceMaxConnections (server, default true) actively rejects connections at capacity; set to false for legacy behaviorTaskCanceledException and OperationCanceledException catch blocksSettings.EnforceMaxConnections defaults to true. If you relied on accepting connections beyond MaxConnections, set EnforceMaxConnections = false.SyncMessageReceived callbackRefer to ARCHITECTURE.md for a detailed overview of the internal design, message flow, threading model, and key design decisions.
For the wire protocol specification (header format, delimiter, payload layout), see FRAMING.md.
Test projects for both client and server are included which will help you understand and exercise the class library. Shared automated coverage lives in Test.Shared, while Test.Automated, Test.XUnit, and Test.NUnit are the supported unattended test hosts.
WatsonTcp supports data exchange with or without SSL. The server and client classes include constructors that allow you to include fields for the PFX certificate file and password. An example certificate can be found in the test projects, which has a password of 'password'.
WatsonTcp allows you to receive messages using either byte arrays or streams.
Events.MessageReceived if you want a buffered byte[]Callbacks.StreamReceivedAsync if you want awaited stream ownershipEvents.StreamReceived if you want the legacy synchronoNo open issues yet, or sync has not completed.