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

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

Related

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);

Websocket connection - not receiving message after some time C#

I built a websocket server in C#. Relevant code is below. The problem is when the if (result.EndOfMessage) is called, the websocket stops listening for any more messages. If any message comes later on, then that message isnt recieved from the client.
How can I receive the messages after a while from the client?
public async Task Listen(int port)
{
try
{
IPAddress serverAddress = IPAddress.Parse("127.0.0.1"); // localhost
_listener = new TcpListener(serverAddress, port);
_listener.Start();
while (true)
{
TcpClient tcpClient = await _listener.AcceptTcpClientAsync();
await Task.Run(() =>
ProcessTcpClientAsync(tcpClient).ConfigureAwait(false));
}
}
catch (SocketException ex)
{
throw new Exception(message, ex);
}
}
private async Task<string> ProcessTcpClientAsync(TcpClient tcpClient)
{
CancellationTokenSource source = new CancellationTokenSource();
try
{
if (_isDisposed)
{
return string.Empty;
}
// get a secure or insecure stream
NetworkStream stream = tcpClient.GetStream();
WebSocketHttpContext context = await _webSocketServerFactory.ReadHttpHeaderFromStreamAsync(stream);
if (context.IsWebSocketRequest)
{
WebSocket webSocket = await _webSocketServerFactory.AcceptWebSocketAsync(context);
await RespondToWebSocketRequestAsync(webSocket, CancellationToken.None);
}
}
catch (ObjectDisposedException ex)
{
// do nothing. This will be thrown if the Listener has been stopped
}
catch (Exception ex)
{
}
}
public async Task RespondToWebSocketRequestAsync(WebSocket webSocket, CancellationToken token)
{
ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[BUFFER_SIZE]);
while (true)
{
WebSocketReceiveResult result = await webSocket.ReceiveAsync(buffer, token);
if (result.MessageType == WebSocketMessageType.Close)
{
break;
}
if (result.Count > BUFFER_SIZE)
{
await webSocket.CloseAsync(WebSocketCloseStatus.MessageTooBig,
$"Web socket frame cannot exceed buffer size of {BUFFER_SIZE:#,##0} bytes. Send multiple frames instead.",
token);
break;
}
if (result.EndOfMessage)
{
break;
}
// Process data
SomeMethod();
}
}

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);
}

Twitter stream with web socket MatchingTweetReceived not triggered in twitterinvl

I am creating a twiiter stremaing MVC controller. The controller method is implemented as a web socket.
Ref: Connecting with websockets to windows azure using MVC controllerenter link description here
I want to display the twitter stream in the UI. I am using twitterinvl. My code is included below. The stream event MatchingTweetReceived are not getting triggered from the controller method.
Any help on this is highly appreciated.
Controller action method.
public ActionResult About()
{
if (ControllerContext.HttpContext.IsWebSocketRequest)
{
ControllerContext.HttpContext.AcceptWebSocketRequest(DoTalking);
}
return new HttpStatusCodeResult(HttpStatusCode.SwitchingProtocols);
}
Rest of the code
public async Task DoTalking(AspNetWebSocketContext context)
{
try
{
WebSocket socket = context.WebSocket;
while (true)
{
var buffer = new ArraySegment<byte>(new byte[1024]);
WebSocketReceiveResult result = await socket.ReceiveAsync(buffer, CancellationToken.None);
if (socket.State == WebSocketState.Open)
{
string userMessage = Encoding.UTF8.GetString(buffer.Array, 0, result.Count);
string outmessage = "Empty";
//userMessage = "You sent: " + userMessage + " at " + DateTime.Now.ToLongTimeString();
Auth.SetUserCredentials("<<credential>>", "<<credential>>", "<<credential>>", "<<credential>>");
var stream = Stream.CreateFilteredStream();
stream.AddTrack("enterhash");
stream.MatchingTweetReceived += (sender, theTweet) =>
{
outmessage = theTweet.Tweet.CreatedBy.ToString() + theTweet.Tweet.CreatedAt + theTweet.Tweet;
buffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(userMessage));
//Console.WriteLine($"Tweet: {theTweet.Tweet.CreatedAt} {theTweet.Tweet}");
socket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None);
};
stream.StartStreamMatchingAllConditions();
Trace.WriteLine(outmessage);
//await socket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None);
}
else
{
break;
}
}
}
catch (Exception ex)
{
}
}

C# - TLS / SSL Websockets using fleck client

Im trying to make a TLS/SSL websocket connection using Fleck lib.
https://github.com/statianzo/Fleck (0.9.8.25)
now i got the server startet .
but when a client connects i get the following message.
28-02-2014 19:16:15 [Info] Server started at wss://localhost:8081
28-02-2014 19:18:51 [Debug] Client connected from 127.0.0.1:62543
28-02-2014 19:18:51 [Debug] Authenticating Secure Connection
28-02-2014 19:18:52 [Debug] 0 bytes read. Closing.
anybody got an idea of what im doing wrong ?
Browser: Chrome, version : 33.0.1750.117
class Program
{
private static UTF8Encoding encoding = new UTF8Encoding();
static void Main(string[] args)
{
Connect("wss://localhost:8081").Wait();
}
public static async Task Connect(string uri)
{
Thread.Sleep(1000);
ClientWebSocket webSocket = null;
X509Certificate2 certif = new X509Certificate2(#"QtrackerServer_TemporaryKey.pfx", "jihedgam");
webSocket.Options.ClientCertificates.Equals(certif);
try
{
webSocket = new ClientWebSocket();
await webSocket.ConnectAsync(new Uri(uri), CancellationToken.None);
await Task.WhenAll(Receive(webSocket), Send(webSocket));
}
catch (Exception ex)
{
Console.WriteLine("Exeption: {0}", ex);
}
finally
{
if (webSocket != null)
webSocket.Dispose();
Console.WriteLine();
Console.WriteLine("WebSocket closed.");
}
}
private static async Task Send(ClientWebSocket webSocket)
{
while (webSocket.State == WebSocketState.Open)
{
Console.WriteLine("Write some to send over to server..");
string stringtoSend = Console.ReadLine();
byte[] buffer = encoding.GetBytes(stringtoSend);
await webSocket.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Binary, false, CancellationToken.None);
Console.WriteLine("Send: " + stringtoSend);
await Task.Delay(1000);
}
}
private static async Task Receive(ClientWebSocket webSocket)
{
byte[] buffer = new byte[1024];
while (webSocket.State == WebSocketState.Open)
{
buffer = new byte[1024];
var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
if (result.MessageType == WebSocketMessageType.Close)
{
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
}
else
{
Console.WriteLine("Recive: " + Encoding.UTF8.GetString(buffer).TrimEnd('\0'));
}
}
}
}

Categories

Resources