Я наконец понял это. Вот что я узнал, задав этот вопрос:
Предыстория: мы создаем приложение для 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 секунд, мы надеемся, что сборка мусора позаботится об этом. ) и создание нового экземпляра. Теперь все отлично работает. По какой-то причине я подумал, что нам следует повторно использовать постоянное соединение и переподключаться, а не просто создавать новый экземпляр.
Установка таймера на отключенное событие для автоматической попытки повторного подключения - единственный известный мне метод.
В javascript это делается так:
$.connection.hub.disconnected(function() { setTimeout(function() { $.connection.hub.start(); }, 5000); // Restart connection after 5 seconds. });
Это рекомендуемый подход в документации:
источник
reconnecting
событие, которое запускается, когда концентратор теряет соединение, и устанавливает для этой переменной (напримерshouldReconnect
) значение true. Итак, я адаптировал ваш пример, чтобы проверить эту переменную. Это выглядит мило.Поскольку 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") } }
источник
Я добавляю обновление для ответа 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; } }
источник
Вы можете попытаться вызвать серверный метод со своего 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); }); }
источник