WebsocketClientLite.PCL 7.3.0

.NET Standard 2.0
dotnet add package WebsocketClientLite.PCL --version 7.3.0
NuGet\Install-Package WebsocketClientLite.PCL -Version 7.3.0
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="WebsocketClientLite.PCL" Version="7.3.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add WebsocketClientLite.PCL --version 7.3.0
#r "nuget: WebsocketClientLite.PCL, 7.3.0"
#r directive can be used in F# Interactive, C# scripting and .NET Interactive. Copy this into the interactive tool or source code of the script to reference the package.
// Install WebsocketClientLite.PCL as a Cake Addin
#addin nuget:?package=WebsocketClientLite.PCL&version=7.3.0

// Install WebsocketClientLite.PCL as a Cake Tool
#tool nuget:?package=WebsocketClientLite.PCL&version=7.3.0

WebSocket Client Lite (Rx)

NuGet Badge

.NET Standard .NET Standard System.Reactive

CI/CD

Please star this project if you find it useful. Thank you.

A Lightweight Cross Platform WebSocket Client

This library is a ground-up implementation of the WebSocket specification (RFC 6544) - i.e., this implementation does not rely on the build-in WebSocket libraries in .NET.

The library allows developers additional flexibility, including the ability to establish secure wss websocket connections to websocket servers that have self-signing certificates, expired certificates etc. This capability should be used with care for obvious reasons, however it is useful for testing environments, closed local networks, local IoT set-ups etc.

Furthermore, this library utilize ReactiveX (aka Rx or Reactive Extensions). Although taking this dependency introduces an added learning curve, it is a learning curve worthwhile investing in, as it IMHO makes using and creating a library like this much more elegant compared to using traditional call-back or events based patterns etc.

New in version 7.0

At writing time, this library has been around for more than 6 years. The work represented in this repo was mainly initiated on a desire to learn and play around with the technologies involved.

Unsurprisingly, over the years learning and insights grew and eventually maintaining and looking back at the aging code-base became more and more painful for the ever more trained eye, hence I decided to redo most of it.

Version 7 is more or less a rewrite of 90+ % of the original code.

The version 7 NuGet package includes both a .NET Standard 2.0 package and a .NET Standard 2.1, with e .NET Standard 2.1 package having a few less dependencies.

Version 7.3

Version 7.3 introduces a new feature. When passing an existing TCP Socket Client it is not possible to pass the life-cycle ownership of the socket, which means that when the observable WebSocket connection is disposed, so will the socket connection and the TCP Socket client be disposed and the connection closed.

new MessageWebsocketRx(tcpClient, hasTransferTcpSocketLifeCycleOwnership: true)

Now With Client Ping

Version 7 introduces a client ping feature, which enabling the WebSocket client to send a ping message with a constant interval.

The clientPingMessage parameter is optional and the default value is null. The behavior for the null value is to not include any message, as part of the ping.

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.

It is only possible to use a string in this method. For more advanced scenarios, the ISender has a SendPing method that can be used for full control when sending client pings as string or as byte[].

New in version 6.4

Successfully tested with .NET 6.0.

Previously the library only accepted the ws and wss scheme. Now http and https is also supported.

To further extend supported schemes override the IsSecureConnectionScheme method of the MessageWebSocketRx class.

The virtual method 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.")
    };

New in version 6.3

  • Fixed bug related to connecting to IPv6 endpoints.
  • Updated System.Reactive to v5.0.0.
  • Successfully tested with .NET 5.0.
  • Updated Readme.

New in version 6.1.

Updates, stability and fundamental improvements to the library. See examples below for changes in usage.

New in version 6.0.

Simplifications and no longer relies on SocketLite but utilizes the cross-platform capabilities of .NET Standard 2.0+.

New in version 5.0.

From hereon and forward only .NET Standard 2.0+ is supported.

Usage

For a more detailed sample of using this library please see the console example app.

To instantiate WebSocket lite class

To use the WebSocket client create an instance of the class MessageWebsocketRx:

var websocketClient = new MessageWebsocketRx()
{
    IgnoreServerCertificateErrors = false,
    Headers = new Dictionary<string, string> {{ "Pragma", "no-cache" }, { "Cache-Control", "no-cache" }}
};

... or use the alternative constructor to pass your own TcpClient for more control of the configuration and the management of your TCP socket connection.

MessageWebSocketRx(TcpClient tcpClient)

Note: If the TcpClient is not connected the library will connect it. Also, the TcpClient will not be disposed automatically when passed in using the constructor, as it will in the case when no TcpClient is supplied.

To connect client to WebSocket server

To connect and observe websocket connection use WebsocketConnectionObservable:

var websocketConnectionObservable = 
    client.WebsocketConnectObservable(
        new Uri(WebsocketTestServerUrl)
);

... or use WebsocketConnectionWithStatusObservable to also observe connection status :

var websocketConnectionWithStatusObservable = 
    client.WebsocketConnectWithStatusObservable(
        new Uri(WebsocketTestServerUrl)
);

To control TLS/SSL certificate validation behavior

To control TLS/SSL Server certificate behavior, either use the IgnoreServerCertificateErrors parameter to ignore any issues with the certificate or override the ValidateServerCertificate method to your liking.

The existing virtual method implementation looks like this:

public virtual bool ValidateServerCertificate(
    object senderObject,
    X509Certificate certificate,
    X509Chain chain,
    SslPolicyErrors tlsPolicyErrors)
{
    if (IgnoreServerCertificateErrors) return true;

    return tlsPolicyErrors switch
    {
        SslPolicyErrors.None => true,
        SslPolicyErrors.RemoteCertificateChainErrors => 
            throw new Exception($"SSL/TLS error: {SslPolicyErrors.RemoteCertificateChainErrors}"),
        SslPolicyErrors.RemoteCertificateNameMismatch => 
            throw new Exception($"SSL/TLS error: {SslPolicyErrors.RemoteCertificateNameMismatch}"),
        SslPolicyErrors.RemoteCertificateNotAvailable => 
            throw new Exception($"SSL/TLS error: {SslPolicyErrors.RemoteCertificateNotAvailable}"),
        _ => throw new ArgumentOutOfRangeException(nameof(tlsPolicyErrors), tlsPolicyErrors, null),
    };
}

Working With Slack (And maybe also other WebSocket server implementations)

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 Postman WebSocket 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 the following property can be set to true, in which case the byte with the zero value will NOT be added to the pong. For instance like this:

var websocketClient = new MessageWebSocketRx
{
    ExcludeZeroApplicationDataInPong = true
}

To further complicate matters the slack.rtm api seems to require a ping at the Slack application layer too.

A simplified implementation of this could look like this, which obviously would need to be repeated in some interval to keep the slack connection going:

await _webSocket.SendText("{\"id\": 1234, // ID, see \"sending messages\" above\"type\": \"ping\",...}");

For details read the Ping and Pong section of the slack.rtm API documentation

Working with socket.io

This library has also been tested with socket.io.

A typical connection will look like this:

var websocketConnectionObservable = 
    client.WebsocketConnectWithStatusObservable(
        new Uri($"http://{url}:{port}/socket.io/?EIO=4&transport=websocket"));

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.

References:

The following documentation was utilized when writing this library:

Thank you !

Thank you to all the developers who've been using this library through the years, many of which that have reported issues or bugs, or made contributions and pull requests to make the library better and/or more capable. It is this interaction with all of you that makes sharing and learning fun.

Product Versions
.NET net5.0 net5.0-windows net6.0 net6.0-android net6.0-ios net6.0-maccatalyst net6.0-macos net6.0-tvos net6.0-windows net7.0 net7.0-android net7.0-ios net7.0-maccatalyst net7.0-macos net7.0-tvos net7.0-windows
.NET Core netcoreapp2.0 netcoreapp2.1 netcoreapp2.2 netcoreapp3.0 netcoreapp3.1
.NET Standard netstandard2.0 netstandard2.1
.NET Framework net461 net462 net463 net47 net471 net472 net48
MonoAndroid monoandroid
MonoMac monomac
MonoTouch monotouch
Tizen tizen40 tizen60
Xamarin.iOS xamarinios
Xamarin.Mac xamarinmac
Xamarin.TVOS xamarintvos
Xamarin.WatchOS xamarinwatchos
Compatible target framework(s)
Additional computed target framework(s)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (3)

Showing the top 3 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).

GitHub repositories (1)

Showing the top 1 popular GitHub repositories that depend on WebsocketClientLite.PCL:

Repository Stars
melihercan/WebRTCme
Providing WebRTC functionality to Blazor, Xamarin Forms and MAUI applications with a single common API.
Version Downloads Last updated
7.3.0 338 5/28/2022
7.2.2 240 4/24/2022
7.2.1 199 4/24/2022
7.1.1 207 4/22/2022
7.1.0 201 4/21/2022
7.0.7 200 4/20/2022
7.0.6 585 4/18/2022
7.0.5 203 4/18/2022
7.0.4 201 4/18/2022
6.4.0 239 3/20/2022
6.3.0 1,092 9/25/2021
6.2.0 18,905 10/27/2018
6.1.1 8,594 9/9/2018
6.1.0 636 9/7/2018
6.0.0 691 9/3/2018
5.1.1 768 8/24/2018
5.0.0 16,729 6/2/2018
4.2.5 2,348 4/8/2018
4.2.5-beta1 809 4/7/2018
4.2.3 1,149 3/23/2018
4.2.1 1,128 2/15/2018
4.2.0 801 2/11/2018
4.1.4 955 2/1/2018
4.1.2 1,017 10/23/2017
4.1.1 10,528 9/22/2017
4.0.6 7,536 5/13/2017
3.7.8 832 4/17/2017
3.7.4 820 4/17/2017
3.7.3 831 4/15/2017
3.7.0 861 3/12/2017
3.6.8 869 3/2/2017
3.6.6 1,005 2/14/2017
3.6.2 997 12/10/2016
3.6.0 1,063 11/24/2016
2.0.11 955 11/6/2016
1.6.2 1,005 10/15/2016
1.5.2 983 6/29/2016

Removed PCL in assembly name.
New feature: hasTransferTcpSocketLifeCycleOwnership.