Najlepsze rozwiązania dotyczące ponownego łączenia klienta SignalR 2.0 .NET z centrum serwera

86

Używam SignalR 2.0 z klientem .NET w aplikacji mobilnej, która musi obsługiwać różne typy rozłączeń. Czasami klient SignalR łączy się ponownie automatycznie - a czasami musi zostać ponownie połączony bezpośrednio przez HubConnection.Start()ponowne wywołanie .

Ponieważ SignalR w magiczny sposób łączy się automatycznie ponownie, zastanawiam się, czy brakuje mi funkcji lub ustawienia konfiguracji?

Jaki jest najlepszy sposób skonfigurowania klienta, który łączy się ponownie automatycznie?


Widziałem przykłady javascript, które obsługują Closed()zdarzenie, a następnie Connect po n-sekundach. Czy jest jakieś zalecane podejście?

Przeczytałem dokumentację i kilka artykułów na temat okresu istnienia połączeń sygnalizujących, ale nadal nie wiem, jak obsługiwać ponowne łączenie klienta.

Ender2050
źródło

Odpowiedzi:

71

W końcu to rozgryzłem. Oto, czego się nauczyłem, odkąd zacząłem to pytanie:

Tło: tworzymy aplikację dla systemu iOS przy użyciu platformy Xamarin / Monotouch i klienta .NET SignalR 2.0.3. Używamy domyślnych protokołów SignalR - i wydaje się, że używa SSE zamiast gniazd internetowych. Nie jestem jeszcze pewien, czy można używać gniazd internetowych z platformą Xamarin / Monotouch. Wszystko jest hostowane przy użyciu witryn sieci Web platformy Azure.

Potrzebowaliśmy aplikacji do szybkiego ponownego nawiązania połączenia z naszym serwerem sygnalizującym, ale nadal mieliśmy problemy, w których połączenie nie zostało ponownie połączone - lub ponowne połączenie trwało dokładnie 30 sekund (z powodu przekroczenia limitu czasu podstawowego protokołu).

Testowaliśmy trzy scenariusze:

Scenariusz A - łączenie się przy pierwszym załadowaniu aplikacji. To działało bez zarzutu od pierwszego dnia. Połączenie trwa mniej niż 0,25 sekundy, nawet w przypadku połączeń mobilnych 3G. (zakładając, że radio jest już włączone)

Scenariusz B - ponowne łączenie się z serwerem SignalR po tym, jak aplikacja była bezczynna / zamknięta przez 30 sekund. W tym scenariuszu klient sygnalizujący ostatecznie ponownie połączy się z serwerem bez żadnej specjalnej pracy - ale wydaje się czekać dokładnie 30 sekund przed podjęciem próby ponownego połączenia. (zbyt wolno dla naszej aplikacji)

Podczas tego 30-sekundowego okresu oczekiwania próbowaliśmy wywołać HubConnection.Start (), co nie przyniosło żadnego efektu. Wywołanie HubConnection.Stop () również zajmuje 30 sekund. Znalazłem powiązany błąd w witrynie SignalR, który wydaje się być rozwiązany , ale nadal mamy ten sam problem w wersji 2.0.3.

Scenariusz C - ponowne łączenie się z serwerem SignalR po tym, jak aplikacja była bezczynna / zamknięta przez 120 sekund lub dłużej. W tym scenariuszu protokół transportowy SignalR już przekroczył limit czasu, więc klient nigdy nie łączy się automatycznie ponownie. To wyjaśnia, dlaczego klient czasami, ale nie zawsze, łączył się ponownie samodzielnie. Dobra wiadomość jest taka, że ​​wywołanie HubConnection.Start () działa niemal natychmiast, jak w scenariuszu A.

Zajęło mi trochę czasu, zanim zdałem sobie sprawę, że warunki ponownego połączenia były różne w zależności od tego, czy aplikacja była zamknięta na 30 sekund, czy ponad 120 sekund. I chociaż dzienniki śledzenia sygnalizującego ilustrują, co dzieje się z podstawowym protokołem, nie sądzę, aby istniał sposób obsługi zdarzeń na poziomie transportu w kodzie. (zdarzenie Closed () uruchamia się po 30 sekundach w scenariuszu B, natychmiast w scenariuszu C; właściwość State mówi „Połączono” podczas tych okresów oczekiwania na ponowne połączenie; brak innych odpowiednich zdarzeń lub metod)

Rozwiązanie: Rozwiązanie jest oczywiste. Nie czekamy, aż SignalR wykona swoją magię ponownego połączenia. Zamiast tego, gdy aplikacja jest aktywowana lub gdy połączenie sieciowe telefonu jest przywracane, po prostu czyścimy zdarzenia i usuwamy odwołania do HubConnection (nie można go usunąć, ponieważ zajmuje to 30 sekund, mam nadzieję, że zajmie się tym odśmiecanie) ) i tworzenie nowej instancji. Teraz wszystko działa świetnie. Z jakiegoś powodu pomyślałem, że powinniśmy ponownie użyć trwałego połączenia i połączyć się ponownie, zamiast po prostu tworzyć nową instancję.

Ender2050
źródło
5
Czy chciałbyś wysłać jakiś kod? Ciekaw jestem, jak to zbudowałeś. Używam Signalr w aplikacji do czatu z poziomu PCL w aplikacji Xamarin. Działa naprawdę świetnie, z wyjątkiem tego, że nie mogę uruchomić magii ponownego połączenia po wyłączeniu i ponownym włączeniu telefonu. Przysięgam, że tłum IT powiedział, że to wszystko, co muszę zrobić.
Timothy Lee Russell
1
Cześć Ender2050, zauważyłem, że gdy urządzenie z Androidem zostanie odłączone od serwera, nigdy więcej nie połącz się ponownie. Więc zaimplementowałem alarm, który uruchamia się co 5 minut i sprawdzam połączenie signalR z hubem serwera. connectionId jest puste, a następnie ponownie nawiązano połączenie, ale to nie działa dobrze. użytkownik musi zabić aplikację i ponownie ją otworzyć. Użyłem klienta java dla Androida i C # .Net do obsługi centrum. Szukam Twojej pomocy w rozwiązaniu tego problemu.
jignesh
1
FYI, Mono nie ma gniazd sieciowych. Dlatego Twoje aplikacje Xamarin zawsze używają SSE. Możesz napisać klienta konsoli. Jeśli uruchomisz go w trybie Mono, użyje SSE. Jeśli uruchomisz go w systemie Windows (przynajmniej Windows 8, ponieważ 7 również nie obsługuje gniazd sieciowych), użyje gniazd internetowych.
daramasala
@ Ender2050 Czy możesz rozszerzyć swoje rozwiązanie o kilka przykładów kodu?
Magrangs
Występuje problem z ponownym połączeniem z Centrum sygnalizacyjnym (wersja 2.2.2 biblioteki SignalR) z aplikacji na Androida, która korzysta z „Biblioteki klienta Java SignalR” i aplikacji dla systemu iOS, która korzysta z biblioteki „SignalR Object Library”. Biblioteki klienta na obu platformach nie były aktualizowane od jakiegoś czasu. Myślę, że problem wynika z niekompatybilności protokołu SignalR między klientem a serwerem.
Nadim Hossain Sonet
44

Jedyną znaną mi metodą jest ustawienie licznika czasu w zdarzeniu odłączenia, aby automatycznie podejmował próbę ponownego połączenia.

W javascript robi się to tak:

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

Jest to zalecane podejście w dokumentacji:

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

KingOfHypocrites
źródło
1
jedna wskazówka - upewnij się, że uruchomiłeś pełną funkcjonalność na początku, ponieważ w przeciwnym razie chciałbyś np. ponownie połączyć się z hubami.
MikeBaz - MSFT,
1
Zauważyłem, że w przypadku klienta .NET, jeśli zasubskrybujesz zdarzenie Closed przed wywołaniem hub.Start (), jeśli początkowe połączenie się nie powiedzie, zostanie wywołana procedura obsługi zdarzenia Closed i spróbuje wywołać hub. , powodując, że oryginalny hub.Start () nigdy się nie kończy. Moim rozwiązaniem było zasubskrybowanie Closed dopiero po pomyślnym uruchomieniu Start () i natychmiastowe anulowanie subskrypcji Closed w wywołaniu zwrotnym.
Oran Dennison,
3
@MikeBaz Myślę, że masz na myśli ponowne połączenie się z grupami
Simon_Weaver
1
@KingOfHypocrites Subskrybowałem reconnectingzdarzenie, które jest uruchamiane, gdy hub utraci połączenie i ustawię tę zmienną (np. shouldReconnect) Na true. Więc dostosowałem twój przykład, aby sprawdzić tę zmienną. To wygląda ładnie.
Alisson
2
Zrobiłem losową liczbę od 10 do 60 sekund. Mamy zbyt wielu klientów, aby umieścić tylko 5 sekund, sami dokonalibyśmy DDoS. $ .connection.hub.disconnected (function () {setTimeout (function () {$ .connection.hub.start ();}, (Math.floor (Math.random () * 50) + 10) * 1000); });
Brain2000
17

Ponieważ OP prosi o klienta .NET (implementacja winform poniżej),

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")
    }
}
ibubi
źródło
Znalazłem w SignalR 2.3.0, że jeśli czekałem na połączenie w zdarzeniu Closed (), czasami nie łączyło się. Jednak gdybym wywołał ręczne Wait () w zdarzeniu z limitem czasu, takim jak 10 sekund, automatycznie wywoływałoby Closed () raz po raz co 10 sekund, a następnie ponowne połączenie zadziałałoby.
Brain2000
0

Dodaję aktualizację do odpowiedzi ibubi . Może ktoś tego potrzebuje. Zauważyłem, że w niektórych przypadkach sygnalizator nie podnosi zdarzenia „zamknięte” po zatrzymaniu ponownego łączenia. Rozwiązałem to za pomocą zdarzenia „StateChanged”. Metoda, która łączy się z serwerem SignalR:

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;
        }

Metoda ponownego połączenia:

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

Metoda niekończących się prób połączenia z serwerem (również używam tej metody do tworzenia pierwszego połączenia):

public async Task RestartConnection()
        {
            while (!ApplicationClosed)
            {
                bool connected = await ConnectToSignalRServer();
                if (connected)
                    return;
            }
        }
Константин Золин
źródło
-3

Możesz spróbować wywołać metodę serwera z systemu Android przed uruchomieniem stanu ponownego połączenia, aby zapobiec problemowi z ponownym połączeniem magicznym.

SignalR Hub C #

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

W systemie 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);
    });
}
Jongz Puangput
źródło