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ę.
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:
źródło
reconnecting
zdarzenie, 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.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") } }
źródło
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
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); }); }
źródło