5

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.

Kyro
  • 748
  • 1
  • 12
  • 28
  • 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 change – xleon Nov 29 '15 at 18:49
  • 1
    @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 Feb 16 '16 at 21:09
  • 1
    @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 Feb 16 '16 at 22:05

1 Answers1

1

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
xleon
  • 6,201
  • 3
  • 36
  • 52
  • The problem with this is that the connection's State never changes from "Connected" and it never detects that it has lost a connection. This really feels like the SignalR .net Client is still in alpha stage, with such fundamental issues. – Kyro Nov 30 '15 at 02:35
  • What client versión are you using? – xleon Nov 30 '15 at 02:43
  • I´m using version 2.2.0 in a PCL project and I can confirm the connection state changes when it should in Xamarin. Can you show some of your code where you listen for connection changes? – xleon Nov 30 '15 at 17:04
  • I have the Microsoft.AspNet.SignalR.Client Version 2.2.0 Nuget package installed in a Xamarin Android project. As for the State changed handler it is just a basic handler that takes in the StateChanged object. It detects the Connecting->Connected state changes when the connection is started. But it never fires Connected->Disconnected, and even if I setup a timer to run ever minute and check the connection.State it still shows "Connected". – Kyro Nov 30 '15 at 21:11
  • What transport protocol is been used? – xleon Nov 30 '15 at 23:05
  • The server supports websockets and will connect with Web Sockets from a normal console app. But on Xamarin Android it is connecting with Server Sent Events (SSE). – Kyro Nov 30 '15 at 23:08
  • Please, check my edits. I added a log entry where you can see reconnecting and disconnection events. I´m trying to guess what´s the difference between you configuration and mine. They seam pretty similar – xleon Dec 01 '15 at 02:04
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/96624/discussion-between-kyro-and-xleon). – Kyro Dec 01 '15 at 03:42
  • 1
    I think this is a more fundamental problem with either the hardware or the android OS. Because a basic System.Net.Sockets.Socket reports itself as Connected when the cellular is disconnected (the cellular is provided by a USB dongle). I would like to see the general design of your invokeQueue utility object. – Kyro Dec 01 '15 at 03:45