Понимание и обработка событий срока службы подключений в SignalR

Предупреждение

Эта документация не подходит для последней версии SignalR. Взгляните на ASP.NET Core SignalR.

В этой статье представлен обзор событий подключения SignalR, повторного подключения и отключения, которые можно обрабатывать, а также параметры времени ожидания и сохранения, которые можно настроить.

В статье предполагается, что у вас уже есть некоторые знания о SignalR и событиях срока службы подключений. Общие сведения о SignalR см. в разделе "Введение в SignalR". Список событий времени существования подключения см. в следующих ресурсах:

Версии программного обеспечения, используемые в этом разделе

Предыдущие версии этого раздела

Сведения о более ранних версиях SignalR см. в разделе SignalR более ранних версий.

Вопросы и комментарии

Оставьте отзыв о том, как вам понравилось это руководство и что, по вашему мнению, мы можем улучшить в комментариях в нижней части страницы. Если у вас есть вопросы, которые не связаны напрямую с руководством, вы можете опубликовать их на форуме ASP.NET SignalR или StackOverflow.com.

Обзор

Эта статья состоит из следующих разделов:

Ссылки на разделы справки API относятся к версии API .NET 4.5. Если вы используете .NET 4, ознакомьтесь с разделами API для версии .NET 4.

Терминология и сценарии времени существования подключения

Обработчик событий OnReconnected в Концентраторе SignalR может выполняться непосредственно после OnConnected, но не может выполняться после OnDisconnected для указанного клиента. Причина, по которой можно выполнить повторное подключение без отключения, заключается в том, что в SignalR слово "соединение" используется в нескольких значениях.

Подключения SignalR, подключения транспорта и физические подключения

В этой статье показано, как различать подключения SignalR, подключения транспорта и физические подключения:

  • Подключение SignalR относится к логической связи между клиентом и URL-адресом сервера, поддерживаемой API SignalR и идентифицируемой уникальным идентификатором подключения. Данные об этой связи поддерживаются SignalR и используются для установления транспортного подключения. Связь заканчивается и SignalR удаляет данные, когда клиент вызывает Stop метод или ограничение времени ожидания достигается, пока SignalR пытается повторно установить потерянное транспортное подключение.
  • Транспортное соединение означает логическую связь между клиентом и сервером, поддерживаемую одним из четырех транспортных API: WebSockets, серверными событиями, постоянным фреймом или длинным опросом. SignalR использует API транспорта для создания транспортного подключения, а API транспорта зависит от существования физического сетевого подключения для создания транспортного подключения. Подключение к транспорту заканчивается, когда SignalR завершает его или когда API транспорта обнаруживает, что физическое соединение нарушено.
  • Физическое подключение относится к физическим сетевым каналам — проводам, беспроводным сигналам, маршрутизаторам и т. д., которые упрощают обмен данными между клиентским компьютером и серверным компьютером. Физическое подключение должно присутствовать для установления транспортного соединения, и транспортное соединение должно быть установлено для создания соединения SignalR. Однако нарушение физического подключения не всегда немедленно завершает транспортное подключение или подключение SignalR, как описано далее в этом разделе.

На следующей схеме подключение SignalR отображено с помощью уровня API концентраторов и API PersistentConnection SignalR, транспортное подключение отображено на уровне транспортов, а физическое соединение - линиями между сервером и клиентами.

Схема архитектуры SignalR

При вызове Start метода в клиенте SignalR вы предоставляете код клиента SignalR со всеми сведениями, необходимыми для установления физического подключения к серверу. Код клиента SignalR использует эти сведения для выполнения HTTP-запроса и установления физического подключения, использующего один из четырех методов транспорта. Если подключение к транспорту прерывается или сервер выходит из строя, подключение SignalR не завершится сразу, так как у клиента по-прежнему есть информация, необходимая для автоматического повторного установления нового транспортного подключения к тому же URL-адресу SignalR. В этом сценарии никакого вмешательства от пользовательского приложения не участвует, и когда клиентский код SignalR устанавливает новое транспортное подключение, он не запускает новое подключение SignalR. Непрерывность подключения SignalR отражается на том, что идентификатор подключения, созданный при вызове Start метода, не изменяется.

Обработчик событий OnReconnected на Концентраторе выполняется при автоматическом повторном установлении транспортного соединения после его потери. Обработчик событий OnDisconnected выполняется в конце подключения SignalR. Подключение SignalR может завершиться любым из следующих способов:

  • Если клиент вызывает Stop метод, то на сервер отправляется сообщение остановки, а подключение SignalR немедленно завершается как клиентом, так и сервером.
  • После потери подключения между клиентом и сервером клиент пытается повторно подключиться, а сервер ожидает повторного подключения клиента. Если попытки повторного подключения не увенчиваются успехом, и истекает тайм-аут отключения, клиент и сервер завершают соединение SignalR. Клиент перестает пытаться повторно подключиться, и сервер удаляет его представление подключения SignalR.
  • Если клиент перестает работать без возможности вызова Stop метода, сервер ожидает повторного подключения клиента, а затем завершает подключение SignalR после периода ожидания отключения.
  • Если сервер перестает работать, клиент пытается повторно подключиться (повторно создать транспортное подключение), а затем завершает подключение SignalR после периода ожидания отключения.

Если нет проблем с подключением, а пользовательское приложение завершает подключение SignalR путем вызова Stop метода, соединение SignalR и транспортное подключение начинаются и заканчиваются примерно одновременно. В следующих разделах подробно описаны другие сценарии.

Сценарии отключения транспорта

Физические подключения могут быть медленными или могут возникнуть прерывания подключения. В зависимости от таких факторов, как продолжительность прерывания, подключение к транспорту может быть удалено. Затем SignalR пытается повторно установить транспортное подключение. Иногда API подключения транспорта обнаруживает прерывание и удаляет транспортное подключение, и SignalR сразу же обнаруживает, что соединение потеряно. В других сценариях ни API подключения к транспорту, ни SignalR не сразу же осознают, что подключение было потеряно. Для всех транспортов, кроме длительного опроса, клиент SignalR использует функцию с именем keepalive для проверки потери подключения, которые API транспорта не может обнаружить. Сведения о длительных подключениях для опроса см. в следующем разделе, "Настройки времени ожидания и keepalive".

При неактивном подключении сервер периодически отправляет клиенту пакет поддержания связи. По состоянию на дату написания этой статьи частота по умолчанию составляет каждые 10 секунд. Прослушивая эти пакеты, клиенты могут определить, возникла ли проблема с подключением. Если keepalive-пакет не получен вовремя, спустя короткое время клиент полагает, что имеются проблемы с подключением, такие как замедление или прерывания. Если пакет keepalive по-прежнему не получен после длительного времени, клиент предполагает, что соединение потеряно, и начинает попытки повторного подключения.

На следующей схеме показаны события клиента и сервера, возникающие в типичном сценарии, когда возникают проблемы с физическим подключением, которые не распознаются API транспорта немедленно. Схема применяется к следующим обстоятельствам:

  • Транспорт — это WebSockets, долгосрочное соединение Forever Frame или события, отправляемые с сервера.
  • В физическом сетевом подключении возникают различные периоды прерывания.
  • API транспорта не узнает о прерываниях, поэтому SignalR полагается на keepalive-функциональность для их обнаружения.

Разрывы соединения транспорта

Если клиент переходит в режим повторного подключения, но не может установить транспортное подключение в пределах ограничения времени ожидания отключения, сервер завершает подключение SignalR. В этом случае сервер выполняет метод концентратора OnDisconnected и помещает в очередь сообщение об отключении, чтобы отправить его клиенту, если клиенту удастся подключиться позже. Если клиент повторно подключается, он получает команду отключения и вызывает Stop метод. В этом сценарии OnReconnected не выполняется при повторном подключении клиента и OnDisconnected не выполняется при вызове Stopклиента. На следующей схеме показан этот сценарий.

Сбои в транспортировке — тайм-аут сервера

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

  • ConnectionSlow событие клиента.

    Возникает, когда предустановленная доля периода тайм-аута keepalive прошла с момента получения последнего сообщения или keepalive пинга. Период предупреждения тайм-аута keepalive по умолчанию составляет 2/3 тайм-аута keepalive. Тайм-аут поддержания соединения составляет 20 секунд, поэтому предупреждение появляется около 13 секунд.

    По умолчанию сервер отправляет keepalive сигналы каждые 10 секунд, а клиент проверяет наличие keepalive сигналов примерно каждые 2 секунды (это составляет одну треть разницы между значением тайм-аута keepalive и значением предупреждения тайм-аута keepalive).

    Если API транспорта становится известно об отключении, SignalR может быть проинформирован об отключении до прохождения периода предупреждения времени ожидания. В этом случае событие ConnectionSlow не будет вызвано, и SignalR перейдет напрямую к событию Reconnecting.

  • Reconnecting событие клиента.

    Возникает, когда (a) транспортное API обнаруживает потерю соединения, или (b) истек период времени ожидания сигнала с момента получения последнего сообщения или keepalive-сигнала. Клиентский код SignalR начинает пытаться повторно подключиться. Вы можете обработать это событие, если хотите, чтобы ваше приложение предприняло некоторые действия при потере транспортного подключения. Текущий тайм-аут keepalive по умолчанию составляет 20 секунд.

    Если клиентский код пытается вызвать метод Hub, пока SignalR находится в режиме повторного подключения, SignalR попытается отправить команду. В большинстве случаев такие попытки завершаются неудачей, но в некоторых обстоятельствах они могут завершиться успешно. Для событий, отправленных сервером, постоянных кадров и транспортов длительных опросов, SignalR использует два канала связи: один используется клиентом для отправки сообщений и другой – для их получения. Канал, используемый для получения, является постоянно открытым, и это тот, который закрывается при прервании физического подключения. Канал, используемый для отправки, остается доступным, поэтому при восстановлении физического подключения вызов метода от клиента к серверу может быть успешным до повторного создания канала получения. Возвращаемое значение не будет получено, пока SignalR повторно не откроет канал, используемый для получения.

  • Reconnected событие клиента.

    Вызывается при восстановлении транспортного соединения. Обработчик событий OnReconnected в хабе исполняется.

  • Closed событие клиента (disconnected событие в JavaScript).

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

Прерывания подключения к транспорту, которые не обнаруживаются API транспорта и не задерживают получение контрольных сигналов (keepalive pings) от сервера дольше, чем период предупреждения о тайм-ауте контрольного сигнала, могут не вызвать возникновение каких-либо событий времени существования подключения.

Некоторые сетевые среды намеренно закрывают неактивные подключения, и еще одна функция keepalive-пакетов — помочь предотвратить это, уведомляя эти сети, что подключение SignalR используется. В крайних случаях частота пакетов keepalive по умолчанию может оказаться недостаточной, чтобы предотвратить разрыв соединений. В этом случае вы можете настроить более частую отправку keepalive пингов. Дополнительные сведения см. в разделе "Время ожидания" и "Сохранение параметров " далее в этом разделе.

Примечание.

Важно: последовательность событий, описанных здесь, не гарантируется. SignalR делает каждую попытку вызвать события времени существования подключения предсказуемым образом в соответствии с этой схемой, но существует множество вариантов сетевых событий и многих способов, в которых базовые платформы коммуникации, такие как API транспорта, обрабатывают их. Например, событие Reconnected может не вызываться при повторном подключении клиента, или обработчик OnConnected на сервере может выполняться, если попытка установить соединение окажется неудачной. В этом разделе описываются только эффекты, которые обычно создаются определенными типичными обстоятельствами.

Сценарии отключения клиента

В клиенте браузера код клиента SignalR, который поддерживает подключение SignalR, выполняется в контексте JavaScript веб-страницы. Вот почему подключение SignalR должен завершиться при переходе с одной страницы на другую, и именно поэтому при подключении из нескольких окон браузера или вкладок у вас есть несколько подключений с несколькими идентификаторами подключений. Когда пользователь закрывает окно браузера или вкладку, переходит на новую страницу или обновляет страницу, подключение SignalR немедленно заканчивается, так как клиентский код SignalR обрабатывает это событие браузера и вызывает Stop метод. В этих сценариях или на любой клиентской платформе, когда приложение вызывает Stop метод, OnDisconnected обработчик событий выполняется немедленно на сервере, а клиент вызывает Closed событие (событие называется disconnected в JavaScript).

Если клиентское приложение или компьютер, на котором он работает при сбоях или переходит в спящий режим (например, когда пользователь закрывает ноутбук), сервер не сообщает о том, что произошло. Насколько известно серверу, потеря клиента может быть вызвана прерыванием подключения, и клиент может попытаться повторно подключиться. Поэтому в этих сценариях сервер ожидает повторного подключения клиента и OnDisconnected не выполняется до истечения срока ожидания отключения (около 30 секунд по умолчанию). На следующей схеме показан этот сценарий.

Сбой клиентского компьютера

Сценарии отключения сервера

Когда сервер отключается — будь то из-за перезагрузки, сбоя, переработки домена приложения и т. д. — результат может быть похож на потерянное соединение, или API транспорта и SignalR могут сразу понять, что сервер исчез, и SignalR может начать повторное подключение без вызова события ConnectionSlow. Если клиент переходит в режим повторного подключения, и если сервер восстанавливается или перезапускается, или новый сервер подключен к сети до истечения срока ожидания отключения, клиент будет повторно подключаться к восстановленным или новому серверу. В этом случае подключение SignalR продолжается на клиенте, и событие Reconnected вызывается. На первом сервере OnDisconnected никогда не выполняется, а на новом сервере OnReconnected выполняется, хотя OnConnected никогда не выполнялся для этого клиента на этом сервере раньше. (Результат такой же, если клиент повторно подключается к тому же серверу после перезагрузки или перезапуска домена приложения, так как при перезапуске сервера нет памяти о предыдущем действии подключения.) На следующей схеме предполагается, что API транспорта сразу же узнает о потерянной связи, поэтому ConnectionSlow событие не вызывается.

Сбой сервера и повторное подключение Если сервер не станет доступным в течение периода ожидания отключения, подключение SignalR завершается. В этом сценарии событие "Закрыто" ("отключено" в клиентах JavaScript) вызывается на клиенте, но onDisconnected никогда не вызывается на сервере. На следующей диаграмме предполагается, что API транспорта не обнаруживает потерю соединения, поэтому она выявляется при помощи функции поддержания активности SignalR, и вызывается событие ConnectionSlow.

Сбой сервера и время ожидания

Параметры времени ожидания и удержания соединения

Значения по умолчанию ConnectionTimeout, DisconnectTimeout и KeepAlive подходят для большинства сценариев, но могут быть изменены, если ваша среда имеет особые потребности. Например, если ваша сетевая среда закрывает соединения, которые простаивают в течение 5 секунд, вам может потребоваться уменьшить значение параметра поддержки соединения.

ConnectionTimeout

Этот параметр представляет время, которое нужно оставить транспортное подключение открытым и ожидая ответа, прежде чем закрыть его и открыть новое подключение. Значение по умолчанию — 110 секунд.

Этот параметр применяется только при отключении функции поддержания соединения, которая обычно применяется только к транспортировке с длительным опросом. На следующей схеме показано влияние этого параметра на длинное подключение к транспорту опроса.

Транспортное соединение длительного опроса

DisconnectTimeout

Этот параметр представляет время ожидания после потери транспортного подключения перед вызовом Disconnected события. Значение по умолчанию — 30 секунд. При установке DisconnectTimeout значение KeepAlive автоматически устанавливается на 1/3 от значения DisconnectTimeout.

KeepAlive

Этот параметр представляет собой время ожидания перед отправкой keepalive-пакета через неактивное соединение. Значение по умолчанию — 10 секунд. Это значение не должно превышать 1/3 DisconnectTimeout значения.

Если вы хотите задать оба DisconnectTimeout и KeepAlive, задайте KeepAlive после DisconnectTimeout. В противном случае параметр KeepAlive будет перезаписан, когда DisconnectTimeout автоматически установит KeepAlive в значение 1/3 от продолжительности времени ожидания.

Если вы хотите отключить функцию поддержания соединения, установите KeepAlive в значение NULL. Функция поддержания соединения автоматически отключается для длинного опроса.

Изменение настроек времени ожидания и поддержания соединения

Чтобы изменить значения по умолчанию для этих параметров, задайте их в Application_Startфайле Global.asax , как показано в следующем примере. Значения, отображаемые в примере кода, совпадают со значениями по умолчанию.

protected void Application_Start(object sender, EventArgs e)
{
    // Make long polling connections wait a maximum of 110 seconds for a
    // response. When that time expires, trigger a timeout command and
    // make the client reconnect.
    GlobalHost.Configuration.ConnectionTimeout = TimeSpan.FromSeconds(110);
    
    // Wait a maximum of 30 seconds after a transport connection is lost
    // before raising the Disconnected event to terminate the SignalR connection.
    GlobalHost.Configuration.DisconnectTimeout = TimeSpan.FromSeconds(30);
    
    // For transports other than long polling, send a keepalive packet every
    // 10 seconds. 
    // This value must be no more than 1/3 of the DisconnectTimeout value.
    GlobalHost.Configuration.KeepAlive = TimeSpan.FromSeconds(10);
    
    RouteTable.Routes.MapHubs();
}

Как уведомить пользователя об отключении

В некоторых приложениях может потребоваться отобразить сообщение пользователю при возникновении проблем с подключением. У вас есть несколько вариантов того, как и когда это сделать. Ниже приведены примеры кода для клиента JavaScript с помощью созданного прокси-сервера.

  • Обработайте connectionSlow событие, чтобы отобразить сообщение, как только SignalR знает о проблемах подключения, прежде чем он переходит в режим повторного подключения.

    $.connection.hub.connectionSlow(function() {
        notifyUserOfConnectionProblem(); // Your function to notify user.
    });
    
  • Обработайте событие reconnecting, чтобы отобразить сообщение, когда SignalR обнаружил отключение и переходит в режим восстановления соединения.

    $.connection.hub.reconnecting(function() {
        notifyUserOfTryingToReconnect(); // Your function to notify user.
    });
    
  • Обработайте disconnected событие, чтобы отобразить сообщение, когда время ожидания попытки повторного подключения истекло. В этом сценарии единственным способом повторного установления соединения с сервером является переподключение SignalR, вызвав метод Start, что создаст новый идентификатор подключения. В следующем примере кода используется флаг, чтобы уведомление было выдано только после истечения времени ожидания повторного подключения, а не после обычного завершения подключения SignalR, произведенного вызовом метода Stop.

    var tryingToReconnect = false;
    
    $.connection.hub.reconnecting(function() {
        tryingToReconnect = true;
    });
    
    $.connection.hub.reconnected(function() {
        tryingToReconnect = false;
    });
    
    $.connection.hub.disconnected(function() {
        if(tryingToReconnect) {
            notifyUserOfDisconnect(); // Your function to notify user.
        }
    });
    

Как непрерывно переподключаться

В некоторых приложениях может понадобиться автоматически восстановить подключение после его потери и когда попытка подключения завершилась по таймауту. Для этого вызовите метод Start из обработчика событий Closed (disconnected на клиентах JavaScript). Может потребоваться подождать некоторое время перед вызовом Start , чтобы избежать этого слишком часто, когда сервер или физическое подключение недоступны. Следующий пример кода предназначен для клиента JavaScript с помощью созданного прокси-сервера.

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

Потенциальная проблема, связанная с мобильными клиентами, заключается в том, что попытки непрерывного повторного подключения, когда сервер или физическое подключение недоступно, может привести к ненужным утечкам батареи.

Отключение клиента в коде сервера

У SignalR версии 2 нет встроенного API сервера для отключения клиентов. В будущем есть планы по добавлению этой функции. В текущем выпуске SignalR самый простой способ отключить клиент от сервера — реализовать метод отключения на клиенте и вызвать этот метод с сервера. В следующем примере кода показан метод отключения для клиента JavaScript с помощью созданного прокси-сервера.

var myHubProxy = $.connection.myHub
myHubProxy.client.stopClient = function() {
    $.connection.hub.stop();
};

Предупреждение

Безопасность. Ни этот метод для отключения клиентов, ни предлагаемый встроенный API не будет решать сценарий взломанных клиентов, выполняющих вредоносный код, так как клиенты могут повторно подключиться или взломанный код может удалить stopClient метод или изменить то, что он делает. Соответствующее место для реализации защиты от атак типа "отказ в обслуживании" (DOS) находится не на уровне платформы или сервера, а во фронтэнд-инфраструктуре.

Обнаружение причины отключения

SignalR 2.1 добавляет перегрузку к событию сервера OnDisconnect, указывающему, было ли отключение клиента намеренным, вместо времени ожидания. Параметр StopCalled равен true, если клиент явно закрыл подключение. В JavaScript, если ошибка сервера привела клиента к отключению, сведения об ошибке будут переданы клиенту как $.connection.hub.lastError.

Код сервера C#: stopCalled параметр

public override System.Threading.Tasks.Task OnDisconnected(bool stopCalled)
{
    if (stopCalled)
    {
        Console.WriteLine(String.Format("Client {0} explicitly closed the connection.", Context.ConnectionId));
    }
    else
    {
        Console.WriteLine(String.Format("Client {0} timed out .", Context.ConnectionId));
    }
            
    return base.OnDisconnected(stopCalled);
}

Клиентский код JavaScript: доступ к lastError в событии disconnect.

$.connection.hub.disconnected(function () {
    if ($.connection.hub.lastError) 
        { alert("Disconnected. Reason: " +  $.connection.hub.lastError.message); }
});