![]() |
VOOZH | about |
dotnet add package WebsocketClientLite.PCL --version 9.0.2
NuGet\Install-Package WebsocketClientLite.PCL -Version 9.0.2
<PackageReference Include="WebsocketClientLite.PCL" Version="9.0.2" />
<PackageVersion Include="WebsocketClientLite.PCL" Version="9.0.2" />Directory.Packages.props
<PackageReference Include="WebsocketClientLite.PCL" />Project file
paket add WebsocketClientLite.PCL --version 9.0.2
#r "nuget: WebsocketClientLite.PCL, 9.0.2"
#:package WebsocketClientLite.PCL@9.0.2
#addin nuget:?package=WebsocketClientLite.PCL&version=9.0.2Install as a Cake Addin
#tool nuget:?package=WebsocketClientLite.PCL&version=9.0.2Install as a Cake Tool
๐ .NET Standard
๐ .NET Standard
๐ .NET 8
๐ .NET 9
๐ .NET 10
Please star this project if you find it useful. Thank you.
See the project NETCore.Console.Test in the ./src/test directory for an example of how to use.
This library is a ground-up implementation of the WebSocket specification (RFC 6455) - it does not rely on any built-in .NET WebSocket libraries.
The library provides developers with additional flexibility, including the ability to establish secure WSS websocket connections to servers with self-signing certificates, expired certificates, etc. This capability should be used with care for obvious reasons, but is valuable for testing environments, closed local networks, local IoT set-ups, and more.
The library utilizes ReactiveX (aka Rx or Reactive Extensions). While this dependency introduces a small learning curve, it's worthwhile for the context.
Fixes a race in the 9.0.1 close-on-unsubscribe behavior: disposing the connection subscription immediately after WebsocketConnected could tear the connection down before the close-handshake hook was wired, skipping the close frame. The hook is now registered before WebsocketConnected is emitted and runs on every teardown path. Use 9.0.2 instead of 9.0.1.
Version 9.0.1 is a correctness and robustness patch on top of 9.0.
Bug fixes
await sender.SendText(...) (and all other sends) now throws WebsocketClientLiteException when the write fails, in addition to reporting SendError on the status channel. Previously failures were silent at the call site.Disconnected status is delivered. An Rx-grammar bug completed the status stream before emitting the last status.lib/netstandard2.0/2.1 alongside the IWebsocketClientLite package dependency (which could cause duplicate-assembly/type-conflict issues).TcpClient only works for the first connection; the sample and docs now reflect that reconnect patterns require leaving TcpClient unset. See the note under Creating a WebSocket Client.Close received mid-fragmentation now cleanly aborts the message; per-frame header reads no longer allocate.New
NegotiatedSubprotocols on ClientWebSocketRx exposes the subprotocol(s) the server accepted during the handshake.Version 9.0 is a correctness and protocol-compliance release.
Bug fixes
Subprotocols you configure are now included in the Sec-WebSocket-Protocol handshake header. Previously they were only used to validate the server's response and never actually sent.ExcludeZeroApplicationDataInPong now works. The flag is honored when replying to a zero-length ping (required for Slack's RTM API). Previously it had no effect.CancellationToken is honored. The token passed to WebsocketConnectObservable is now forwarded to the underlying connection. Previously it was ignored on that overload.Security hardening
MaxFrameSize property (default 16 MiB) bounds both a single incoming frame and a fully reassembled fragmented message, preventing a malicious server from exhausting memory. See Limiting incoming message size.Origin, and subprotocol values containing CR, LF, or NUL are now rejected, preventing HTTP header injection / request smuggling.CheckCertificateRevocation property (default true) enables TLS revocation checking. See Certificate revocation.Breaking changes
CheckCertificateRevocation now defaults to true (TLS revocation checking is performed). If your environment cannot reach the certificate's OCSP/CRL endpoints, set it to false.
ClientWebSocketRx is now a class instead of a record. A connection holds live, disposable state, so value-equality and with semantics were both misleading and incorrect.
ClientWebSocketRx.TcpClient is now nullable (TcpClient?). Leave it unset to have the client create one with the address family matching the target URI and dispose it automatically, or supply your own and control its lifetime via HasTransferSocketLifeCycleOwnership.
Internal cleanup
Version 8.0 includes several significant improvements:
MessageWebsocketRx to ClientWebSocketRx to better reflect its purpose.Version 8.0 provides better control over the TCP socket lifecycle through the HasTransferSocketLifeCycleOwnership property:
var client = new ClientWebSocketRx
{
TcpClient = tcpClient, HasTransferSocketLifeCycleOwnership = true
}; // When true, the WebSocket client will dispose the TCP client
When set to true, the WebSocket client will take ownership of disposing the TCP client when the WebSocket client is disposed.
The client ping feature enables the WebSocket client to send ping messages at predefined intervals:
var websocketConnectionObservable =
client.WebsocketConnectWithStatusObservable(
uri: WebsocketServerUri,
hasClientPing: true, // default is false.
clientPingInterval: TimeSpan.FromSeconds(20), // default is 30 seconds.
clientPingMessage: "my ping message"); // default no message when set to null.
For advanced scenarios, use the SendPing method on the ISender interface for full control over ping messages.
The library supports the ws, wss, http, and https URI schemes. You can extend the supported schemes by overriding the IsSecureConnectionScheme method of the ClientWebSocketRx class. The default implementation looks like this:
public virtual bool IsSecureConnectionScheme(Uri uri) =>
uri.Scheme switch
{
"ws" or "http" => false,
"https" or "wss"=> true,
_ => throw new ArgumentException("Unknown Uri type.")
};
Instantiate the ClientWebSocketRx class:
var client = new ClientWebSocketRx {
IgnoreServerCertificateErrors = true,
Headers = new Dictionary<string, string> { { "Pragma", "no-cache" }, { "Cache-Control", "no-cache" } },
TlsProtocolType = SslProtocols.Tls12 };
You can also provide your own TcpClient for greater control:
TcpClient tcpClient = new() { LingerState = new LingerOption(true, 0) };
var client = new ClientWebSocketRx { TcpClient = tcpClient, HasTransferSocketLifeCycleOwnership = false };
Note:
- If the TcpClient is not connected already the library will connect it.
- The TcpClient will not be disposed automatically when passed in using the constructor unless
HasTransferSocketLifeCycleOwnership = trueis set.- A supplied
TcpClientcan only serve one connection. Its socket is closed when the connection tears down, and .NET sockets cannot be reconnected. If you use reconnect patterns such asRetry()/Repeat(), leaveTcpClientunset so every attempt gets a fresh socket.
To connect and observe WebSocket communication:
using System.Reactive.Disposables;
using System.Reactive.Linq;
using CompositeDisposable disposables = new();
Func<IObservable<(IDataframe dataframe, ConnectionStatus state)>> connect = () =>
client.WebsocketConnectWithStatusObservable(
uri: new Uri("wss://ws.postman-echo.com/raw"),
hasClientPing: true,
clientPingInterval: TimeSpan.FromSeconds(10),
clientPingMessage: "ping message",
cancellationToken: cts.Token);
IDisposable connectionSubscription = Observable.Defer(connect)
.Retry()
.Repeat()
.DelaySubscription(TimeSpan.FromSeconds(5))
.Do(tuple =>
{
Console.ForegroundColor = (int)tuple.state switch
{
>= 1000 and <= 1999 => ConsoleColor.Magenta,
>= 2000 and <= 2999 => ConsoleColor.Green,
>= 3000 and <= 3999 => ConsoleColor.Cyan,
>= 4000 and <= 4999 => ConsoleColor.DarkYellow,
_ => ConsoleColor.Gray,
};
Console.WriteLine(tuple.state);
if (tuple.state == ConnectionStatus.DataframeReceived && tuple.dataframe is not null)
{
Console.WriteLine($"Received: {tuple.dataframe.Message}");
}
})
.Where(t => t.state == ConnectionStatus.WebsocketConnected)
// SendMessages() is your own method that uses client.Sender once connected.
.SelectMany(_ => Observable.FromAsync(_ => SendMessages()))
.Subscribe();
disposables.Add(connectionSubscription);
The observable pipeline above logs each connection state and prints received messages. It automatically retries and repeats the connection on errors and ensures resources are cleaned up via the CompositeDisposable.
Once connected, use the WebSocket sender interface to transmit messages:
// Get the sender
var sender = client.Sender;
// Send a simple text message
await sender.SendText("Test Single Frame");
// Send a multi-part message
await sender.SendText([ "Test ", "multiple ", "frames ", "message." ]);
// Send fragmented messages with control over the fragmentation process
await sender.SendText("Start ", OpcodeKind.Text, FragmentKind.First);
await sender.SendText("Continue... ", OpcodeKind.Continuation);
await sender.SendText("End", OpcodeKind.Text, FragmentKind.Last);
Control certificate validation behavior:
// Option 1: Ignore all certificate errors.
// โ ๏ธ WARNING: this disables ALL certificate validation and exposes the
// connection to man-in-the-middle attacks. Use it only for local testing
// against self-signed/expired certificates. NEVER enable it in production.
var client = new ClientWebSocketRx { IgnoreServerCertificateErrors = true };
// Option 2: Override the validation method for custom logic
public override bool ValidateServerCertificate(
object senderObject, X509Certificate certificate, X509Chain chain, SslPolicyErrors tlsPolicyErrors)
{
// Your custom validation logic here
// Fall back to base implementation
return base.ValidateServerCertificate(senderObject, certificate, chain, tlsPolicyErrors);
}
Revocation checking is performed during the TLS handshake by default
(CheckCertificateRevocation = true). If your environment cannot reach the
certificate's OCSP/CRL endpoints (e.g. some corporate proxies or air-gapped
networks), the handshake will fail; disable the check in that case:
var client = new ClientWebSocketRx { CheckCertificateRevocation = false };
This setting has no effect when IgnoreServerCertificateErrors is true.
By default the client rejects any incoming frame โ or any reassembled
fragmented message โ larger than 16 MiB, then tears down the connection. This
guards against a malicious or misbehaving server exhausting memory by declaring
a huge payload length or flooding fragments. Adjust or disable the limit with
MaxFrameSize:
// Cap incoming frames/messages at 4 MiB.
var client = new ClientWebSocketRx { MaxFrameSize = 4 * 1024 * 1024 };
// Or allow up to int.MaxValue bytes (no explicit limit โ not recommended
// when connecting to untrusted servers).
var unlimited = new ClientWebSocketRx { MaxFrameSize = 0 };
The RFC 6455 section defining how ping/pong works seems to be ambiguous on the question whether or not a pong must include the byte defining the length of data-frame, in the special case when there is no data and the length of the data is zero.
When testing against for instance the Postman WebSocket test server the data-frame byte is expected and should have the value 0 (zero), when there's no data in the data-frame.
However, when used with the slack.rtm API the byte should not be there at all in the case of no data in the data-frame, and if it is, the slack WebSocket server will disconnect.
To manage this length-byte issue, set the following property to true, in which case the zero-value length byte will not be added to the pong. This is required for Slack's RTM API and similar services:
// Required for Slack's RTM API
var client = new ClientWebSocketRx { ExcludeZeroApplicationDataInPong = true };
Slack RTM also requires application-level ping messages:
// Slack expects an application-level ping as a JSON text message.
// The id is your own correlation id; see "Sending Messages" above.
await sender.SendText("{\"id\": 1234, \"type\": \"ping\"}");
To further complicate matters the slack.rtm api seems to require a ping at the Slack application layer too.
For details read the Ping and Pong section of the slack.rtm API documentation
For Socket.IO servers:
This library has also been tested with socket.io.
var uri = new Uri($"http://{url}:{port}/socket.io/?EIO=4&transport=websocket");
var websocketObservable = client.WebsocketConnectWithStatusObservable(uri);
This will connect on the WebSocket layer with socket.io server.
To further connect on socket.io level see documentation. For instance, typically a text message with the content 40 needs to be sent right after the connection have been established. Also, some socket.io server implementations seem to be very sensitive to the encoding of the messages that are being send, and will disconnect immediately if receiving a data-frame with a text message that does not comply with the expected socket.io encoding protocol.
For more see here: WebSocket client not connecting to the socket.io server.
This library was developed using the following reference documentation:
This library targets .NET Standard 2.0, .NET Standard 2.1, .NET 8, .NET 9, and .NET 10. The CI/CD pipeline uses GitHub Actions to build, test, and publish packages.
To build locally you need the matching .NET SDKs installed. The .NET 10 SDK can build every target framework; with an older SDK only the target frameworks it supports will build.
For contributors and developers, please ensure your changes maintain compatibility with these target frameworks.
The test project links one library build at a time via the LibTfm property, so
every build โ including netstandard2.0 and netstandard2.1 โ can be exercised
on a single modern .NET runtime (a netstandard assembly runs fine on .NET 10).
Run the whole matrix with:
./test-all-tfms.sh # Debug
./test-all-tfms.sh Release # Release
Or test a single build directly:
dotnet test src/main/WebsocketClientLiteTest/WebsocketClientLiteTest.csproj -p:LibTfm=netstandard2.0
Thank you to all the developers who have used this library over the years, reported issues, submitted bug fixes, or made contributions to improve the library. Your feedback and support make open source development rewarding and educational.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 net5.0 was computed. net5.0-windows net5.0-windows was computed. net6.0 net6.0 was computed. net6.0-android net6.0-android was computed. net6.0-ios net6.0-ios was computed. net6.0-maccatalyst net6.0-maccatalyst was computed. net6.0-macos net6.0-macos was computed. net6.0-tvos net6.0-tvos was computed. net6.0-windows net6.0-windows was computed. net7.0 net7.0 was computed. net7.0-android net7.0-android was computed. net7.0-ios net7.0-ios was computed. net7.0-maccatalyst net7.0-maccatalyst was computed. net7.0-macos net7.0-macos was computed. net7.0-tvos net7.0-tvos was computed. net7.0-windows net7.0-windows was computed. net8.0 net8.0 is compatible. net8.0-android net8.0-android was computed. net8.0-browser net8.0-browser was computed. net8.0-ios net8.0-ios was computed. net8.0-maccatalyst net8.0-maccatalyst was computed. net8.0-macos net8.0-macos was computed. net8.0-tvos net8.0-tvos was computed. net8.0-windows net8.0-windows was computed. net9.0 net9.0 is compatible. net9.0-android net9.0-android was computed. net9.0-browser net9.0-browser was computed. net9.0-ios net9.0-ios was computed. net9.0-maccatalyst net9.0-maccatalyst was computed. net9.0-macos net9.0-macos was computed. net9.0-tvos net9.0-tvos was computed. net9.0-windows net9.0-windows was computed. net10.0 net10.0 is compatible. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 netcoreapp2.0 was computed. netcoreapp2.1 netcoreapp2.1 was computed. netcoreapp2.2 netcoreapp2.2 was computed. netcoreapp3.0 netcoreapp3.0 was computed. netcoreapp3.1 netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 netstandard2.0 is compatible. netstandard2.1 netstandard2.1 is compatible. |
| .NET Framework | net461 net461 was computed. net462 net462 was computed. net463 net463 was computed. net47 net47 was computed. net471 net471 was computed. net472 net472 was computed. net48 net48 was computed. net481 net481 was computed. |
| MonoAndroid | monoandroid monoandroid was computed. |
| MonoMac | monomac monomac was computed. |
| MonoTouch | monotouch monotouch was computed. |
| Tizen | tizen40 tizen40 was computed. tizen60 tizen60 was computed. |
| Xamarin.iOS | xamarinios xamarinios was computed. |
| Xamarin.Mac | xamarinmac xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos xamarinwatchos was computed. |
Showing the top 4 NuGet packages that depend on WebsocketClientLite.PCL:
| Package | Downloads |
|---|---|
|
CryptoExchangeClient
Clients for multiple cryptocurrency exchanges. All clients have a shared interface for common actions. |
|
|
SlackConnector_Demo
Demo version of SlackConnector initiates an open connection between you and the Slack api servers. SlackConnector uses web-sockets to allow real-time messages to be received and handled within your application. |
|
|
PureWebSockets_WebsocketLite
A cross platform WebSocket client library for .NET NetStandard core (backed by WebsocketClientLite). |
|
|
WebRTCme.Middleware
WebRTC middleware for .NET MAUI and Blazor applications. |
Showing the top 1 popular GitHub repositories that depend on WebsocketClientLite.PCL:
| Repository | Stars |
|---|---|
|
melihercan/WebRTCme
A cross-platform framework for adding WebRTC support to .NET MAUI, Blazor, and Desktop applications by using a single unified .NET/C# API.
|
| Version | Downloads | Last Updated |
|---|---|---|
| 9.0.2 | 92 | 6/11/2026 |
| 9.0.1 | 99 | 6/11/2026 |
| 9.0.0 | 103 | 6/6/2026 |
| 8.3.0 | 365 | 11/16/2025 |
| 8.2.0 | 327 | 7/10/2025 |
| 8.1.0 | 346 | 3/31/2025 |
| 8.0.0 | 299 | 3/16/2025 |
| 7.3.2 | 1,699 | 1/5/2023 |
| 7.3.0 | 1,333 | 5/28/2022 |
| 7.2.2 | 1,240 | 4/24/2022 |
| 7.2.1 | 1,157 | 4/24/2022 |
| 7.1.1 | 1,117 | 4/22/2022 |
| 7.1.0 | 1,115 | 4/21/2022 |
| 7.0.7 | 1,128 | 4/20/2022 |
| 7.0.6 | 1,575 | 4/18/2022 |
| 7.0.5 | 1,124 | 4/18/2022 |
| 7.0.4 | 1,146 | 4/18/2022 |
v9.0.2: Fixes a race in the 9.0.1 close-on-unsubscribe feature where disposing the subscription immediately after WebsocketConnected could skip the close handshake. Includes all 9.0.1 changes: close handshake on unsubscribe; fail-fast handshake errors with the server's status code; send failures throw at the await site; final Disconnected status delivered; new NegotiatedSubprotocols property; NuGet packaging fix; reconnect requires leaving TcpClient unset. See README for details.