Best practice for reconnecting SignalR 2.0 .NET client to server hub
Asked Answered
P

5

95

I'm using SignalR 2.0 with the .NET client in a mobile application which needs to handle various types of disconnects. Sometimes the SignalR client reconnects automatically - and sometimes it has to be reconnected directly by calling HubConnection.Start() again.

Since SignalR magically auto-reconnects some of the time, I'm wondering if I'm missing a feature or config setting?

What's the best way to set up a client that reconnects automatically?


I've seen javascript examples that handle the Closed() event and then Connect after a n-seconds. Is there any recommended approach?

I've read the documentation and several articles about the lifetime of SignalR connections, but I'm still unclear on how to handle the client reconnect.

Privy answered 29/4, 2014 at 20:53 Comment(0)
P
74

I finally figured this out. Here's what I've learned since starting this question:

Background: We're building an iOS app using Xamarin / Monotouch and the .NET SignalR 2.0.3 client. We're using the default SignalR protocols - and it seems to be using SSE instead of web sockets. I'm not sure yet if it's possible to use web sockets with Xamarin / Monotouch. Everything is hosted using Azure websites.

We needed the app to reconnect to our SignalR server quickly, but we kept having problems where the connection didn't reconnect on its own - or the reconnect took exactly 30 seconds (due to an underlying protocol timeout).

There were three scenarios we ended up testing for:

Scenario A - connecting the first time the app was loaded. This worked flawlessly from day one. The connection completes in less than .25 seconds even over 3G mobile connections. (assuming the radio is already on)

Scenario B - reconnecting to the SignalR server after the app was idle/closed for 30 seconds. In this scenario, the SignalR client will eventually reconnect to the server on its own without any special work - but it seems to wait exactly 30 seconds before attempting to reconnect. (way too slow for our app)

During this 30 second waiting period, we tried calling HubConnection.Start() which had no effect. And calling HubConnection.Stop() also takes 30 seconds. I found a related bug on the SignalR site that appears to be resolved, but we're still having the same problem in v2.0.3.

Scenario C - reconnecting to the SignalR server after the app was idle/closed for 120 seconds or longer. In this scenario, the SignalR transport protocol has already timed out so the client never automatically reconnects. This explains why the client was sometimes but not always reconnecting on its own. The good news is, calling HubConnection.Start() works almost instantly like scenario A.

So it took me a while to realize that the reconnect conditions were different based on whether the app was closed for 30 seconds vs 120+ seconds. And although the SignalR tracing logs illuminate what's going on with the underlying protocol, I don't believe there's a way to handle the transport level events in code. (the Closed() event fires after 30 seconds in scenario B, instantly in scenario C; the State property says "Connected" during these reconnect waiting periods; no other relevant events or methods)

Solution: The solution is obvious. We're not waiting for SignalR to do its reconnection magic. Instead, when the app is activated or when the phone's network connection is restored, we're simply cleaning up the events and de-referencing the HubConnection (can't dispose it because it takes 30 seconds, hopefully garbage collection will take care of it) and creating a new instance. Now everything is working great. For some reason, I thought we should be reusing a persisted connection and reconnecting instead of just creating a new instance.

Privy answered 13/5, 2014 at 23:59 Comment(4)
Would you be willing to post some code? Just curious on how you structured it. I'm using Signalr in a chat app from within a PCL in a Xamarin app as well. It works really great except I can't seem to get the reconnection magic to work after the phone has been turned off and on again. I swear the IT Crowd said that was all I had to do.Seneca
Hi Ender2050, I have noticed that once android device disconnected from server then never reconnect again.so I had implemented alarm which runs every 5 mins and check for signalR connection with server hub.On Alarm tick event I have check if connection object is null or connectionId is empty then established the connection again.But this not work well.User need to kill the app and reopen it again. I have used java-client for android and C#.Net for serve hub. Looking for your help to solve this issue.Bilbao
FYI, Mono does not have web sockets. This is why your Xamarin apps always use SSE. You can write a console client. If you run it on Mono, it will use SSE. If you run it on Windows (at least Windows 8 because 7 also does not support web sockets) it will use web sockets.Caterwaul
We are having re-connection issue with SignalR Hub (SignalR library version 2.2.2) from Android App which is using "SignalR Java Client library" and iOS App which is using "SignalR Object C library". The client libraries on both the platforms have not been updated in a while. I think the issue is because of SignalR protocol incompatibility between client and server.Octamerous
L
50

Setting a timer on the disconnected event to automatically attempt reconnect is the only method I am aware of.

In javascript it is done like so:

$.connection.hub.disconnected(function() {
   setTimeout(function() {
       $.connection.hub.start();
   }, 5000); // Restart connection after 5 seconds.
});

This is the recommended approach in the documentation:

http://www.asp.net/signalr/overview/signalr-20/hubs-api/handling-connection-lifetime-events#clientdisconnect

Landsman answered 1/5, 2014 at 11:56 Comment(7)
I'm having mixed success with this approach on the .net client. Sometimes I receive an exception that a reconnection is already in progress - I'll test more and write back my results in a day or two. Thanks!Privy
Perhaps something unique in the behavior of the .net client. I recently implemented this for a project and it worked like a charm. I thought I would have to set an interval (setInterval vs. setTimeout), but somehow this works even when it needs to try multiple times to reconnect. I think the disconnected event must get fired on the reconnect attempt failures as far as what I can tell.Landsman
one hint - make sure you do any start complete functionality on the start as you otherwise would e. g. reconnect to the hubs.Jeffcott
I found that with the .NET client, if you subscribe to the Closed event before you call hub.Start(), if there is a failure to connect initially then your Closed event handler is called and tries to call hub.Start() again, causing the original hub.Start() to never complete. My solution was to only subscribe to Closed after Start() is successful, and unsubscribe to Closed immediately in the callback.Teleutospore
@MikeBaz I think you mean reconnect to groupsWinston
@Landsman I subscribed to reconnecting event, which is fired once the hub loses connection and set that variable (e.g shouldReconnect) to true. So I adapted your example to check that variable. It looks nice.Adoptive
I did a random number between 10 and 60 seconds. We have too many clients to just put 5 seconds, we would DDoS ourselves. $.connection.hub.disconnected(function() { setTimeout(function() { $.connection.hub.start(); }, (Math.floor(Math.random() * 50) + 10) * 1000); });Sacks
O
20

Since the OP asking for a .NET client (a winform implementation below),

private async Task<bool> ConnectToSignalRServer()
{
    bool connected = false;
    try
    {
        Connection = new HubConnection("server url");
        Hub = Connection.CreateHubProxy("MyHub");
        await Connection.Start();

        //See @Oran Dennison's comment on @KingOfHypocrites's answer
        if (Connection.State == ConnectionState.Connected)
        {
            connected = true;
            Connection.Closed += Connection_Closed;
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Error: {ex.Message}");
    }
    return connected;
}

private async void Connection_Closed()
{   // A global variable being set in "Form_closing" event 
    // of Form, check if form not closed explicitly to prevent a possible deadlock.
    if(!IsFormClosed) 
    {
        // specify a retry duration
        TimeSpan retryDuration = TimeSpan.FromSeconds(30);
        DateTime retryTill = DateTime.UtcNow.Add(retryDuration);

        while (DateTime.UtcNow < retryTill)
        {
            bool connected = await ConnectToSignalRServer();
            if (connected)
                return;
        }
        Console.WriteLine("Connection closed")
    }
}
Object answered 24/7, 2017 at 13:58 Comment(1)
I found in SignalR 2.3.0 that if I waited for the connect in the Closed() event that it would sometimes not connect. However, if I called a manual Wait( ) on the event with a timeout, such as 10 seconds, it would automatically call Closed( ) again and again every 10 seconds, and then the reconnect would work.Sacks
C
1

I add some update for ibubi answer. May be somebody need it. I found that in some case signalr don't rise "closed" event after reconnecting stopped. I solved it using event "StateChanged". Method which connect to SignalR server:

private async Task<bool> ConnectToSignalRServer()
        {
            bool connected = false;
            try
            {
                var connection = new HubConnection(ConnectionUrl);
                var proxy = connection.CreateHubProxy("CurrentData");
                await connection.Start();

                if (connection.State == ConnectionState.Connected)
                {
                    await proxy.Invoke("ConnectStation");

                    connection.Error += (ex) =>
                    {
                        Console.WriteLine("Connection error: " + ex.ToString());
                    };
                    connection.Closed += () =>
                    {
                        Console.WriteLine("Connection closed");
                    };
                    connection.StateChanged += Connection_StateChanged;
                    Console.WriteLine("Server for Current is started.");
                    connected = true;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error: {ex.Message}");
            }
            return connected;
        }

Method for reconnecting:

private async void Connection_StateChanged(StateChange obj)
        {
            if (obj.NewState == ConnectionState.Disconnected)
            {
                await RestartConnection();
            }
        }

Method of endless attempts to connect to the server (Also I use this method for creating the fist connection):

public async Task RestartConnection()
        {
            while (!ApplicationClosed)
            {
                bool connected = await ConnectToSignalRServer();
                if (connected)
                    return;
            }
        }
Curlicue answered 21/8, 2020 at 5:55 Comment(0)
P
-2

You might try to invoke server method from your android before reconnect state start to prevent magic reconnect problem.

SignalR Hub C#

 public class MyHub : Hub
    {
        public void Ping()
        {
            //ping for android long polling
        }
 }

In Android

private final int PING_INTERVAL = 10 * 1000;

private boolean isConnected = false;
private HubConnection connection;
private ClientTransport transport;
private HubProxy hubProxy;

private Handler handler = new Handler();
private Runnable ping = new Runnable() {
    @Override
    public void run() {
        if (isConnected) {
            hubProxy.invoke("ping");
            handler.postDelayed(ping, PING_INTERVAL);
        }
    }
};

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    System.setProperty("http.keepAlive", "false");

    .....
    .....

    connection.connected(new Runnable() {
        @Override
        public void run() {
            System.out.println("Connected");
            handler.postDelayed(ping, PING_INTERVAL);
    });
}
Pasley answered 24/1, 2015 at 23:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.