Skip to content

[New article]: Explanation of how to use ClientWebSocket to manage typical client websocket connection #43001

Open

Description

Proposed topic or title

Using ClientWebSocket to manage client web socket connections

Location in table of contents.

https://learn.microsoft.com/en-us/dotnet/fundamentals/networking/websockets

Reason for the article

Problem

As discussed here, there exists no proper documentation for the typical usage of ClientWebSocket. The only documents I have found provide minimal instruction:

The first official DotNet page is barren and entirely useless for any practical purpose. The Xamarin example at least is somewhat helpful though barely rudimentary and lacking. The StackOverflow link shows some idea for setting up a system as well (although the example given there accomplishes nothing and just spams random noise to the server).

I was therefore suggested to make a request here for better documentation.

Requirements

A typical usage of ClientWebSocket should include:

  • Create a ClientWebSocket object
  • Connect it to a websocket server
  • Start a receive loop for receiving messages based on types (or at least for basic text messages)
  • Trigger events that can be subscribed to for typical occurrences like: connected, message received, disconnect, message sent, error
  • Offer a basic way to attempt automatically reconnection after timeout if lost connection.

I think Microsoft should have a basic code explanation of this.

Why

Many people have been forced to use unnecessary and abandoned packages like:

  • WebSocketSharp which is bizarre and shows fraudulent and "sketchy" updates as per posts here and here
  • WebSocket.Client which is a nice package from the seem of things but is just a wrapper around ClientWebSocket to fix the fact that there is no proper documentation (and has millions of downloads because it seems no one otherwise knows how to use ClientWebSocket).

Example

I managed to work together this rough functional class so far, though I am not yet sure of how to best trigger or manage the events for disconnection, reconnection, etc. or whether I have composed this as it is intended. I just put this together from the StackOverflow example and then the Xamarin page when I found that.

I would love to see what Microsoft actually intends us to be doing or some more efficient or clean architecture so events are triggered in the right places (so nothing is missed) and we can use it well. It could be a static class or an object (makes no difference) - I just went static as I started from the static StackOverflow example. Probably non-static is better.

Class

public static class WebSocketClient {

    private static int sendChunkSize = 256;
    private static int receiveChunkSize = 1024 * 4;
    public static event Action connectedEvent = null; // could benefit from other events for disconnect, message received, message sent, etc.

    public static ClientWebSocket webSocket;
    public static bool receiveRunning = false; //was just for my own testing purposes


    public static async Task Connect(string uri) { //cannot await this when it is run as this function runs until websocket closes

        webSocket = null;         //rather than this, can create webSocket if not already created otherwise no need to recreate it...

        try {
            webSocket = new ClientWebSocket();
            await webSocket.ConnectAsync(new Uri(uri), default);
            await Receive(); //working
            Debug.WriteLine("WEB SOCKET CLOSED"); //won't hit here until connection is lost or failed, I think
        }
        catch (Exception ex) {
            Debug.WriteLine("Exception: {0}", ex);
        }
        finally {
            if (webSocket != null) {
                Debug.WriteLine("DISPOSE WEBSOCKET");
                webSocket.Dispose();
                webSocket = null;

            }
            Debug.WriteLine("WebSocket closed.");
        }
    }

    public static async Task Send(string message) {
        
        if (webSocket.State == WebSocketState.Open) {
            
            Debug.WriteLine("ABOUT TO SEND WEBSOCKET MESSAGE");
            byte[] array = Encoding.UTF8.GetBytes(message);
            //var segment = new ArraySegment<byte>(array); //redundant line, not needed
            await webSocket.SendAsync(array, WebSocketMessageType.Text, true, CancellationToken.None); //must be 'true" in third argument or doesn't run 
            Debug.WriteLine("SENT MESSAGE");
      
        }
    }

    private static async Task Receive() {
        Debug.WriteLine("START RECEIVE");

        connectedEvent?.Invoke(); //is this the right place to invoke this? Need to ensure receive loop is running (or about to run) so it catches any replies to messages sent on this event
        Debug.WriteLine("CONNECTED EVENT DONE");

        while (webSocket!=null && webSocket.State == WebSocketState.Open) {
            receiveRunning = true;
            Debug.WriteLine("START WHILE LOOP");

            ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[receiveChunkSize]);
            var result = await webSocket.ReceiveAsync(buffer, CancellationToken.None);

            if (result.MessageType == WebSocketMessageType.Close) {
                Debug.WriteLine("CLOSE WEBSOCKET STATE: " + webSocket.State.ToString());
                await closeConnection();
            }
            else {
                Debug.WriteLine("RECEIVED SOMETHING");
                if (result.MessageType != WebSocketMessageType.Text)
                    break;
                var messageBytes = buffer.Skip(buffer.Offset).Take(result.Count).ToArray();
                string receivedMessage = Encoding.UTF8.GetString(messageBytes);
                Debug.WriteLine("Received: " + receivedMessage);

            }
        }
        Debug.WriteLine("RECEIVE ENDED");
    }

    async static Task ReadMessage() { 
        WebSocketReceiveResult result;
        var message = new ArraySegment<byte>(new byte[4096]);
        do {
            result = await webSocket.ReceiveAsync(message, CancellationToken.None);
            if (result.MessageType != WebSocketMessageType.Text)
                break;
            var messageBytes = message.Skip(message.Offset).Take(result.Count).ToArray();
            string receivedMessage = Encoding.UTF8.GetString(messageBytes);
            Debug.WriteLine("Received: " + receivedMessage);
        }
        while (!result.EndOfMessage); 
    }

    public static async Task closeConnection() {
        await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Client closed", CancellationToken.None); //does this just disconnect or what?
    }
    

Usage

The above class can be used like this:

string url = "ws://localhost:4000/websocket";

WebSocketClient.connectedEvent += async delegate {
    Debug.WriteLine("CONNECTED STATIC WEBSOCKET");

    Debug.WriteLine("SOCKET STATUS: | " + WebSocketClient.webSocket.State.ToString() + " Is receive running? " + WebSocketClient.receiveRunning);

    await WebSocketClient.Send("ping");
    Debug.WriteLine("SENT WEBSOCKET MESSAGE");
};
WebSocketClient.Connect(url); 

Request

The above does work. But I would love to know what Microsoft engineers are actually intending rather than just guessing in terms of adding the further events, re-connections, or other message types.

There is no need for people to be using abandoned broken glitchy third party systems when we already have a functional websocket client class in .NET. We just need some proper documentation and a clear code example of a working class to handle the basic needs almost everyone will have.

Thanks for any help.

Article abstract

How to use WebSocketClient to handle websockets from .NET client applications.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Labels

    Pri3doc-ideaIndicates issues that are suggestions for new topics [org][type][category]

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions