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
Related
I have following code:
while (!cancellationToken.IsCancellationRequested)
{
var connection = await listener.AcceptAsync(cancellationToken);
HandleConnectionAsync(connection, cancellationToken)
.FireAndForget(HandleException);
}
The FireAndForget is an extension method:
public static async void FireAndForget(this ValueTask task, Action<Exception> exceptionHandler)
{
try
{
await task.ConfigureAwait(false);
}
catch (Exception e)
{
exceptionHandler.Invoke(e);
}
}
The while loop is the server lifecycle. When new connection is accepted then it starts some "background task" so it can handle this new connection and then while loop goes back to accepting new connections without awaiting anything - pausing the lifecycle.
I cannot await HandleConnectionAsync (pause the lifecycle) here, because I want to immediately accept another connection (if there is one) and be able to handle multiple connections concurrently. HandleConnectionAsync is I/O bound and handles one connection at time until closed (task completes after some time).
The connections have to be handled separately - I don't want to have a situation when some error while handling one connection have any influence on other connections.
The "fire and forget" solution I have here works, but the general rule is to always await asynchronous methods and never use async void.
It seems like I've broken the rules, so is there a better, maybe more reliable way to handle variable (number of tasks varies in time) number of asynchronous I/O bound tasks concurrently in a situation described here?
More information:
Each call to AcceptAsync allocates system resources even before returning the connection and I want to avoid that whenever possible (the connection may not be returned for hours (code may "await" for hours) - until some external client decides to connect to my server). It is better to assume that this is the method I don't want to be called concurrently/in parallel - just one AcceptAsync at time is enough
Please take into account that I can have millions of clients per day connecting and disconnecting to my server and server (while loop) can work for many many days
I don't know how many connections I will need to handle at a specific time
I do know the maximum number of connections my program will be able to handle concurrently
If I hit the maximum number of connections limit then AcceptAsync won't return new connection until some other active connection closes, so I don't need to worry about that, but any solution based on this limit have to take into account that the active connections may be closed and I still need to handle new connections - number of connections varies over time. "fire and forget" have no issues with that
The code for HandleConnectionAsync is not relevant - it just handles one connection at time until closed (task completes after some time) and is I/O bound (HandleConnectionAsync handles one connection at time, but of course we can start multiple HandleConnectionAsync tasks to handle multiple connections concurrently - which is what I did with "fire and forget")
I'm assuming that changing to something like SignalR isn't an acceptable solution. That would be my first recommendation.
Custom server sockets is a scenario where some kind of "fire and forget" is acceptable. I'm considering adding a "task manager" kind of type to AsyncEx to make this kind of solution easier, but haven't done it yet.
The bottom line is that you need to manage your list of connections yourself. The "connection" object can include a Task that represents the handling loop; that's fine. It's also useful (especially for debugging or management purposes) to have other properties on there as well, such as the remote IP.
So I would approach it something like this:
private readonly object _mutex = new object();
private readonly List<State> _connections = new List<State>();
private void Add(State state)
{
lock (_mutex)
_connections.Add(state);
}
private void Remove(State state)
{
lock (_mutex)
_connections.Remove(state);
}
public async Task RunAsync(CancellationToken cancellationToken)
{
while (true)
{
var connection = await listener.AcceptAsync(cancellationToken);
Add(new State(this, connection));
}
}
private sealed class State
{
private readonly Parent _parent;
public State(Parent parent, Connection connection, CancellationToken cancellationToken)
{
_parent = parent;
Task = ExecuteAsync(connection, cancellationToken);
}
private static async Task ExecuteAsync(Connection connection, CancellationToken cancellationToken)
{
try { await HandleConnectionAsync(connection, cancellationToken); }
finally { _parent.Remove(this); }
}
public Task Task { get; }
// other properties as desired, e.g., RemoteAddress
}
You now have a collection of connections. You can either ignore the tasks in the State objects (as the code above is doing), which is just like fire-and-forget. Or you can await them all at some point. E.g.:
public async Task RunAsync(CancellationToken cancellationToken)
{
try
{
while (true)
{
var connection = await listener.AcceptAsync(cancellationToken);
Add(new State(this, connection));
}
}
catch (OperationCanceledException)
{
// Wait for all connections to cancel.
// I'm not really sure why you would *want* to do this, though.
List<State> connections;
lock (_mutex) { connections = _connections.ToList(); }
await Task.WhenAll(connections.Select(x => x.Task));
}
}
Then it's easy to extend the State object so you can do things that are sometimes useful for a server app to do, e.g.:
List all remote addresses this server has connections to.
Wait until a specific connection is done.
...
Notes:
Use one pattern for cancellation. Passing the token will result in an OperationCanceledException, which is the normal cancellation pattern. The code also was formerly doing a while (!IsCancellationRequested), resulting in a successful completion on cancellation, which is not the normal cancellation pattern. So I removed that so the code is no longer using two cancellation patterns.
When working with raw sockets, in the general case, you need to be constantly reading (even when you're writing) and periodically writing (even if you have no data to send). So your HandleConnectionAsync should be starting an asynchronous reader and writer and then using Task.WhenAll.
I removed the call to HandleException because (probably) whatever it does should be handled by State.ExecuteAsync. It's not hard to add it back in if necessary.
If there is a limit to the maximum number of allowed concurrent tasks, you should use SemaphoreSlim:
int allowedConcurrent = //..
var semaphore = new SemaphoreSlim(allowedConcurrent);
var tasks = new List<Task>();
while (!cancellationToken.IsCancellationRequested)
{
Func<Task> func = async () =>
{
var connection = await listener.AcceptAsync(cancellationToken);
await HandleConnectionAsync(connection, cancellationToken);
semaphore.Release();
};
await semaphore.WaitAsync(); // Will return immediately if the number of concurrent tasks does not exceed allowed
tasks.Add(func());
}
await Task.WhenAll(tasks);
This will accumulate the tasks into a list, then Task.WhenAll can wait for them all to complete.
First things first:
Don't do async void...
Then you can implement a producer/consumer pattern for this, the below pseudocode is just to guide, you need to make sure your Consumer is a Singleton in your app
public class Data
{
public Uri Url { get; set; }
}
public class Producer
{
private Consumer _consumer = new Consumer();
public void DoStuff()
{
var data = new Data();
_consumer.Enqueue(data);
}
}
public class Consumer
{
private readonly List<Data> _toDo = new List<Data>();
private bool _stop = false;
public Consumer()
{
Task.Factory.StartNew(Loop);
}
private async Task Loop()
{
while (!_stop)
{
Data toDo = null;
lock (_toDo)
{
if (_toDo.Any())
{
toDo = _toDo.First();
_toDo.RemoveAt(0);
}
}
if (toDo != null)
{
await DoSomething(toDo);
}
Thread.Sleep(TimeSpan.FromSeconds(1));
}
}
private async Task DoSomething(Data toDo)
{
// YOUR ASYNC STUFF HERE
}
public void Enqueue(Data data)
{
lock (_toDo)
{
_toDo.Add(data);
}
}
}
So your calling method produces what you need to do the background task and the consumer performs that, that's another fire and forget.
You should consider too what happens if something goes wrong at an application level, should you store the Data in the Consumer.Enqueue() so if the app starts again can do the missing job...
Hope this helps
I'm building a server app that accepts incoming TCP connections. (roughly 300 unique clients). It's important to note that I do not have control over the clients.
I have found that some of the connecting clients remain idle for quite some time after making the initial connection and sending the first status update. When they remain idle for over 5 mins the application's CPU usage jumps to over 90% and remains there.
To address this issue I built in a cancellation token that is triggered after 4 mins. This allows me to kill the connection. The client then detects this and reconnects about a minute later. This solves the high CPU usage issue, but has the side effect of high memory usage, there seems to be a memory leak. I suspect the resources is being held by the previous socket object.
I have a client object that contains the socket connection and information about the connected client. It also manages the incoming messages. There is also a manager class which accepts the incoming connections. It then creates the client object, assigns the socket to it and adds the client object to a concurrent dictionary. Every 10 seconds it checks the dictionary for clients that have been set to _closeConnection = true and calls their dispose method.
Here is the some of client object code:
public void StartCommunication()
{
Task.Run(async () =>
{
ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[75]);
while (IsConnected)
{
try
{
// This is where I suspect the memory leak is originating - this call I suspect is not properly cleaned up when the object is diposed
var result = await SocketTaskExtensions.ReceiveAsync(ClientConnection.Client, buffer, SocketFlags.None).WithCancellation(cts.Token);
if (result > 0)
{
var message = new ClientMessage(buffer.Array, true);
if(message.IsValid)
HandleClientMessage(message);
}
}
catch (OperationCanceledException)
{
_closeConnection = true;
DisconnectReason = "Client has not reported in 4 mins";
}
catch (Exception e)
{
_closeConnection = true;
DisconnectReason = "Error during receive opperation";
}
}
});
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_closeConnection = true;
cts.Cancel();
// Explicitly kill the underlying socket
if (UnitConnection.Client != null)
{
UnitConnection.Client.Close();
}
UnitConnection.Close();
cts.Dispose();
}
}
Task Extension Method:
public static async Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<bool>();
using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs))
{
if (task != await Task.WhenAny(task, tcs.Task))
{
throw new OperationCanceledException(cancellationToken);
}
}
return task.Result;
}
Mananger Code:
public bool StartListener()
{
_listener = new TcpListenerEx(IPAddress.Any, Convert.ToInt32(_serverPort));
_listener.Start();
Task.Run(async () =>
{
while (_maintainConnection) // <--- boolean flag to exit loop
{
try
{
HandleClientConnection(await _listener.AcceptTcpClientAsync());
}
catch (Exception e)
{
//<snip>
}
}
});
return true;
}
private void HandleClientConnection(TcpClient client)
{
Task.Run(async () =>
{
try
{
// Create new Coms object
var client = new ClientComsAsync();
client.ClientConnection = client;
// Start client communication
client.StartCommunication();
//_clients is the ConcurrentDictionary
ClientComsAsync existingClient;
if (_clients.TryGetValue(client.ClientName, out existingClient) && existingClient != null)
{
if (existingClient.IsConnected)
existingClient.SendHeatbeat();
if (!existingClient.IsConnected)
{
// Call Dispose on existing client
CleanUpClient(existingClient, "Reconnected with new connection");
}
}
}
catch (Exception e)
{
//<snip>
}
finally
{
//<snip>
}
});
}
private void CleanUpClient(ClientComsAsync client, string reason)
{
ClientComsAsync _client;
_units.TryRemove(client.ClientName, out _client);
if (_client != null)
{
_client.Dispose();
}
}
When they remain idle for over 5 mins the application's CPU usage jumps to over 90% and remains there.
To address this issue I built in a cancellation token that is triggered after 4 mins.
The proper response is to solve the high CPU usage problem.
Looks to me like it's here:
while (IsConnected)
{
try
{
var result = await SocketTaskExtensions.ReceiveAsync(ClientConnection.Client, buffer, SocketFlags.None);
if (result > 0)
{
...
}
}
catch ...
{
...
}
}
Sockets are weird, and dealing with raw TCP/IP sockets is quite difficult to do correctly. On a side note, I always encourage devs to use something more standard like HTTP or WebSockets, but in this case you don't control the clients, so that's not an option.
Specifically, your code is not handling the case where result == 0. If the client devices gracefully closed their socket, you'd see a result of 0, immediately loop back and keep getting a result of 0 - a tight loop that uses up CPU.
This is, of course, assuming that IsConnected remains true. And that may be possible...
You don't show where IsConnected is set in your code, but I suspect it's in the error handling after sending the heartbeat message. So here's why that may not work as expected... I suspect that the client devices are closing their sending stream (your receiving stream) while keeping their receiving stream (your sending stream) open. This is one way to shut down a socket, sometimes considered "more polite" because it allows the other side to continue sending data even though this side is done sending. (This is from the client device perspective, so the "other side" is your code, and "this side" is the client device).
And this is perfectly legal socket-wise because each connected socket is two streams, not one, each of which can be independently closed. If this happens, your heartbeats will still be send and received without error (and likely just silently discarded by the client device), IsConnected will remain true, and the read loop will become synchronous and eat up your CPU.
To resolve, add a check for result == 0 in your read loop and clean up the client just the same as if a heartbeat failed to send.
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.
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.
I have been thinking about what is the right way of set up a TCP server by using asynchronous programming.
Usually I would spawn a thread per incoming request, but I would like to do the most of the ThreadPool, so when connections are idle, there are no blocked threads.
First I would create the listener and start to accept clients, in this case, in a Console app:
static void Main(string[] args)
{
CancellationTokenSource cancellation = new CancellationTokenSource();
var endpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8001);
TcpListener server = new TcpListener(endpoint);
server.Start();
var task = AcceptTcpClients(server, cancellation.Token);
Console.ReadKey(true);
cancellation.Cancel();
await task;
Console.ReadKey(true);
}
In that method, I would loop accepting incoming requests and spawning a new Task to handle the connection, so the loop can get back to accept more clients:
static async Task AcceptTcpClients(TcpListener server, CancellationToken token)
{
while (!token.IsCancellationRequested)
{
var ws = await server.AcceptTcpClientAsync();
Task.Factory.StartNew(async () =>
{
while (ws.IsConnected && !token.IsCancellationRequested)
{
String msg = await ws.ReadAsync();
if (msg != null)
await ws.WriteAsync(ProcessResponse(msg));
}
}, token);
}
}
Creating new Task does not necessarily mean new thread, but is this the right way? Am I taking advantage of the ThreadPool or is there anything else I can do?
Is there any potential pitfall in this approach?
The await task; in your Main won't compile; you'll have to use task.Wait(); if you want to block on it.
Also, you should use Task.Run instead of Task.Factory.StartNew in asynchronous programming.
Creating new Task does not necessarily mean new thread, but is this the right way?
You certainly can start up separate tasks (using Task.Run). Though you don't have to. You could just as easily call a separate async method to handle the individual socket connections.
There are a few problems with your actual socket handling, though. The Connected property is practically useless. You should always be continuously reading from a connected socket, even while you're writing to it. Also, you should be writing "keepalive" messages or have a timeout on your reads, so that you can detect half-open situations. I maintain a TCP/IP .NET FAQ that explains these common problems.
I really, strongly recommend that people do not write TCP/IP servers or clients. There are tons of pitfalls. It would be far better to self-host WebAPI and/or SignalR, if possible.
In order to stop a server accept loop gracefully, I register a callback that stops listening when the cancellationToken is cancelled (cancellationToken.Register(listener.Stop);).
This will throw a SocketException on await listener.AcceptTcpClientAsync(); that is easy to capture.
There is no need for Task.Run(HandleClient()), because calling an async method returns a task that is running in parallel.
public async Task Run(CancellationToken cancellationToken)
{
TcpListener listener = new TcpListener(address, port);
listener.Start();
cancellationToken.Register(listener.Stop);
while (!cancellationToken.IsCancellationRequested)
{
try
{
TcpClient client = await listener.AcceptTcpClientAsync();
var clientTask = protocol.HandleClient(client, cancellationToken)
.ContinueWith(antecedent => client.Dispose())
.ContinueWith(antecedent => logger.LogInformation("Client disposed."));
}
catch (SocketException) when (cancellationToken.IsCancellationRequested)
{
logger.LogInformation("TcpListener stopped listening because cancellation was requested.");
}
catch (Exception ex)
{
logger.LogError(new EventId(), ex, $"Error handling client: {ex.Message}");
}
}
}