Description
openedon Oct 10, 2024
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:
- https://learn.microsoft.com/en-us/dotnet/fundamentals/networking/websockets
- https://devblogs.microsoft.com/xamarin/developing-real-time-communication-apps-with-websocket/
- https://stackoverflow.com/a/68284475/10305478
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.