SignalR - OnDisconnectedAsync not called when client disconnects abruptly - c#

I have a SignalR hub with an override for OnDisconnectedAsync. I have found that gracefully closing the connection on the client side - like calling close() - will trigger OnDisconnectedAsync. However, the disconnect doesn't seem to trigger when disconnecting "disgracefully".
For example; When I open the client in a tab, click onto a couple of other open tabs, then close the client tab while having another tab in focus. This does not trigger a disconnect.
This also seems to happen when disconnecting from my work's network through a VPN connection. Basically, any condition that falls outwith the normal conditions for a disconnect. It's like the server still thinks the connection is open.
I have tried to reduce the KeepAliveInterval and ClientTimeoutInterval to see if I was just being impatient, but after waiting 5 minutes after disconnecting, I'm convinced it's not firing properly.
Am I doing something wrong in my hub?
services.AddSignalR(opts => {
opts.MaximumParallelInvocationsPerClient = 10;
opts.KeepAliveInterval = TimeSpan.FromSeconds(5);
opts.ClientTimeoutInterval = TimeSpan.FromSeconds(5);
});
public override async Task OnDisconnectedAsync(Exception? exception)
{
if (exception != null)
{
Log.Error(exception.Message);
}
Log.Information("${Context.UserIdentifier} Disconnected");
...bunch of additional logic
await base.OnDisconnectedAsync(exception);
}

Related

SignalR Core detect if connection is closed by client or server

On ASP.NET Core 2.1 LTS, there is no automatically reconnect for SignalR. So I implemented it on my own using onclose event of the js client. While this works, it has a side-effect: The client also trys to reconnect, when the connection is cancelled by the client itself - for example when closing the browser tab.
The reconnect works for a short period of time, until the tab is closed. Since I'm re-loading notifications on re-connect to keep the user up to date in such a case, this is a waste of ressources since I do several SQL queries on a user that closes the tab.
let reconnectWaitTime = 5000
let client = new signalR.HubConnectionBuilder()
.withUrl("/overviewHub")
.build();
client.onclose(async (e) => {
await tryReconnect(client)
notificationsManager.handleReConnect()
})
await tryReconnect(client)
let notificationsManager = new ULNotificationsManager(client)
async function tryReconnect(client,) {
try {
let started = await client.start()
return started;
} catch (e) {
await new Promise(resolve => setTimeout(resolve, reconnectWaitTime));
return await tryReconnect(client)
}
}
How can I detect if the connection was closed from the client or the server, so that I'm able to only reload the notification data if the server closed the connection?
Approach
According to the documentation, there should be an Error callback for the onclose method:
client.onclose(function(ev){
console.warn(ev)
})
My idea was to use this error for getting more detailled information like some error code that let me filter out the client side abort. But this doesn't seem work, I got undefined in my logs.
Update
My solution seems not to work in all cases. For example. when the dotnet console window is closed, there is no exception and so also no reconnect. I assume that .NET core is properly closing the connections in this case.
I tried working around this by overriding the Dispose method of the hub, calling some event on the client that triggery my reconnect. Doesnt't work well. I ended in using the window.onbeforeunload event to detect if the user navigates away
window.onbeforeunload = function () {
window.userIsNavigatingAway = true
return
};
and check for this boolean instead of the closing error
client.onclose(async (error) => {
// https://stackoverflow.com/a/57833660/3276634
if (!window.userIsNavigatingAway) {
await tryReconnect(client, initialReConnectWaitTime)
console.log('ReConnect worked')
notificationsManager.handleReConnect()
} else {
console.log("Client closed the WS connection, so no reconnect attemp is made")
}
})
Original answear
This only works if the server was killed unexpectedly.
Found out that the Error callback refers to server side issues: When the connection is aborted by the server (e.g. server restart), I get an Error object:
Error: "WebSocket closed with status code: 1006 ()."
So my problem could be easily solved by checking if the error is undefined:
client.onclose(async (error) => {
if (typeof error !== 'undefined') {
await tryReconnect(client)
notificationsManager.handleReConnect()
} else {
console.log("Client closed the WS connection, so no reconnect attemp is made")
}
})
I'll have to try if this also works correct when the client lost network connection (e.g. on mobile devies), where a reconnect would be usefull.

What SignalR method is called when the connection is lost or is there a handler for connection state changes?

I'm working on a .NET web app using SignalR with the hub class similar to the example class below:
public class ContosoChatHub : Hub
{
public override Task OnConnected()
{
// Add your own code here.
// For example: in a chat application, record the association between
// the current connection ID and user name, and mark the user as online.
// After the code in this method completes, the client is informed that
// the connection is established; for example, in a JavaScript client,
// the start().done callback is executed.
return base.OnConnected();
}
public override Task OnDisconnected()
{
// Add your own code here.
// For example: in a chat application, mark the user as offline,
// delete the association between the current connection id and user name.
return base.OnDisconnected();
}
public override Task OnReconnected()
{
// Add your own code here.
// For example: in a chat application, you might have marked the
// user as offline after a period of inactivity; in that case
// mark the user as online again.
return base.OnReconnected();
}
}
More specific, my web app serves as hub for connecting tablets. when i close the app on the tablet it does not trigger instantly the OnDisconnected task, taking up to 20 seconds or more (server tries to reconnect with the client).
My question is, which method should I use in order to detect the connection loss as soon as it happens or, is there a connection state handler that triggers when the connection is lost?
In order to prevent the data loss (considering a tablet online when in fact it's not) I really need to handle the disconnecting event.
Any help is much appreciated!
Later edit:
I've also included the following lines in the Global.asax file
GlobalHost.Configuration.ConnectionTimeout = TimeSpan.FromSeconds(6);
GlobalHost.Configuration.KeepAlive = TimeSpan.FromSeconds(2);
in the Application Start method. The values seem to be saved, as seen in debug and actually reduce the time by half, from 20-30 to 12-14 seconds, but it's still not as close to 2-3 seconds.
You can detect the server disconnection from SignalR client:
$.connection.hub.disconnected(function () {
alert('Server has disconnected');
});
This is the official documentation when each method called:
When OnConnected, OnDisconnected, and OnReconnected are called
Each time a browser navigates to a new page, a new connection has to
be established, which means SignalR will execute the OnDisconnected
method followed by the OnConnected method. SignalR always creates a
new connection ID when a new connection is established.
The OnReconnected method is called when there has been a temporary
break in connectivity that SignalR can automatically recover from,
such as when a cable is temporarily disconnected and reconnected
before the connection times out. The OnDisconnected method is called
when the client is disconnected and SignalR can't automatically
reconnect, such as when a browser navigates to a new page. Therefore,
a possible sequence of events for a given client is OnConnected,
OnReconnected, OnDisconnected; or OnConnected, OnDisconnected. You
won't see the sequence OnConnected, OnDisconnected, OnReconnected for
a given connection.
The OnDisconnected method doesn't get called in some scenarios, such
as when a server goes down or the App Domain gets recycled. When
another server comes on line or the App Domain completes its recycle,
some clients may be able to reconnect and fire the OnReconnected
event.

Tcp.BeginConnect thinks it is connected, even if there is no server

I am trying to make an aSync connection to a server using TcpClient.BeginConnect, but am encountering some difficulties. This is my first time using Tcp so please bear with me.
The connection itself works fine when the server is running, i can send and receive messages without problem. However when I stop the server and try to connect to it, Tcp.BeginConnect will pretend it is actually connected to a server without returning an error, until i try to actually send data which will obviously fail.
When i use TcpClient.Connect() instead it'll return A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond. when no connection is established after a few seconds, letting me know the connection failed.
Is there a way to get this same behaviour with TcpClient.BeginConnect? Or am I doing something wrong myself?
i looked around and found C# BeginConnect callback is fired when not connected which is somewhat similair and the answer was that EndConnect had to be called in the callback before the socket becomes usuable, but i'm already doing that.
my code:
public static void OpenTcpASyncConnection()
{
if (client == null)
{
client = new TcpClient();
IAsyncResult connection = client.BeginConnect(serverIp, serverPort, new AsyncCallback(ASyncCallBack), client);
bool succes = connection.AsyncWaitHandle.WaitOne();//returns true
if (!succes)
{
client.Close();
client.EndConnect(connection);
throw new Exception("TcpConnection::Failed to connect.");
}
else
{
Debug.LogFormat("TcpConnection::Connecting to {0} succeeded", serverIp);
}
}
else
{
Debug.Log("TcpConnection::Client already exists");
}
}
public static void ASyncCallBack(IAsyncResult ar)
{
Debug.Log("Pre EndConnect");
client.EndConnect(ar);
Debug.Log("Post EndConnect");//this never gets called?
}
the boolean succes is true even if the server is offline (or does this always return true as long as the operation finishes?), thus i assume it thinks it is actually connected, and the Debug.Log after client.EndConnect(ar) never gets called. Not a single error gets returned.
In summary; Am I forgetting something/doing something wrong? or is this expected behaviour?
Edit: language is C# with the .net 3.5 framework. It is ment for a Unity application though i'm not inheriting from monobehaviour for this. If you require any additional information I will try to provide this.
Kind regards and thanks for your time,
Remy.

WPF startup application execute after internet connection connected?

I'm developing a windows application using WPF.The program is running in the startup and should wait untill the internet connection is connected, to be executed. Normally internet connection will get some time to connect. Therefore now im running a thread to ping(like this) with the server in space of 3 seconds to watch the connection status.
public bool CheckConnection()
{
try
{
//////////////check connction
System.Net.Sockets.TcpClient clnt = new System.Net.Sockets.TcpClient(UserConfig.configlist[2], Convert.ToInt32(UserConfig.configlist[3]));
clnt.Close();
return true;
}
catch (Exception)
{
return false;
}
}
If the status is true program will be executed. Is there any efficient way of doing this. Any ideas please??????
There is a very useful NetworkManager class over on CP that will allow you to check for network connection status using the NetConnectionStatus enum...
You could start a timer to check for network connection every couple of seconds with if (NetworkManager.NetConnectionStatus != NetConnectionStatus.Connected){} and wait to execute your network dependent code until the network status changes to Connected.

Detecting async client disconnect in ASP.NET MVC

Given an async controller:
public class MyController : AsyncController
{
[NoAsyncTimeout]
public void MyActionAsync() { ... }
public void MyActionCompleted() { ... }
}
Assume MyActionAsync kicks off a process that takes several minutes. If the user now goes to the MyAction action, the browser will wait with the connection open. If the user closes his browser, the connection is closed. Is it possible to detect when that happens on the server (preferably inside the controller)? If so, how? I've tried overriding OnException but that never fires in this scenario.
Note: I do appreciate the helpful answers below, but the key aspect of this question is that I'm using an AsyncController. This means that the HTTP requests are still open (they are long-lived like COMET or BOSH) which means it's a live socket connection. Why can't the server be notified when this live connection is terminated (i.e. "connection reset by peer", the TCP RST packet)?
I realise this question is old, but it turned up frequently in my search for the same answer.
The details below only apply to .Net 4.5
HttpContext.Response.ClientDisconnectedToken is what you want. That will give you a CancellationToken you can pass to your async/await calls.
public async Task<ActionResult> Index()
{
//The Connected Client 'manages' this token.
//HttpContext.Response.ClientDisconnectedToken.IsCancellationRequested will be set to true if the client disconnects
try
{
using (var client = new System.Net.Http.HttpClient())
{
var url = "http://google.com";
var html = await client.GetAsync(url, HttpContext.Response.ClientDisconnectedToken);
}
}
catch (TaskCanceledException e)
{
//The Client has gone
//you can handle this and the request will keep on being processed, but no one is there to see the resonse
}
return View();
}
You can test the snippet above by putting a breakpoint at the start of the function then closing your browser window.
And another snippet, not directly related to your question but useful all the same...
You can also put a hard limit on the amount of time an action can execute for by using the AsyncTimeout attribute. To use this use add an additional parameter of type CancellationToken. This token will allow ASP.Net to time-out the request if execution takes too long.
[AsyncTimeout(500)] //500ms
public async Task<ActionResult> Index(CancellationToken cancel)
{
//ASP.Net manages the cancel token.
//cancel.IsCancellationRequested will be set to true after 500ms
try
{
using (var client = new System.Net.Http.HttpClient())
{
var url = "http://google.com";
var html = await client.GetAsync(url, cancel);
}
}
catch (TaskCanceledException e)
{
//ASP.Net has killed the request
//Yellow Screen Of Death with System.TimeoutException
//the return View() below wont render
}
return View();
}
You can test this one by putting a breakpoint at the start of the function (thus making the request take more than 500ms when the breakpoint is hit) then letting it run out.
Does not Response.IsClientConnected work fairly well for this? I have just now tried out to in my case cancel large file uploads. By that I mean if a client abort their (in my case Ajax) requests I can see that in my Action. I am not saying it is 100% accurate but my small scale testing shows that the client browser aborts the request, and that the Action gets the correct response from IsClientConnected.
It's just as #Darin says. HTTP is a stateless protocol which means that there are no way (by using HTTP) to detect if the client is still there or not. HTTP 1.0 closes the socket after each request, while HTTP/1.1 can keep it open for a while (a keep alive timeout can be set as a header). That a HTTP/1.1 client closes the socket (or the server for that matter) doesn't mean that the client has gone away, just that the socket hasn't been used for a while.
There are something called COMET servers which are used to let client/server continue to "chat" over HTTP. Search for comet here at SO or on the net, there are several implementations available.
For obvious reasons the server cannot be notified that the client has closed his browser. Or that he went to the toilet :-) What you could do is have the client continuously poll the server with AJAX requests at regular interval (window.setInterval) and if the server detects that it is no longer polled it means the client is no longer there.

Categories

Resources