MVC Core, Web Sockets and threading - c#

I am working on a solution that uses web socket protocol to notify client (web browser) when some event happened on the server (MVC Core web app). I use Microsoft.AspNetCore.WebSockets nuget.
Here is my client-side code:
$(function () {
var socket = new WebSocket("ws://localhost:61019/data/openSocket");
socket.onopen = function () {
$(".socket-status").css("color", "green");
}
socket.onmessage = function (message) {
$("body").append(document.createTextNode(message.data));
}
socket.onclose = function () {
$(".socket-status").css("color", "red");
}
});
When this view is loaded the socket request is immediately sent to the MVC Core application. Here is the controller action:
[Route("data")]
public class DataController : Controller
{
[Route("openSocket")]
[HttpGet]
public ActionResult OpenSocket()
{
if (HttpContext.WebSockets.IsWebSocketRequest)
{
WebSocket socket = HttpContext.WebSockets.AcceptWebSocketAsync().Result;
if (socket != null && socket.State == WebSocketState.Open)
{
while (!HttpContext.RequestAborted.IsCancellationRequested)
{
var response = string.Format("Hello! Time {0}", System.DateTime.Now.ToString());
var bytes = System.Text.Encoding.UTF8.GetBytes(response);
Task.Run(() => socket.SendAsync(new System.ArraySegment<byte>(bytes),
WebSocketMessageType.Text, true, CancellationToken.None));
Thread.Sleep(3000);
}
}
}
return new StatusCodeResult(101);
}
}
This code works very well. WebSocket here is used exclusively for sending and doesn't receive anything. The problem, however, is that the while loop keeps holding the DataController thread until cancellation request is detected.
Web socket here is bound to the HttpContext object. As soon as HttpContext for the web request is destroyed the socket connection is immediately closed.
Question 1: Is there any way that socket can be preserved outside of the controller thread?
I tried putting it into a singleton that lives in the MVC Core Startup class that is running on the main application thread. Is there any way to keep the socket open or establish connection again from within the main application thread rather than keep holding the controller thread with a while loop?
Even if it is deemed to be OK to hold up controller thread for socket connection to remain open, I cannot think of any good code to put inside the OpenSocket's while loop. What do you think about having a manual reset event in the controller and wait for it to be set inside the while loop within OpenSocket action?
Question 2: If it is not possible to separate HttpContext and WebSocket objects in MVC, what other alternative technologies or development patterns can be utilized to achieve socket connection reuse? If anyone thinks that SignalR or a similar library has some code allowing to have socket independent from HttpContext, please share some example code. If someone thinks there is a better alternative to MVC for this particular scenario, please provide an example, I do not mind switching to pure ASP.NET or Web API, if MVC does not have capabilities to handle independent socket communication.
Question 3: The requirement is to keep socket connection alive or be able to reconnect until explicit timeout or cancel request by the user. The idea is that some independent event happens on the server that triggers established socket to send data.
If you think that some technology other than web sockets would be more useful for this scenario (like HTML/2 or streaming), could you please describe the pattern and frameworks you would use?
P.S. Possible solution would be to send AJAX requests every second to ask if there was new data on the server. This is the last resort.

After lengthy research I ended up going with a custom middleware solution. Here is my middleware class:
public class SocketMiddleware
{
private static ConcurrentDictionary<string, SocketMiddleware> _activeConnections = new ConcurrentDictionary<string, SocketMiddleware>();
private string _packet;
private ManualResetEvent _send = new ManualResetEvent(false);
private ManualResetEvent _exit = new ManualResetEvent(false);
private readonly RequestDelegate _next;
public SocketMiddleware(RequestDelegate next)
{
_next = next;
}
public void Send(string data)
{
_packet = data;
_send.Set();
}
public async Task Invoke(HttpContext context)
{
if (context.WebSockets.IsWebSocketRequest)
{
string connectionName = context.Request.Query["connectionName"]);
if (!_activeConnections.Any(ac => ac.Key == connectionName))
{
WebSocket socket = await context.WebSockets.AcceptWebSocketAsync();
if (socket == null || socket.State != WebSocketState.Open)
{
await _next.Invoke(context);
return;
}
Thread sender = new Thread(() => StartSending(socket));
sender.Start();
if (!_activeConnections.TryAdd(connectionName, this))
{
_exit.Set();
await _next.Invoke(context);
return;
}
while (true)
{
WebSocketReceiveResult result = socket.ReceiveAsync(new ArraySegment<byte>(new byte[1]), CancellationToken.None).Result;
if (result.CloseStatus.HasValue)
{
_exit.Set();
break;
}
}
SocketHandler dummy;
_activeConnections.TryRemove(key, out dummy);
}
}
await _next.Invoke(context);
string data = context.Items["Data"] as string;
if (!string.IsNullOrEmpty(data))
{
string name = context.Items["ConnectionName"] as string;
SocketMiddleware connection = _activeConnections.Where(ac => ac.Key == name)?.Single().Value;
if (connection != null)
{
connection.Send(data);
}
}
}
private void StartSending(WebSocket socket)
{
WaitHandle[] events = new WaitHandle[] { _send, _exit };
while (true)
{
if (WaitHandle.WaitAny(events) == 1)
{
break;
}
if (!string.IsNullOrEmpty(_packet))
{
SendPacket(socket, _packet);
}
_send.Reset();
}
}
private void SendPacket(WebSocket socket, string packet)
{
byte[] buffer = Encoding.UTF8.GetBytes(packet);
ArraySegment<byte> segment = new ArraySegment<byte>(buffer);
Task.Run(() => socket.SendAsync(segment, WebSocketMessageType.Text, true, CancellationToken.None));
}
}
This middleware is going to run on every request. When Invoke is called it checks if it is a web socket request. If it is, the middleware checks if such connection was already opened and if it wasn't, the handshake is accepted and the middleware adds it to the dictionary of connections. It's important that the dictionary is static so that it is created only once during application lifetime.
Now if we stop here and move up the pipeline, HttpContext will eventually get destroyed and, since the socket is not properly encapsulated, it will be closed too. So we must keep the middleware thread running. It is done by asking socket to receive some data.
You may ask why we need to receive anything if the requirement is just to send? The answer is that it is the only way to reliably detect client disconnecting. HttpContext.RequestAborted.IsCancellationRequested works only if you constantly send within the while loop. If you need to wait for some server event on a WaitHandle, cancellation flag is never true. I tried to wait for HttpContext.RequestAborted.WaitHandle as my exit event, but it is never set either. So we ask socket to receive something and if that something sets CloseStatus.HasValue to true, we know that client disconnected. If we receive something else (client side code is unsafe) we will ignore it and start receiving again.
Sending is done in a separate thread. The reason is the same, it's not possible to detect disconnection if we wait on the main middleware thread. To notify the sender thread that client disconnected we use _exit synchronization variable. Remember, it is fine to have private members here since SocketMiddleware instances are saved in a static container.
Now, how do we actually send anything with this set up? Let's say an event occurs on the server and some data becomes available. For simplicity sake, lets assume this data arrives inside normal http request to some controller action. SocketMiddleware will run for every request, but since it is not web socket request, _next.Invoke(context) is called and the request reaches controller action which may look something like this:
[Route("ProvideData")]
[HttpGet]
public ActionResult ProvideData(string data, string connectionName)
{
if (!string.IsNullOrEmpty(data) && !string.IsNullOrEmpty(connectionName))
{
HttpContext.Items.Add("ConnectionName", connectionName);
HttpContext.Items.Add("Data", data);
}
return Ok();
}
Controller populates Items collection which is used to share data between components. Then the pipeline returns to the SocketMiddleware again where we check whether there is anything interesting inside the context.Items. If there is we select respective connection from the dictionary and call its Send() method that sets data string and sets _send event and allows single run of the while loop inside the sender thread.
And voila, we a have socket connection that sends on server side event. This example is very primitive and is there just to illustrate the concept. Of course, to use this middleware you will need to add the following lines in your Startup class before you add MVC:
app.UseWebSockets();
app.UseMiddleware<SocketMiddleware>();
Code is very strange and hopefully we'll be able to write something much nicer when SignalR for dotnetcore is finally out. Hopefully this example will be useful for someone. Comments and suggestions are welcome.

Related

Azure service bus (topic) subscription session not closing on request

[edit: I have reformulated and simplified my original question]
I am using Azure service bus topic/subscriptions with sessions and I am having issue closing sessions.
For background information, my application receives data from a topic subscription session (FIFO requirement) which I keep alive most of the time. Only once in a while we need to 'pause' the data flow momentarily.
When this data flow 'pause' is requested, we exit the subscription session and wait to be asked to open the session again.
// pseudo code
public class Test
{
public static async Task Me()
{
var client = new SubscriptionClient(
EndPoint,
Path,
Name,
TokenProvider,
TransportType.Amqp,
ReceiveMode.PeekLock,
new RetryExponential(
minimumBackoff: TimeSpan.FromSeconds(1),
maximumBackoff: TimeSpan.FromSeconds(30),
maximumRetryCount: 10));
// Setup consumer options
var sessionOptions = new SessionHandlerOptions(OnHandleExceptionReceived)
{
AutoComplete = false,
MessageWaitTimeout = TimeSpan.FromSeconds(10),
MaxConcurrentSessions = 1,
};
// Registration 1 - Start data flow
client.RegisterSessionHandler(OnMessageSessionAsync, sessionOptions);
// Wait 1 - Artificially wait for 'data flow pause' to kick in.
// For the sake of this example, we artificially give plenty
// of time to the message session handler to receive something
// and close the session.
Task.Wait(TimeSpan.FromSeconds(30));
// Registration 2 - Artificially 'unpause' data flow
client.RegisterSessionHandler(OnMessageSessionAsync, sessionOptions);
// Wait 2 - Artificially wait for 'pause' to kick in again
Task.Wait(TimeSpan.FromSeconds(30));
// Finally close client
await client.CloseAsync();
}
private static async Task OnMessageSessionAsync(IMessageSession session, Message message, CancellationToken cancellationToken)
{
try
{
await client.CompleteAsync(message.SystemProperties.LockToken);
// Process message .. It doesn't matter what it is,
// just that at some point I want to break away from session
if (bool.TryParse(message.UserProperties["SessionCompleted"] as string, out bool completed) && completed)
await session.CloseAsync(); // <-- This never works
}
catch (Exception e)
{
Console.WriteLine("OnMessageSessionAsync exception: {0}", e);
// Indicates a problem, unlock message in subscription.
await client.AbandonAsync(message.SystemProperties.LockToken);
}
}
private static Task OnHandleExceptionReceived(ExceptionReceivedEventArgs e)
{
var context = e.ExceptionReceivedContext;
Options.Logger?.LogWarning(e.Exception, new StringBuilder()
.AppendLine($"Message handler encountered an exception {e.Exception.GetType().Name}.")
.AppendLine("Exception context for troubleshooting:")
.AppendLine($" - Endpoint: {context.Endpoint}")
.AppendLine($" - Entity Path: {context.EntityPath}")
.Append($" - Executing Action: {context.Action}"));
return Task.CompletedTask;
}
}
Questions :
As previously stated, I have an issue exiting the session as calling session.CloseAsync() seems to be inoperative. Messages keep coming up even though I explicitly asked the session to stop.
Is that normal behavior that a topic session cannot be directly closed ? If so, why expose the call session.CloseAsync() at all ?
Can I actually close a session independently from a subscription connection ?
ps1: I based my code on the official sample made available on github.com by Microsoft. And although this example is based on queue session rather than a topic session, it seems logical to me that the behavior should be identical.
ps2: I drilled down what could be the reason on Microsoft.Azure.ServiceBus repository and I wonder if there is a variable initialization missing under the hood of MessageSession.OwnsConnection property ..

C# WebSockets Multicast notifications asynchronously

I have a Asp.NET core application. Startup.Configure() mainly contains this code
app.UseWebSockets();
app.Use(async (httpContext, next) =>
{
// If the request is a WebServerRequest, handle it as such ...
if (httpContext.WebSockets.IsWebSocketRequest)
{
ClientHandler h = new ClientHandler(httpContext);
if (h.IsWebsockOpen)
{
await h.Handle();
}
else
{
httpContext.Response.StatusCode = 400;
}
}
// ... otherwise just hand the request to the next element in chain
else
{
await next();
}
});
Inside h.Handle() the client is supposed to register with a ClientManager which in turn multicasts that a new client has connected like this
public async Task Multicast<T>(List<ClientHandler> l, Msg<T> m)
{
foreach (ClientHandler h in l)
{
if (h.IsWebsockOpen)
{
await h.webSocket.SendAsync(
System.Text.Encoding.UTF8.GetBytes(m.ToString()),
System.Net.WebSockets.WebSocketMessageType.Text,
true,
System.Threading.CancellationToken.None);
}
}
}
I am now wondering if that is safe to do. I can imagine a scenario where two clients connect at the same time resulting in h.webSocket.SendAsync being called twice simultaneously, which is not allowed as said in
https://learn.microsoft.com/en-us/dotnet/api/system.net.websockets.websocket.sendasync?view=netframework-4.8
Remarks
This operation will not block. The returned Task object will complete after the data has been sent on the WebSocket.
Exactly one send and one receive is supported on each WebSocket object in parallel.
Wraping the h.webSocket.SendAsync in a lock-statement seems to be impossible due to the await keyword.
How can I make my code safe? Related questions are either not using WebSockets or they use different frameworks for which mechanisms apply.
You can make use of a semaphore here, specifically SemaphoreSlim. I'd suggest making a SendAsync method on your ClientHandler class and piping all requests via that - ie call it from your Multicast method.
The content of your ClientHandler would then be something like:
class ClientHandler() {
private readonly SemaphoreSlim _sendLock;
public ClientHandler(HttpContext context) {
_sendLock = new SemaphoreSlim(1, 1);
//....
}
public async Task SendAsync(string msg) {
await _sendLock.WaitAsync();
try {
await webSocket.SendAsync(
System.Text.Encoding.UTF8.GetBytes(msg.ToString()),
System.Net.WebSockets.WebSocketMessageType.Text,
true,
System.Threading.CancellationToken.None);
} finally {
_sendLock.Release();
}
}
}
The SemaphoreSlim is IDisposable, so you'll need to take care of that, and its WaitAsync method has overloads for cancellation tokens and/or timeouts that might be appropriate for you to use.

Multiple connections with TcpClient, second connection always hangs/does nothing

So I have a TcpClient in a console app that is listening on port 9096. I want the client to be able to handle multiple connections (simultaneous or not). I also do not want to use Threads. I want to use async/await. I also need to be able to gracefully close the app during certain events, being careful not to lose any data. So I need a cancellation token. I have the code mostly working but there are two issues.
First, when the app starts listening and I send it data; everything works correctly as long as the sender is using the same initial connection to the app. Once a new connection (or socket I guess? not clear on the terminology) is established the app does not process the new data.
Second, when the terminate signal is given to the app and the token is canceled the app does not close. I am not getting any exceptions and I cannot figure out what I an doing wrong.
I have looked all over and cannot find an example of a TcpClient that uses async/await with a cancellation token. I also cannot find an example that I have been able to get working that correctly processes multiple connections, without using Threads or other complicated designs. I want the design as simple as possible with as little code as possible while still meeting my requirements. If using threads is the only way to do it I will, but I am soo close to getting it right I feel like I am just missing a little thing.
I am trying to figure this out at my wits end and have exhausted all my ideas.
EDIT: I moved the AcceptTcpClientAsync into the loop as suggested below and it did not change anything. The app functions the same as before.
Program.cs
class Program
{
private static List<Task> _listeners = new List<Task>();
private static readonly CancellationTokenSource cancelSource = new CancellationTokenSource();
static void Main(string[] args)
{
Console.TreatControlCAsInput = false;
Console.CancelKeyPress += (o, e) => {
Console.WriteLine("Shutting down.");
cancelSource.Cancel();
};
Console.WriteLine("Started, press ctrl + c to terminate.");
_listeners.Add(Listen(cancelSource.Token));
cancelSource.Token.WaitHandle.WaitOne();
Task.WaitAll(_listeners.ToArray(), cancelSource.Token);
}
}
Listen
public async Task Listen(CancellationToken token){
var listener = new TcpListener(IPAddress.Parse("0.0.0.0"), 9096);
listener.Start();
Console.WriteLine("Listening on port 9096");
while (!token.IsCancellationRequested) {
// Also tried putting AcceptTcpClientAsync here.
await Task.Run(async () => {
var client = await listener.AcceptTcpClientAsync();
using (var stream = client.GetStream())
using (var streamReader = new StreamReader(stream, Encoding.UTF8))
using (var streamWriter = new StreamWriter(stream, Encoding.UTF8)) {
while (!token.IsCancellationRequested) {
// DO WORK WITH DATA RECEIVED
vat data = await streamReader.ReadAsync();
await streamWriter.WriteLineAsync("Request received.");
}
}
});
}
Console.WriteLine("Stopped Accepting Requests.");
listener.Server.Close();
listener.Stop();
}
This is actually working the way you designed it, however you have only built to receive one connection. I am not going to write a full socket implementation for you (as this can get fairly in-depth). However, as for your main problem, you need to put the AcceptTcpClientAsync in the loop otherwise you won't get any more connections:
var cancellation = new CancellationTokenSource();
...
var listener = new TcpListener(...);
listener.Start();
try
{
while (!token.IsCancellationRequested)
{
var client = await listener.AcceptTcpClientAsync()
...
}
}
finally
{
listener.Stop();
}
// somewhere in another thread
cancellation.Cancel();
Update
I tried that and no behavior changed. Still does not pick up any
connection after the first.
await ...
while (!token.IsCancellationRequested) {
// DO WORK WITH DATA RECEIVED
It's obvious that AcceptTcpClientAsync will never get called again because you are awaiting the task. This method is what accepts the client, if you can't call it, you don't get any more clients.
You cannot block here, which is what you are doing. Please see some socket server examples to get a better idea of how to write a listener.

How to M2MQTT Auto Reconnect

i'm trying to use this protocol and already have 2 clients (one to publish and another to subscribe) and a broker working.
My question is i want to implement a reconnect feature in the subscribe client because the wifi signal is unstable and don't want to manually restart the client every single time, how can i accomplish this?
You can use the ConnectionClosed event to detect when a disconnect happens.
I then start a task that will try to reconnect the client.
Something like:
private async Task TryReconnectAsync(CancellationToken cancellationToken)
{
var connected = _client.IsConnected;
while (!connected && !cancellationToken.IsCancellationRequested)
{
try
{
_client.Connect(_clientId);
}
catch
{
_logger.Log(LogLevel.Warn, "No connection to...{0}",_serverIp);
}
connected = _client.IsConnected;
await Task.Delay(10000, cancellationToken);
}
}
Not perfect, but will do the job.
For those looking for a simple connection persistence here is my solution used.
When you want to start your mqtt connection call Task.Run(() => PersistConnectionAsync()); and note that static bool _tryReconnectMQTT should be defined at the class (or desired) scope level.
private async Task PersistConnectionAsync()
{
var connected = _mqttClient.IsConnected;
while (_tryReconnectMQTT)
{
if (!connected)
{
try
{
_mqttClient.Connect(_clientId);
}
catch
{
Debug.WriteLine("failed reconnect");
}
}
await Task.Delay(1000);
connected = _mqttClient.IsConnected;
}
}
I would also suggest using the last will and testament to know how long your client was down for and when. Replace _mqttClient.Connect(_clientId); with the following.
_mqttClient.Connect(_clientId,
null, null,
false,
MqttMsgBase.QOS_LEVEL_AT_MOST_ONCE, //aws
true,
$"/my/topic/{_clientId}connectionstatus",
"{\"message\":\"disconnected\"}",
true,
60);
Publish a connected message to the same topic with similar message after the connection statement to ensure you know all the times you were disconnected. Using AWS rules engine you would be able to send a notification on connection status, it is even possible to notify yourself if the duration between the reconnect is over a certain time.
The m2mqtt client has a .ConnectionClosed event you can subscribe your reconnect method to.

C# - asynchronous sockets with timeout

I am implementing a piece of software that reads a list of ids from a message queue. Once some come through, I would like to pass each one through a socket to a third party application, that will then process it and return a value back once it's done.
If the third party app takes too long to reply, I want to report this and maybe even close the connection.
Furthermore, this should run asynchronously, that is, once the messages are read from the queue, a separate task is started to handle it being sent to the socket and any subsequent communication.
Following this I have created a class that spawns a task and sends an exception after a timeout threshold.
public async Task Run(Action action, int timeoutInSeconds)
{
try
{
await Task.Run(action).TimeoutAfter(timeoutInSeconds);
}
catch (TimeoutException te)
{
//add error capture here or retry
}
}
public static async Task TimeoutAfter(this Task task, int timeoutInSeconds)
{
if (task == await Task.WhenAny(task, Task.Delay(timeoutInSeconds*1000)))
{
await task;
}
else
{
throw new TimeoutException(string.Format("Task {0} timed out after {1} seconds", task.Id, timeoutInSeconds));
}
}
Next I created another class to asynchronously listen to connections.
public class SocketListener
{
...
public async void Listen(Action action)
{
//initialization code
var listener = new TcpListener(ipAddress, Port);
listener.Start(numberOfConnections);
while (true)
{
try
{
//wait for client to connect
var client = await listener.AcceptTcpClientAsync();
//do something once client is connected
var task = new TaskWithTimeout();
await task.Run(() => action, 10);
}
catch (Exception ex)
{
// Log error
throw;
}
}
}
...
}
Here, after the client connects successfully, I want to call a method that will handle communication between server and client. If the client takes too long to respond, the TaskWithTimeout should throw an exception and move on.
My thought process was to call SocketListener once I read from the queue
public void ProcessQueue() {
//initialize SocketListener
listener.Listen(MethodToHandleCommunication)
...
}
Now I am a bit stuck. Preferably, SocketListener should be able to handle any type of communication, and that's why I thought I'd pass the Action as a parameter, so that I can determine what method I want to run from outside (by this I mean that if in the future I need to pass different data to the client, I would be able to reuse this code). However with this approach, I cannot even pass the client object back to the action.
In general I feel like I'm taking the wrong approach, and I am sure there's a better and more efficient way of doing what I want. As you can see I'm fairly new to parallel programming in general. I am a bit frustrated with this and would greatly appreciate any help or insight from SO

Categories