Рекомендации по повторному подключению клиента SignalR 2.0 .NET к концентратору сервера

86

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

Поскольку SignalR в некоторых случаях автоматически переподключается автоматически, мне интересно, не хватает ли мне функции или настройки конфигурации?

Как лучше всего настроить автоматическое переподключение клиента?


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

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

Эндер2050
источник

Ответы:

71

Я наконец понял это. Вот что я узнал, задав этот вопрос:

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

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

Мы протестировали три сценария:

Сценарий A - подключение при первой загрузке приложения. Это работало безупречно с первого дня. Подключение выполняется менее чем за 0,25 секунды даже при использовании мобильных подключений 3G. (при условии, что радио уже включено)

Сценарий B - повторное подключение к серверу SignalR после того, как приложение было бездействующим / закрыто в течение 30 секунд. В этом сценарии клиент SignalR в конечном итоге повторно подключится к серверу самостоятельно без каких-либо специальных действий, но, похоже, он ждет ровно 30 секунд перед попыткой повторного подключения. (слишком медленно для нашего приложения)

В течение этого 30-секундного периода ожидания мы пытались вызвать HubConnection.Start (), но это не дало результата. И вызов HubConnection.Stop () также занимает 30 секунд. Я обнаружил связанную ошибку на сайте SignalR, которая, похоже, устранена , но у нас все еще есть та же проблема в версии 2.0.3.

Сценарий C - повторное подключение к серверу SignalR после простоя / закрытия приложения в течение 120 секунд или дольше. В этом сценарии для транспортного протокола SignalR уже истекло время ожидания, поэтому клиент никогда не подключается автоматически. Это объясняет, почему клиент иногда, но не всегда, повторно подключался сам. Хорошая новость в том, что вызов HubConnection.Start () работает почти мгновенно, как сценарий A.

Мне потребовалось время, чтобы понять, что условия повторного подключения были разными в зависимости от того, было ли приложение закрыто на 30 секунд или более 120 секунд. И хотя журналы трассировки SignalR освещают то, что происходит с базовым протоколом, я не верю, что есть способ обрабатывать события транспортного уровня в коде. (событие Closed () срабатывает через 30 секунд в сценарии B, мгновенно в сценарии C; в свойстве State указано «Connected» во время этих периодов ожидания повторного подключения; никаких других соответствующих событий или методов)

Решение: Решение очевидно. Мы не ждем, пока SignalR выполнит свою магию повторного подключения. Вместо этого, когда приложение активируется или когда сетевое соединение телефона восстанавливается, мы просто очищаем события и отменяем ссылку на HubConnection (не можем удалить его, потому что это занимает 30 секунд, мы надеемся, что сборка мусора позаботится об этом. ) и создание нового экземпляра. Теперь все отлично работает. По какой-то причине я подумал, что нам следует повторно использовать постоянное соединение и переподключаться, а не просто создавать новый экземпляр.

Эндер2050
источник
5
Не могли бы вы опубликовать код? Просто интересно, как вы это структурировали. Я также использую Signalr в приложении чата из PCL в приложении Xamarin. Это работает действительно здорово, за исключением того, что я не могу заставить работать магию повторного подключения после того, как телефон был выключен и снова включен. Клянусь, IT Crowd сказал, что это все, что мне нужно было сделать.
Тимоти Ли Рассел
1
Привет, Ender2050, я заметил, что как только устройство Android отключено от сервера, оно никогда не подключается снова. Поэтому я реализовал сигнализацию, которая запускается каждые 5 минут и проверяет соединение signalR с серверным концентратором. В событии Alarm Tick я проверяю, является ли объект подключения нулевым или connectionId пуст, а затем снова установил соединение. Но это не сработает. Пользователь должен убить приложение и снова открыть его. Я использовал java-клиент для android и C # .Net для обслуживающего узла. Ищу вашу помощь в решении этой проблемы.
jignesh 05
1
FYI, 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 и снова пытается вызвать hub.Start () , в результате чего исходный hub.Start () никогда не завершится. Мое решение заключалось в том, чтобы подписаться на Closed только после успешного запуска () и сразу отказаться от подписки на Closed в обратном вызове.
Оран Деннисон
3
@MikeBaz Я думаю, вы имеете в виду переподключение к группам
Simon_Weaver
1
@KingOfHypocrites Я подписался на reconnectingсобытие, которое запускается, когда концентратор теряет соединение, и устанавливает для этой переменной (например shouldReconnect) значение true. Итак, я адаптировал ваш пример, чтобы проверить эту переменную. Это выглядит мило.
Alisson
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);
    });
}
Йонгз Пуангпут
источник