Найкраща практика повторного підключення клієнта SignalR 2.0 .NET до концентратора сервера


86

Я використовую SignalR 2.0 з клієнтом .NET у мобільному додатку, який повинен обробляти різні типи відключень. Іноді клієнт SignalR підключається автоматично - а іноді його доводиться підключати безпосередньо за допомогою HubConnection.Start()повторного дзвінка .

Оскільки SignalR чарівним чином повторно автоматично підключається, мені цікаво, не вистачає функції чи налаштування конфігурації?

Який найкращий спосіб налаштувати клієнта, який автоматично підключається автоматично?


Я бачив приклади javascript, які обробляють Closed()подію, а потім підключаються через n-секунд. Чи існує якийсь рекомендований підхід?

Я прочитав документацію та кілька статей про термін служби підключень SignalR, але мені все ще незрозуміло, як обробляти повторне підключення клієнта.


Ви можете поділитися кодом?
Pxaml

Відповіді:


71

Я нарешті зрозумів це. Ось що я дізнався з початку цього запитання:

Передумови: Ми створюємо додаток для iOS за допомогою Xamarin / Monotouch та клієнта .NET SignalR 2.0.3. Ми використовуємо протоколи SignalR за замовчуванням - і, схоже, ми використовуємо SSE замість веб-сокетів. Я ще не впевнений, чи можливо використовувати веб-сокети з Xamarin / Monotouch. Все розміщується на веб-сайтах Azure.

Нам потрібна була програма для швидкого повторного підключення до нашого сервера SignalR, але ми постійно мали проблеми, коли з’єднання не підключалося самостійно - або повторне підключення зайняло рівно 30 секунд (через базовий час очікування протоколу).

Ми закінчили тестування на три сценарії:

Сценарій А - підключення під час першого завантаження програми. Це працювало бездоганно з першого дня. З'єднання завершується менш ніж за .25 секунд, навіть через мобільні з'єднання 3G. (припускаючи, що радіо вже включено)

Сценарій B - повторне підключення до сервера SignalR після того, як програма не працювала / не працювала протягом 30 секунд. У цьому випадку клієнт SignalR зрештою самостійно підключиться до сервера без особливої ​​роботи, але, здається, він чекає рівно 30 секунд, перш ніж намагатись підключитися. (занадто повільно для нашого додатка)

Протягом цього 30-секундного періоду очікування ми спробували зателефонувати HubConnection.Start (), що не мало ефекту. А виклик HubConnection.Stop () також триває 30 секунд. Я знайшов відповідну помилку на сайті SignalR, яка, здається, була вирішена , але у нас все ще є та сама проблема у версії 2.0.3.

Сценарій C - повторне підключення до сервера SignalR після того, як програма не працювала / не працювала 120 секунд або довше. У цьому сценарії транспортний протокол SignalR вже вичерпано, тому клієнт ніколи не підключається автоматично. Це пояснює, чому іноді клієнт, але не завжди, самостійно підключався. Хороша новина полягає в тому, що виклик HubConnection.Start () працює майже миттєво, як сценарій А.

Тож мені знадобився деякий час, щоб зрозуміти, що умови повторного підключення були різними залежно від того, чи було додаток закрито на 30 секунд проти 120+ секунд. І хоча журнали трасування SignalR висвітлюють, що відбувається з базовим протоколом, я не вірю, що існує спосіб обробляти події транспортного рівня в коді. (подія Closed () спрацьовує через 30 секунд за сценарієм B, миттєво за сценарієм C; у власності штату вказано "Підключено" під час цих періодів очікування повторного підключення; жодних інших відповідних подій або методів)

Рішення: Рішення очевидне. Ми не чекаємо, поки SignalR зробить магію відновлення. Натомість, коли додаток активовано або коли мережеве з’єднання телефону відновлено, ми просто очищаємо події та скасовуємо посилання на HubConnection (не можемо утилізувати його, оскільки це займає 30 секунд, сподіваємось, збір сміття подбає про це ) та створення нового екземпляра. Зараз все працює чудово. З якоїсь причини я подумав, що нам слід повторно використовувати постійне з’єднання та повторно підключатись, а не просто створювати новий екземпляр.


5
Чи хотіли б ви опублікувати якийсь код? Просто цікаво, як ви це структурували. Я також використовую Signalr у програмі чату з PCL у програмі Xamarin. Це працює дуже добре, за винятком того, що я мабуть не можу змусити магію повторного підключення спрацювати після того, як телефон було вимкнено і ввімкнено. Клянусь, ІТ-натовп сказав, що це все, що мені потрібно було зробити.
Тімоті Лі Рассел

1
Привіт, Ender2050, я помітив, що як тільки пристрій Android відключено від сервера, то більше ніколи не підключається знову, тому я запровадив сигнал тривоги, який запускається кожні 5 хвилин, і перевіряю наявність зв’язку signalR із концентратором сервера. connectionId порожній, а потім встановив підключення знову. Але це не працює добре. Користувачеві потрібно вбити програму та відкрити її знову. Я використовував Java-клієнт для android та C # .Net для сервісного центру. Шукаємо вашої допомоги для вирішення цієї проблеми.
jignesh

1
З повагою, Mono не має веб-розеток. Ось чому ваші програми Xamarin завжди використовують SSE. Ви можете написати консольний клієнт. Якщо запустити його на Mono, він використовуватиме SSE. Якщо ви запускаєте його на Windows (принаймні Windows 8, оскільки 7 також не підтримує веб-сокети), він буде використовувати веб-сокети.
daramasala

@ Ender2050 Будь ласка, чи можете ви розширити своє рішення кількома прикладами коду?
Magrangs

У нас виникла проблема з повторним підключенням до SignalR Hub (бібліотека SignalR версії 2.2.2) з додатка Android, який використовує «Бібліотеку клієнта SignalR Java», та додатка iOS, який використовує «Бібліотеку SignalR Object C». Клієнтські бібліотеки на обох платформах не оновлювались деякий час. Я думаю, що проблема в тому, що несумісність протоколу SignalR між клієнтом і сервером.
Надім Хоссен Сонет

44

Встановлення таймера для відключеної події для автоматичної спроби повторного підключення - єдиний метод, про який мені відомо.

У javascript це робиться так:

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

Це рекомендований підхід у документації:

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


1
одна підказка - переконайтеся, що ви виконуєте будь-яку функцію запуску на старті, як інакше, наприклад, повторно підключіться до концентраторів.
MikeBaz - MSFT

1
Я виявив, що з клієнтом .NET, якщо ви передплатите подію Closed перед тим, як зателефонувати hub.Start (), якщо спочатку не вдалося підключитися, тоді ваш обробник Closed event викликається і намагається знову викликати hub.Start () , внаслідок чого оригінальний hub.Start () ніколи не завершується. Моє рішення полягало в тому, щоб підписатися на Closed лише після успішного запуску () і негайно скасувати підписку на Closed у зворотному дзвінку.
Оран Деннісон,

3
@MikeBaz Я думаю, ви маєте на увазі відновити зв’язок з групами
Simon_Weaver

1
@KingOfHypocrites Я підписався на reconnectingподію, яка запускається , коли концентратор втрачає зв’язок і встановлює для цієї змінної (наприклад shouldReconnect) значення true. Тож я адаптував ваш приклад для перевірки цієї змінної. Це виглядає приємно.
Аліссон,

2
Я зробив випадкове число від 10 до 60 секунд. У нас занадто багато клієнтів, щоб просто витратити 5 секунд, ми б самі DDoS. $ .connection.hub.disconnected (function () {setTimeout (function () {$ .connection.hub.start ();}, (Math.floor (Math.random () * 50) + 10) * 1000); });
Brain2000

17

Оскільки OP запитує клієнта .NET (реалізація winform нижче),

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")
    }
}

Я виявив у SignalR 2.3.0, що якби я чекав підключення у випадку Closed (), він іноді не підключався. Однак, якби я зателефонував вручну Wait () на подію з таймаутом, наприклад 10 секунд, він би автоматично та знову викликав Closed () кожні 10 секунд, і тоді повторне підключення спрацювало.
Brain2000

0

Я додаю оновлення для відповіді ibubi . Можливо, це комусь потрібно. Я виявив, що в деяких випадках сигнальник не піднімається "закритою" подією після припинення повторного підключення. Я вирішив це за допомогою події "StateChanged". Спосіб підключення до сервера 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;
        }

Спосіб повторного підключення:

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

Метод нескінченних спроб підключитися до сервера (також я використовую цей метод для створення першого з'єднання):

public async Task RestartConnection()
        {
            while (!ApplicationClosed)
            {
                bool connected = await ConnectToSignalRServer();
                if (connected)
                    return;
            }
        }

-3

Ви можете спробувати викликати серверний метод з вашого Android перед початком стану повторного підключення, щоб запобігти проблемі магічного повторного підключення.

SignalR Hub C #

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

В 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);
    });
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.