5
votes

I have a SignalR Hub and a .net Xamarin Android client. When a stable internet connection is used they work fine, the client can send to the hub and the hub can send to the client.

However the client appears to have no ability to automatically reconnect (contrary to what the online documentation says) and trying to implement reconnect myself is impossible because of the following issue.

If the client succesfully starts the HubConnection the State goes from Disconnected -> Connecting -> Connected. But if you pull the network cable from the client so there is no way for it to get to the server. The state never (and I do mean never) changes from "Connected".

I do get a single "ConnectionSlow" event, but the connections State never changes and calling Start on the connection does nothing because looking at the signalR source code, internally it checks if the State != "Connected" and if it isnt, it just returns. SignalR Github Source Code

My question is, how are you meant establish a SignalR connection to a server and have it (or implement) automatic reconnect. Because nothing I have tried works.

1
I´m using signalr in an Android/ios app. In both, connection goes to disconnected state when I disable wifi/3g. Not sure why that doesn´t work for you. Are you using https connection to the server? Maybe not related but SSE started to work when I did that changexleon
@Kyro did you find a solution? I am dealing with something similar. I have the exact same experience with iOS. Some android devices work for a couple times and then stop working.Garrett Daniel DeMeyer
@Garrett Daniel DeMeyer no we never found a solution. The SignalR development team did not think it was an issue. So we abandoned SignalR and went to XSockets.Net, they were way more helpful (even made us a custom client) and to be honest they have a much better framework than the pretty pathetic signalR framework.Kyro

1 Answers

1
votes

I´m not really sure if this will help you, but just be aware that:

  1. After signalr disconnects (i.e: network disconnection) I found out it´s better to create a new connection hub. Don´t expect signalr to reconnect always (it will try for a few seconds, then bye bye connection).

  2. If you want to dismiss the current connection, don´t try to close it (signalR could freeze for around 20 seconds before it actually releases the connection. Just reasign the connection variable to a new one.

This is my working code:

    private async Task Connect(bool dismissCurrentConnection = false)
    {
        // Always create a new connection to avoid SignalR close event delays
        if (connection != null)
        {
            if (!dismissCurrentConnection)
            {
                return;
            }

            connection.StateChanged -= OnConnectionStateChangedHandler;
            connection.Reconnected -= OnReconnectedHandler;
            // DON´T call connection.Dispose() or it may block for 20 seconds
            connection = null;
            proxy = null;
        }

        connection = new HubConnection(Settings.SERVER);
        // connection.TransportConnectTimeout = TimeSpan.FromSeconds(5);
        connection.TraceWriter = tracer;
        connection.TraceLevel = TraceLevels.All;
        connection.StateChanged += OnConnectionStateChangedHandler;
        connection.Reconnected += OnReconnectedHandler;

        proxy = connection.CreateHubProxy("ChatHub");
        proxy.On<ChatMessage>("AddNewMessage", message => AddNewMessage(message));
        proxy.On<ChatMessage>("ConfirmMessageDelivered", Message => ConfirmMessageDelivered(Message));
        proxy.On<string>("ConfirmMessageReceived", uid => ConfirmMessageReceived(uid));
        proxy.On<string>("ConfirmMessageRead", uid => ConfirmMessageRead(uid));
        proxy.On<UserChatStatus>("ChangeUserChatStatus", status => ChangeUserChatStatus(status));

        if (connection.State == ConnectionState.Disconnected)
        {
            try
            {
                MvxTrace.Trace("[{0}] Connecting...", nameof(ChatService));
                await connection.Start();
                await invokeQueue.ProcessQueue();
            }
            catch (Exception ex)
            {
                MvxTrace.Error("[{0}] CONNECTION START ERROR: {1}", nameof(ChatService), ex.Message);
            }
        }
    }

The first time I will connect with await Connect(); Subsequent connections will use await Connect(true) to dismiss the current one

    private void OnReconnectedHandler()
    {
        Task.Factory.StartNew(async () => await invokeQueue.ProcessQueue());
    }

    private void OnConnectionStateChangedHandler(StateChange change)
    {
        this.ConnectionState = change.NewState;
        OnConnectionStateChanged?.Invoke(change);

        switch (change.NewState)
        {
            case ConnectionState.Disconnected:
                // SignalR doesn´t do anything after disconnected state, so we need to manually reconnect
                reconnectTokenSource = new CancellationTokenSource();
                Task.Factory.StartNew(async () =>
                {
                    await Task.Delay(TimeSpan.FromSeconds(Settings.RECONNECT_PERIOD_SECONDS), reconnectTokenSource.Token);
                    await Connect(true);
                }, reconnectTokenSource.Token);

                break;
        }
    }

In case you wonder what is "invokeQueue", it´s just a custom utility that catches all signalr proxy calls when state is not connected and process them once connection is back.

"reconnectTokenSource" will be cancelled in case I don´t want the re-connect process to go on (i.e: when closing the activity).

It looks like we both have very similar configurations. The only obvious difference is that you´re using the nuget in an Android project and I´m using it on a PCL.

In this log you can check disconnection events:

mvx:Diagnostic: 14,72 [SignalRTracer] 01:47:56.2406950 - null - ChangeState(Disconnected, Connecting)
mvx:Diagnostic: 15,48 [SignalRTracer] 01:47:57.0176730 - 6dd879af-7d49-4632-96b2-cb5fb542dc24 - SSE: GET http://192.168.0.53/signalr/connect?clientProtocol=1.4&transport=serverSentEvents&connectionData=[{"Name":"ChatHub"}]&connectionToken=IgMS6rxmjHJCeudFlDRV8jFSt9Tz1Mt210X21RbiFAIalj%2BioMMRWPH5%2BfNaNIkVW%2BzHbv%2FmSjk8uoRNVbK%2FKUL%2FI87iBkejKkXYivq5FZy6x8E1%2FAK2rBnCg3Zi9nwY&noCache=d4878c0b-5c2a-4535-929c-29952f5e6795
mvx:Diagnostic: 15,56 [SignalRTracer] 01:47:57.0945740 - 6dd879af-7d49-4632-96b2-cb5fb542dc24 - SSE: OnMessage(Data: initialized)
mvx:Diagnostic: 15,57 [SignalRTracer] 01:47:57.1054290 - 6dd879af-7d49-4632-96b2-cb5fb542dc24 - SSE: OnMessage(Data: {"C":"d-3050D8AB-B,C|I,0|J,1","S":1,"M":[]})
mvx:Diagnostic: 15,70 [SignalRTracer] 01:47:57.2408050 - 6dd879af-7d49-4632-96b2-cb5fb542dc24 - ChangeState(Connecting, Connected)
mvx:Diagnostic: 15,82 [SignalRTracer] 01:47:57.3586480 - 6dd879af-7d49-4632-96b2-cb5fb542dc24 - SSE: OnMessage(Data: {"C":"d-3050D8AB-B,D|I,0|J,1","M":[]})
mvx:Diagnostic: 15,83 [SignalRTracer] 01:47:57.3718630 - 6dd879af-7d49-4632-96b2-cb5fb542dc24 - OnMessage({"I":"0"})

Then I switched off wifi:

mvx:Diagnostic: 29,55 [SignalRTracer] 01:48:11.0813880 - 6dd879af-7d49-4632-96b2-cb5fb542dc24 - OnError(System.Net.Sockets.SocketException: Connection timed out at System.Net.Sockets.Socket.EndReceive (IAsyncResult result) [0x0002d] in /Users/builder/data/lanes/2098/3efa14c4/source/mono/mcs/class/System/System.Net.Sockets/Socket.cs:1798 at System.Net.Sockets.NetworkStream.EndRead (IAsyncResult ar) [0x0002f] in /Users/builder/data/lanes/2098/3efa14c4/source/mono/mcs/class/System/System.Net.Sockets/NetworkStream.cs:320 )
mvx:Diagnostic: 31,56 [SignalRTracer] 01:48:13.1037440 - 6dd879af-7d49-4632-96b2-cb5fb542dc24 - ChangeState(Connected, Reconnecting)
mvx:Diagnostic: 31,68 [SignalRTracer] 01:48:13.2199720 - 6dd879af-7d49-4632-96b2-cb5fb542dc24 - OnError(System.Net.Sockets.SocketException: Network is unreachable at System.Net.Sockets.Socket.Connect (System.Net.EndPoint remoteEP) [0x000bc] in /Users/builder/data/lanes/2098/3efa14c4/source/mono/mcs/class/System/System.Net.Sockets/Socket.cs:1235 at System.Net.WebConnection.Connect (System.Net.HttpWebRequest request) [0x0019b] in /Users/builder/data/lanes/2098/3efa14c4/source/mono/mcs/class/System/System.Net/WebConnection.cs:213 )

Later on...

mvx:Diagnostic: 61,68 [SignalRTracer] 01:48:43.2182200 - 6dd879af-7d49-4632-96b2-cb5fb542dc24 - OnError(System.TimeoutException: Couldn't reconnect within the configured timeout of 00:00:30, disconnecting.)
mvx:Diagnostic: 61,69 [SignalRTracer] 01:48:43.2330650 - 6dd879af-7d49-4632-96b2-cb5fb542dc24 - Disconnected
mvx:Diagnostic: 61,76 [SignalRTracer] 01:48:43.2972460 - 6dd879af-7d49-4632-96b2-cb5fb542dc24 - Transport.Dispose(6dd879af-7d49-4632-96b2-cb5fb542dc24)
mvx:Diagnostic: 61,77 [SignalRTracer] 01:48:43.3065810 - 6dd879af-7d49-4632-96b2-cb5fb542dc24 - Closed