c# ClientWebSocket creating mulitple websocket connections - c#

I'm working on a .net core project where I need to create multiple websocket connections to a ws server using the ClientWebSocket class.
Below is the code to connect to the server and I am able to receive data in the while loop. However, none of the code after this works until the websocket is closed.
How do I create multiple websocket clients? Do I create a new long running Task everytime I need to create a new ws client?
Thank you in advance
using (ClientWebSocket ws = new ClientWebSocket())
{
await ws.ConnectAsync(new Uri(url), CancellationToken.None);
var msgbuf = new ArraySegment<byte>(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(data)));
await ws.SendAsync(msgbuf, WebSocketMessageType.Text, true, CancellationToken.None);
var buffer = WebSocket.CreateClientBuffer(4096, 4096);
while (ws.State != WebSocketState.Closed && !cancellationToken.IsCancellationRequested)
{
var receiveResult = await ws.ReceiveAsync(buffer, cancellationToken);
// if the token is cancelled while ReceiveAsync is blocking, the socket state changes to aborted and it can't be used
if (!cancellationToken.IsCancellationRequested)
{
// the server is notifying us that the connection will close; send acknowledgement
if (ws.State == WebSocketState.CloseReceived && receiveResult.MessageType == WebSocketMessageType.Close)
{
Console.WriteLine($"\nAcknowledging Close frame received from server");
await ws.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "Acknowledge Close frame", CancellationToken.None);
}
// display text or binary data
if (ws.State == WebSocketState.Open && receiveResult.MessageType != WebSocketMessageType.Close)
{
if (receiveResult.Count > 0)
{
string message = "";
if (receiveResult.EndOfMessage == false)
{
message += Encoding.UTF8.GetString(buffer.Array, 0, receiveResult.Count);
}
else
{
message += Encoding.UTF8.GetString(buffer.Array, 0, receiveResult.Count);
HandleMessage(message, cancellationTokenSource);
message = "";
}
}
}
}
}
Console.WriteLine($"Ending processing loop in state {ws.State}");
return ws;
}

Related

ispossible create websocket server connected to another websocket?

I need WebSocket code for implement structure of my client. I will create WebSocket server for my client with receive by client from ex: binance websocket
Just part of connect to binance websocket need implement.
ASP.NET Core 5 C#
private async Task Echo(HttpContext context, WebSocket webSocket)
{
var buffer = new byte[1024 * 4];
WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
while (!result.CloseStatus.HasValue)
{
await webSocket.SendAsync(new ArraySegment<byte>(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None);
result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
}
await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
}
I did something like that. I created 2 WebSocket servers, one acting as a proxy to the other. You can check it out, maybe it works for you or at least you can find some answers there. It is a bit disordered because I did it as a research, so ask me if you need more info.
https://github.com/RenanDiaz/WebSockets
public override async Task OnConnected(WebSocket socket)
{
await base.OnConnected(socket);
if (_client == null)
{
_client = new ClientWebSocket();
await _client.ConnectAsync(new Uri("ws://localhost:5001/chat"), CancellationToken.None);
var thread = new Thread(new ThreadStart(ReceiveMessageFromAPIServer));
thread.Start();
}
}
private async void ReceiveMessageFromAPIServer()
{
var buffer = new byte[1024 * 4];
while (_client != null)
{
var result = await _client.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
if (result.MessageType == WebSocketMessageType.Close)
{
await _client.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);
break;
}
var messageString = Encoding.UTF8.GetString(buffer, 0, result.Count);
var message = JsonConvert.DeserializeObject<IncomingAPIServerMessage>(messageString);
await SendMessageToAll(message);
}
}

Websocket.ReceiveAsync causing a crash when a client disconnects without cancelling connection

DotNet Core 3.1 (running either Kestral or IIS)
I have the following loop, running in a Task for each connected client
using (var ms = new MemoryStream())
{
do
webSocketReceiveResult = await socket.ReceiveAsync(buffer, CancellationToken.None);
ms.Write(buffer.Array, buffer.Offset, webSocketReceiveResult.Count);
}
while (!webSocketReceiveResult.EndOfMessage);
When a client abruptly exits, network failure, crash or whatever and doesnt have a chance to close its connection, it crashes the whole class, and all tasks running. The webserver is still up and will accept all new connections, but all existing connections are terminated.
The error is : 'The remote party closed the WebSocket connection without completing the close handshake.'
which is expected, but I cannot try-catch it to preserve the rest of the connections?
Complete method here:
private static async Task SocketProcessingLoopAsync(ConnectedClient client)
{
_ = Task.Run(() => client.BroadcastLoopAsync().ConfigureAwait(false));
var socket = client.Socket;
var loopToken = SocketLoopTokenSource.Token;
var broadcastTokenSource = client.BroadcastLoopTokenSource; // store a copy for use in finally block
string sessionName = "";
string command = "";
int commandCounter = 0;
WebSocketReceiveResult webSocketReceiveResult = null;
try
{
var buffer = WebSocket.CreateServerBuffer(4096);
while (socket.State != WebSocketState.Closed && socket.State != WebSocketState.Aborted && !loopToken.IsCancellationRequested)
{
// collect all the bytes incoming
using (var ms = new MemoryStream())
{
do
{
webSocketReceiveResult = await socket.ReceiveAsync(buffer, CancellationToken.None);
ms.Write(buffer.Array, buffer.Offset, webSocketReceiveResult.Count);
}
while (!webSocketReceiveResult.EndOfMessage);
//var receiveResult = await client.Socket.ReceiveAsync(buffer, loopToken);
// if the token is cancelled while ReceiveAsync is blocking, the socket state changes to aborted and it can't be used
if (!loopToken.IsCancellationRequested)
{
// the client is notifying us that the connection will close; send acknowledgement
if (client.Socket.State == WebSocketState.CloseReceived && webSocketReceiveResult.MessageType == WebSocketMessageType.Close)
{
Console.WriteLine($"Socket {client.SocketId}: Acknowledging Close frame received from client");
broadcastTokenSource.Cancel();
await socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "Acknowledge Close frame", CancellationToken.None);
// the socket state changes to closed at this point
}
if (client.Socket.State == WebSocketState.Open)
{
if (webSocketReceiveResult.MessageType == WebSocketMessageType.Text)
{
// here we receive text instructioons from the clients
using (StreamReader reader = new StreamReader(ms, Encoding.UTF8))
{
ms.Seek(0, SeekOrigin.Begin);
command = reader.ReadToEnd().ToLower();
}
// var command = Encoding.UTF8.GetString(buffer.Array, 0, webSocketReceiveResult.Count).ToLower();
if (command.Contains("session:"))
{
// assign the client to a session, and inform them of their position in it
sessionName = command.Replace("session:", "");
if (!ClientControl.Sessions.ContainsKey(sessionName))
{
ClientControl.Sessions.TryAdd(sessionName, new BlockingCollection<ConnectedClient>());
}
ClientControl.Sessions[sessionName].Add(client);
// broadcast the collection count
Broadcast(ClientControl.Sessions[sessionName].Count.ToString(), client, true, sessionName);
Broadcast("Number of clients " + ClientControl.Sessions[sessionName].Count.ToString(), client, false, sessionName);
Broadcast("Number of clients " + ClientControl.Sessions[sessionName].Count.ToString(), client, true, sessionName);
}
else if (command.Contains("status:"))
{
string output = "<br/><h1>Sessions:</h1><br/><br/>";
foreach (var session in ClientControl.Sessions)
{
output += session.Key + " Connected Clients: " + session.Value.Count.ToString() + "<br/>";
}
Broadcast(output, client, true, "");
}
else if (command.Contains("ping"))
{
Console.WriteLine(command + " " + DateTime.Now.ToString() + " " + commandCounter);
}
}
else
{
// we just mirror what is sent out to the connected clients, depending on the session
Broadcast(ms.ToArray(), client, sessionName);
commandCounter++;
}
}
}
}// end memory stream
}
}
catch (OperationCanceledException)
{
// normal upon task/token cancellation, disregard
}
catch (Exception ex)
{
Console.WriteLine($"Socket {client.SocketId}:");
Program.ReportException(ex);
}
finally
{
broadcastTokenSource.Cancel();
Console.WriteLine($"Socket {client.SocketId}: Ended processing loop in state {socket.State}");
// don't leave the socket in any potentially connected state
if (client.Socket.State != WebSocketState.Closed)
client.Socket.Abort();
// by this point the socket is closed or aborted, the ConnectedClient object is useless
if (ClientControl.Sessions[sessionName].TryTake(out client))
socket.Dispose();
// signal to the middleware pipeline that this task has completed
client.TaskCompletion.SetResult(true);
}
}
Ok, couldn't get this to work, I have re-written the loop to look like this, it exits cleanly when a client hangs
do
{
cancellationTokenSource = new CancellationTokenSource(10000);
task = socket.ReceiveAsync(buffer, loopToken);
while (!task.IsCompleted && !SocketLoopTokenSource.IsCancellationRequested)
{
await Task.Delay(2).ConfigureAwait(false);
}
if (socket.State != WebSocketState.Open || task.Status != TaskStatus.RanToCompletion)
{
if (socket.State == WebSocketState.CloseReceived)
{
Console.WriteLine($"Socket {client.SocketId}: Acknowledging Close frame received from client");
broadcastTokenSource.Cancel();
await socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "Acknowledge Close frame", CancellationToken.None);
ClientControl.Sessions[sessionName].TryTake(out client);
}
Console.WriteLine("Client Left " + client.SocketId);
break;
}
ms.Write(buffer.Array, buffer.Offset, task.Result.Count);
}
while (!task.Result.EndOfMessage);

Basic WebSocket Chat

My objective is to create a basic chat application using web sockets.
I've got a client setup, however I am getting a specific exception every time I try to receive text on the client's side specifically.
The exception I am getting is the following:
The buffer type '166' is invalid. Valid buffer types are: 'Close', 'BinaryFragment', 'BinaryMessage', 'UTF8Fragment', 'UTF8Message'
I get this exception on the following line of code var socketResult = await _socket.ReceiveAsync(segment, CancellationToken.None); in the below code:
private readonly ClientWebSocket_socket;
private async Task ListenAsync(Action<string> textReceived)
{
while (_socket.State == WebSocketState.Open || _socket.State == WebSocketState.CloseSent)
{
var buffer = new byte[1024];
var segment = new ArraySegment<byte>(buffer, 0, buffer.Length);
var socketResult = await _socket.ReceiveAsync(segment, CancellationToken.None); // problem occurs here
if (socketResult.MessageType == WebSocketMessageType.Close)
{
await _socket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
break;
}
var text = Encoding.UTF8.GetString(buffer, 0, socketResult.Count);
textReceived(text);
}
}
what am I doing wrong? similar code works fine to receive the text on the server side.
EDIT:
The server listens fine and the _socket.ReceiveAsync method runs as expected. It waits until a message has been published and only then does it move on to reply. If I don't listen on the client side, Everything works. However when I start the server to listen for the client messages then start the client to listen, the client breaks. I have tried sending a message first from the client and then starting to listen for the response afterwards and I still experience the same thing.
Below is the method I use to listen on the server side.
private static WebSocket _socket;
public async Task ListenAsync()
{
while (_socket.State == WebSocketState.Open || _socket.State == WebSocketState.CloseSent)
{
var buffer = new byte[1024];
var segment = new ArraySegment<byte>(buffer, 0, buffer.Length);
var socketResult = await _socket.ReceiveAsync(segment, CancellationToken.None);
if (socketResult.MessageType == WebSocketMessageType.Close)
{
await _socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
break;
}
var text = Encoding.UTF8.GetString(buffer, 0, socketResult.Count);
await PublishAsync($"Server received the following text: {text} # {DateTime.Now:g}");
}
}
This is the method I use to publish messages from the server.
public async Task PublishAsync(string text)
{
var bytes = Encoding.UTF8.GetBytes(text);
var buffer = new ArraySegment<byte>(bytes, 0, bytes.Length);
await _socket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None);
}

C# WebSocket.SendAsync() randomly gets stuck

I have used the following code to send using the System.Net.WebSocket from WPF after socket has connected
try
{
Console.WriteLine("Sending...");
await _ws.SendAsync(new ArraySegment<byte>(packet.GetBytes()), WebSocketMessageType.Binary, true, CancellationToken.None);
Console.WriteLine("Sent...");
}
catch(Exception e)
{
Console.WriteLine(e.Message);
}
Calling code
if (!ValidateSettings()) return;
var hostname = TbHostname.Text.Trim();
var tcpport = TbTcpPort.Text.Trim();
_ws = new AchiWebSocket();
_streamTimer = new System.Threading.Timer(async s =>
{
var socket = (AchiWebSocket)s;
if (!_semaWs.Wait(0)) return;
UpdateBuffer();
var buffer = GetFrameBuffer();
var packet = new BinaryPacket(buffer.GetBytes());
if(socket.WebSocketState == null || socket.WebSocketState == WebSocketState.Closed)
await socket.Connect(new Uri("ws://" + hostname + "/superrfb"));
try
{
await socket.SendAsync(packet);
}
catch(Exception e)
{
Console.WriteLine("--" + e.Message);
}
finally
{
_semaWs.Release();
}
}, _ws, 0, 70);
Receiving Code (Asp.Net Core)
public async Task StartReceiveAsync(Action<Packet> onReceive)
{
WebSocketReceiveResult result;
var buffer = new byte[4 * 1024];
var stream = new MemoryStream();
do
{
result = await _ws.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
await stream.WriteAsync(buffer, 0, result.Count);
if (result.EndOfMessage)
{
var packet = BinaryPacket.FromRaw(stream.ToArray());
onReceive.Invoke(packet);
}
}
while (!result.CloseStatus.HasValue);
Dispose();
}
I do receive some data but the socket gets stuck in SendAsync after a few packets. It doesn't return even after waiting several minutes.
Ok I figured it out. My onReceive.Invoke() function was throwing exception sometimes causing the server to stop receiving. This made the receive buffer fill up and WebSocket.SendAsync to stuck and wait until the buffer is free. After wrapping the onReceive.Invoke() in try catch, the code is working fine now.
try
{
onReceive.Invoke(packet);
}
catch(Exception e)
{
Debug.WriteLine(e.Message);
}

How to gracefully close a two-way WebSocket in .Net

I have a WebSocket server that accepts a stream of binary data from a client and responds with another stream of text data for every 4MB read. The server uses IIS 8 and asp.net web api.
Server
public class WebSocketController : ApiController
{
public HttpResponseMessage Get()
{
if (!HttpContext.Current.IsWebSocketRequest)
{
return new HttpResponseMessage(HttpStatusCode.BadRequest);
}
HttpContext.Current.AcceptWebSocketRequest(async (context) =>
{
try
{
WebSocket socket = context.WebSocket;
byte[] requestBuffer = new byte[4194304];
int offset = 0;
while (socket.State == WebSocketState.Open)
{
var requestSegment = new ArraySegment<byte>(requestBuffer, offset, requestBuffer.Length - offset);
WebSocketReceiveResult result = await socket.ReceiveAsync(requestSegment, CancellationToken.None);
if (result.MessageType == WebSocketMessageType.Close)
{
// Send one last response before closing
var response = new ArraySegment<byte>(Encoding.UTF8.GetBytes("Server got " + offset + " bytes\n"));
await socket.SendAsync(response, WebSocketMessageType.Text, true, CancellationToken.None);
// Close
await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
break;
}
offset += result.Count;
if (offset == requestBuffer.Length)
{
// Regular response
var response = new ArraySegment<byte>(Encoding.UTF8.GetBytes("Server got 4194304 bytes\n"));
await socket.SendAsync(response, WebSocketMessageType.Text, true, CancellationToken.None);
offset = 0;
}
}
}
catch (Exception ex)
{
// Log and continue
}
});
return new HttpResponseMessage(HttpStatusCode.SwitchingProtocols);
}
}
The c# client uses the ClientWebSocket class to connect to the server and send requests. It creates a task for receiving responses from the server that runs in parallel with the request sending. When it is done sending the requests it calls CloseAsync on the socket and then waits for the Receive task to complete.
Client
using System;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace WebSocketClient
{
class Program
{
static void Main(string[] args)
{
try
{
CallWebSocketServer().Wait();
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
static async Task CallWebSocketServer()
{
using (ClientWebSocket socket = new ClientWebSocket())
{
await socket.ConnectAsync(new Uri("ws://localhost/RestWebController"), CancellationToken.None);
byte[] buffer = new byte[128 * 1024];
Task receiveTask = Receive(socket);
for (int i = 0; i < 1024; ++i)
{
await socket.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Binary, true, CancellationToken.None);
}
await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
receiveTask.Wait();
Console.WriteLine("All done");
}
}
static async Task Receive(ClientWebSocket socket)
{
try
{
byte[] recvBuffer = new byte[64 * 1024];
while (socket.State == WebSocketState.Open)
{
var result = await socket.ReceiveAsync(new ArraySegment<byte>(recvBuffer), CancellationToken.None);
Console.WriteLine("Client got {0} bytes", result.Count);
Console.WriteLine(Encoding.UTF8.GetString(recvBuffer, 0, result.Count));
if (result.MessageType == WebSocketMessageType.Close)
{
Console.WriteLine("Close loop complete");
break;
}
}
}
catch (Exception ex)
{
Console.WriteLine("Exception in receive - {0}", ex.Message);
}
}
}
}
The problem is that the client blocks at the CloseAsync call.
What would be the correct way of gracefully closing the WebSocket in this scenario?
Figured this out.
Server
Basically, I had to call the ClientWebSocket.CloseOutputAsync (instead of the CloseAsync) method to tell the framework no more output is going to be sent from the client.
await socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
Client
Then in the Receive function, I had to allow for socket state WebSocketState.CloseSent to receive the Close response from the server
static async Task Receive(ClientWebSocket socket)
{
try
{
byte[] recvBuffer = new byte[64 * 1024];
while (socket.State == WebSocketState.Open || socket.State == WebSocketState.CloseSent)
{
var result = await socket.ReceiveAsync(new ArraySegment<byte>(recvBuffer), CancellationToken.None);
Console.WriteLine("Client got {0} bytes", result.Count);
Console.WriteLine(Encoding.UTF8.GetString(recvBuffer, 0, result.Count));
if (result.MessageType == WebSocketMessageType.Close)
{
Console.WriteLine("Close loop complete");
break;
}
}
}
catch (Exception ex)
{
Console.WriteLine("Exception in receive - {0}", ex.Message);
}
}
i suggest you to look at these links:
asynchronous server:
https://msdn.microsoft.com/en-us/library/fx6588te%28v=vs.110%29.aspx
asynchronous client:
https://msdn.microsoft.com/en-us/library/bew39x2a(v=vs.110).aspx
recently i implementled something similar with these links as an example. the methods "BeginReceive" (for server) and "BeginConnect" (for client) start each a new thread. so there won't be anything that blocks

Categories

Resources