Websocket.Client: a reconnecting WebSocket wrapper for .NET
🔧 .NET/C# websocket client library
At a glance
- What is it?
- Websocket.Client is a C# wrapper over the native ClientWebSocket that adds reconnection, Rx.NET observables, a Channels-based send queue and pooled buffers, targeting .NET Standard 2.0 through .NET 10 as well as .NET Framework. Its reconnection model is timeout-driven, which is the design choice to understand before adopting it.
- Who is it for?
- Websocket.Client suits a .NET service that needs a long-running WebSocket consumer with automatic reconnection, Rx-based message handling and a send queue, since the allocation-conscious receive path is measured and documented. It does not suit a low-traffic client with a quiet server, because the reconnection trigger is one minute of silence rather than a socket drop.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 31 days ago.
- What is it written in?
- Mainly C#, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 25, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What the library wraps and what it adds
Websocket.Client is a wrapper over the native C# class ClientWebSocket with built-in reconnection and error handling. That framing is accurate and worth sitting with, because the library is not a WebSocket implementation. The protocol work is done by the .NET runtime, and the value here is in the operational layer around it.
The feature list covers the ground you would expect from such a wrapper. Installation via NuGet as the Websocket.Client package. Targeting .NET Standard 2.0, .NET Standard 2.1, .NET 6, .NET 7, .NET 8, .NET 9 and .NET 10, with full .NET Framework support through the .NET Standard 2.0 asset, where the included framework sample targets .NET Framework 4.7.2 on Windows. Reactive extensions through Rx.NET. An integrated logging abstraction through Microsoft.Extensions.Logging. A sending queue built on Channels for high performance. And allocation-conscious receive and send paths using reusable buffers and pooled text encoding.
The topic list on the repository is short and confirms the shape: client, dotnet-core, dotnet-standard, websocket, websockets and websockets-client.
The target framework spread is the practical detail for most teams. The .NET Standard 2.0 asset is the one that matters for a legacy .NET Framework service, and the README includes a framework sample specifically for that case, so the wrapper is usable without moving off .NET Framework.
A first client with observables and a send queue
The usage example in the README is short enough to read as a template.
var exitEvent = new ManualResetEvent(false);
var url = new Uri("wss://xxx");
using (var client = new WebsocketClient(url))
{
client.ReconnectTimeout = TimeSpan.FromSeconds(30);
client.ReconnectionHappened.Subscribe(info =>
Log.Information($"Reconnection happened, type: {info.Type}"));
client.MessageReceived.Subscribe(msg => Log.Information($"Message received: {msg}"));
client.Start();
Task.Run(() => client.Send("{ message }"));
exitEvent.WaitOne();
}Three things are happening at once. The client is disposable, so the using block releases it. The ReconnectionHappened observable reports what kind of reconnection happened, and MessageReceived delivers the incoming text. And the send happens on a separate task, which is the pattern the library expects, since the send path is queued rather than blocking.
The example uses Log.Information, which is the Serilog-style static call, but the library itself integrates Microsoft.Extensions.Logging, so any logging implementation of that abstraction works.
The README also points to a set of samples that cover the different hosting situations: integration tests, a console sample, a .NET Framework sample and a Blazor sample. That list is a good map of where the library is expected to work, and the Blazor sample in particular is worth a look for anyone embedding a socket in a browser-facing application.
The reconnection model and its one-minute trigger
The reconnection behaviour is the part of the library you have to design around, and the README is unusually direct about it.
A built-in reconnection is invoked after 1 minute, as the default, of not receiving any messages from the server. The timeout is configurable, and the README refers to it as communicator.ReconnectTimeout while the usage example sets it as client.ReconnectTimeout. When a reconnection happens, a ReconnectionHappened stream sends information about the type of reconnection.
Then comes the warning that matters. If you are subscribed to low-rate channels, you will likely encounter that timeout, and the advice is to raise it to a few minutes or to implement a ping-pong interaction on your own every few seconds. So the library detects a quiet server by absence of traffic, not by detecting a closed socket, which means a healthy but idle connection will be torn down and rebuilt on the default schedule.
There is a second timer for the outage case. When a remote server is actually down, a built-in functionality slows down reconnection requests, configurable through client.ErrorReconnectTimeout with a default of 1 minute.
And then the resubscription point, which is the part that catches teams out. Websocket servers usually do not keep a persistent connection between reconnections, and every new connection creates a new session, so you most likely need to resubscribe to channels, groups or topics inside the ReconnectionHappened stream.
client.ReconnectionHappened.Subscribe(info => {
client.Send("{type: subscribe, topic: xyz}")
});That is a reasonable design, and it is also work the caller has to do. The library handles the socket, the caller handles the session semantics.
Advanced configuration, the native client and changing the URL
Some options on the native ClientWebSocket class are not surfaced by the wrapper, and the mechanism for reaching them is a factory method passed as the second parameter to WebsocketClient. The factory is called on every reconnection to produce a new ClientWebSocket instance.
var factory = new Func<ClientWebSocket>(() => new ClientWebSocket
{
Options =
{
KeepAliveInterval = TimeSpan.FromSeconds(5),
Proxy = ...
ClientCertificates = ...
}
});
var client = new WebsocketClient(url, factory);
client.Start();The keep-alive interval, the proxy and the client certificates are the three that come up in practice, and all three are transport-level settings the wrapper has no business owning.
The README is also candid about the escape hatch. You can access the current native class via client.NativeClient, but the advice is to use it with caution, because on every reconnection there will be a new instance. Anything you hold a reference to goes stale at exactly the moment the connection recovers.
The URL itself can change at runtime, which is useful for a client that follows a server through a failover.
client.Url = new Uri("wss://my_new_url");;
await client.Reconnect();Setting the property and then awaiting Reconnect is the sequence, so the caller decides when the switch happens.
Where Websocket.Client is the wrong choice
The first limitation is the reconnection trigger, and it is the one that decides whether the library fits. A timeout of one minute of silence means the library treats an idle-but-healthy connection as a failure. For a subscription that receives a message every few minutes, the default produces a reconnect every minute, and each reconnect costs a handshake, and on a server that counts sessions that is a real cost. The fix is documented, a higher timeout or your own ping-pong, but it means the library is not zero-configuration for quiet workloads.
The second is the threading model. Observables from Reactive Extensions are single threaded by default, so subscription code is called synchronously and as soon as a message arrives from the WebSocket API. The README calls the advantage not having to worry about synchronization, and the cost is that long-running subscription code blocks the receiving method, buffers messages and may end up losing messages. The advice is to handle messages on another thread and unblock the receiving thread as soon as possible. So the simple-looking sample has a footgun in it, and a team that puts slow work in a subscription has to know about it.
The third is that the wrapper is a dependency rather than a necessity. .NET has had ClientWebSocket since the framework's own websocket support arrived, and a service that manages its own reconnection and its own queue is not hard. The library earns its place through the measured allocation improvements, and a team that does not care about per-message allocation is paying for a dependency it may not need.
The fourth is the version range. The library covers .NET Standard 2.0 through .NET 10, which is generous, but a project on .NET Framework gets the netstandard2.0 asset and therefore the older code path, and the README notes the framework path is measured separately at 1000 messages of 1024 B taking 1.062 ms with 2312.21 KB allocated, which is a different profile from the modern runtimes.
The allocation work, the benchmark numbers and the cost of the library
The performance section is the most detailed part of the README, and it is the clearest statement of what this library is for: long-running WebSocket consumers where per-message allocation and reconnect behaviour matter.
The mechanics are described as a list. Incoming messages are accumulated in a reusable pooled receive buffer, text messages are decoded directly from the received bytes, outgoing text messages are encoded into rented ArrayPool byte buffers, the sending queue uses System.Threading.Channels with a single reader, public observable wrappers are cached to avoid per-access wrapper allocations, disabled trace logging is guarded to avoid hot-path log argument allocations, queued text send requests use an internal value-type envelope to avoid a per-message wrapper allocation, and multi-segment binary sends complete on the final real segment without an extra empty WebSocket frame.
The representative BenchmarkDotNet results are given as before and after pairs. A 128 B text receive path goes from 264.17 ns and 560 B allocated to 36.26 ns and 280 B. A 4 KB text receive path goes from 784.50 ns and 8496 B to 485.90 ns and 8216 B. A 1024 character text send encoding goes from 102.36 ns and 1048 B to 71.05 ns and 0 B, and the 8192 character case from 738.40 ns and 8216 B to 419.79 ns and 0 B. Disabled trace logging goes from 28.72 ns and 64 B to approximately 0 ns and 0 B. The queued text request envelope goes from 31.54 ns and 24 B to 29.65 ns and 0 B. A stream-backed binary ResponseMessage.ToString() at 32 KB goes from 1.149 us and 32872 B to 44.60 ns and 104 B.
The framing around them is careful, since the README says representative, which means these are the project's own measurements on its own hardware. The benchmarks folder is documented separately, with the BenchmarkDotNet project, the commands and full result tables, which is where a reader would go to reproduce them.
The licence is MIT, and the package is on NuGet as Websocket.Client. The project is not archived and the last push was on 2026-09-24, the same day as the v5.5.1 release, with v5.5 in May 2026 and v5.4 in the same month, so the release cadence is regular enough to follow. The README links to the releases page specifically for breaking changes, which is a sensible convention for a library that has been through five major versions.
Editorial conclusion
Websocket.Client suits a .NET service that needs a long-running WebSocket consumer with automatic reconnection, Rx-based message handling and a send queue, since the allocation-conscious receive path is measured and documented. It does not suit a low-traffic client with a quiet server, because the reconnection trigger is one minute of silence rather than a socket drop. Verify your target framework first, and raise ReconnectTimeout or implement your own ping-pong if the server is idle for longer than the default.
Frequently asked questions
What does Websocket.Client add over the native ClientWebSocket?
It is a wrapper over the native class with built-in reconnection and error handling, Rx.NET observables for messages and reconnection events, Microsoft.Extensions.Logging integration, a Channels-based sending queue, and pooled buffers for the receive and send paths.
When does Websocket.Client reconnect?
A built-in reconnection is invoked after 1 minute by default of not receiving any messages from the server, and the timeout is configurable through ReconnectTimeout. The README advises raising it or implementing a ping-pong interaction every few seconds if you are on a low-rate channel.
Which frameworks does Websocket.Client support?
It targets .NET Standard 2.0, .NET Standard 2.1, .NET 6, .NET 7, .NET 8, .NET 9 and .NET 10, with full .NET Framework support through the .NET Standard 2.0 asset. The included framework sample targets .NET Framework 4.7.2 on Windows.
Do I need to resubscribe after a Websocket.Client reconnection?
Most likely yes. The README notes that WebSocket servers usually do not keep a persistent connection between reconnections and that every new connection creates a new session, so you should resubscribe to your channels, groups or topics inside the ReconnectionHappened stream.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/marfusios-websocket-client)Was this useful?