клиент JavaScript ASP.NET Core SignalR

Автор: Рэйчел Аппель (Rachel Appel)

Клиентская библиотека JavaScript для ASP.NET Core SignalR позволяет разработчикам вызывать код концентратора на стороне SignalR сервера.

Установка клиентского SignalR пакета

Клиентская SignalR библиотека JavaScript поставляется в виде пакета npm . В следующих разделах описаны различные способы установки клиентской библиотеки.

Установка с помощью npm

Выполните следующие команды из консоли диспетчер пакетов:

npm init -y
npm install @microsoft/signalr

npm устанавливает содержимое пакета в папке node_modules\@microsoft\signalr\dist\browser . Создайте папку wwwroot/lib/signalr . Скопируйте файл в signalr.js папку wwwroot/lib/signalr .

Ссылка на SignalR клиент JavaScript в элементе <script> . Например:

<script src="~/lib/signalr/signalr.js"></script>

Использование сеть доставки содержимого (CDN)

Чтобы использовать клиентную библиотеку без предварительных требований npm, наведите ссылку на размещенную в CDN копию клиентской библиотеки. Например:

<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/6.0.1/signalr.js"></script>

Клиентская библиотека доступна на следующих CDN:

Установка с помощью LibMan

LibMan можно использовать для установки определенных файлов клиентской библиотеки из клиентской библиотеки, размещенной в CDN. Например, добавьте в проект только минифицированный файл JavaScript. Дополнительные сведения об этом подходе см. в разделе "Добавление клиентской SignalR библиотеки".

Подключение в концентратор

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

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .configureLogging(signalR.LogLevel.Information)
    .build();

async function start() {
    try {
        await connection.start();
        console.log("SignalR Connected.");
    } catch (err) {
        console.log(err);
        setTimeout(start, 5000);
    }
};

connection.onclose(async () => {
    await start();
});

// Start the connection.
start();

Подключения между источниками (CORS)

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

При выполнении запросов между доменами код клиента должен использовать абсолютный URL-адрес вместо относительного URL-адреса. Для запросов между доменами измените значение .withUrl("/chathub").withUrl("https://{App domain name}/chathub")на .

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

using SignalRChat.Hubs;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddSignalR();

builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(
        builder =>
        {
            builder.WithOrigins("https://example.com")
                .AllowAnyHeader()
                .WithMethods("GET", "POST")
                .AllowCredentials();
        });
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

// UseCors must be called before MapHub.
app.UseCors();

app.MapRazorPages();
app.MapHub<ChatHub>("/chatHub");

app.Run();

UseCors необходимо вызвать перед вызовом MapHub.

Методы концентратора вызовов от клиента

Клиенты JavaScript вызывают общедоступные методы в центрах с помощью метода вызова концентратора Подключение ion. Метод invoke принимает:

  • Имя метода концентратора.
  • Все аргументы, определенные в методе концентратора.

В следующем выделенном коде имя метода в концентраторе равно SendMessage. Второй и третий аргументы, передаваемые для invoke сопоставления с аргументами и message аргументами концентратораuser:

try {
    await connection.invoke("SendMessage", user, message);
} catch (err) {
    console.error(err);
}

Вызов методов концентратора из клиента поддерживается только при использовании службы Azure SignalR в режиме по умолчанию . Дополнительные сведения см. в разделе Часто задаваемые вопросы (репозиторий GitHub azure-signalr).

Метод invoke возвращает JavaScript Promise. Разрешение Promise выполняется с возвращаемым значением (при наличии), когда метод на сервере возвращается. Если метод на сервере выдает ошибку, Promise он отклоняется с сообщением об ошибке. Используйте async и методы и thenawaitPromisecatch методы для обработки этих случаев.

Клиенты JavaScript также могут вызывать общедоступные методы в центрах с помощью метода отправкиHubConnection. invoke В отличие от метода, send метод не ожидает ответа от сервера. Метод send возвращает JavaScript Promise. Разрешается Promise при отправке сообщения на сервер. Если сообщение отправляет сообщение об ошибке, Promise сообщение об ошибке отклоняется. Используйте async и методы и thenawaitPromisecatch методы для обработки этих случаев.

Использование sendне ожидает, пока сервер не получит сообщение. Следовательно, невозможно вернуть данные или ошибки с сервера.

Вызов клиентских методов из концентратора

Чтобы получать сообщения из концентратора, определите метод с помощью метода .HubConnection

  • Имя клиентского метода JavaScript.
  • Аргументы концентратора передаются методу.

В следующем примере используется ReceiveMessageимя метода. Имена аргументов:usermessage

connection.on("ReceiveMessage", (user, message) => {
    const li = document.createElement("li");
    li.textContent = `${user}: ${message}`;
    document.getElementById("messageList").appendChild(li);
});

Приведенный выше код выполняется при connection.on вызове кода на стороне SendAsync сервера с помощью метода:

using Microsoft.AspNetCore.SignalR;
namespace SignalRChat.Hubs;

public class ChatHub : Hub
{
    public async Task SendMessage(string user, string message)
    {
        await Clients.All.SendAsync("ReceiveMessage", user, message);
    }
}

SignalR определяет, какой метод клиента следует вызывать путем сопоставления имени метода и аргументов, определенных в SendAsync и connection.on.

Рекомендуется вызвать метод запуска после HubConnectionon. Это гарантирует регистрацию обработчиков перед получением сообщений.

Обработка ошибок и ведение журнала

Используется console.error для вывода ошибок в консоль браузера, когда клиент не может подключиться или отправить сообщение:

try {
    await connection.invoke("SendMessage", user, message);
} catch (err) {
    console.error(err);
}

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

  • signalR.LogLevel.Error: сообщения об ошибках. Error Регистрирует только сообщения.
  • signalR.LogLevel.Warning: предупреждают сообщения о потенциальных ошибках. Журналы Warningи Error сообщения.
  • signalR.LogLevel.Information: сообщения о состоянии без ошибок. Журналы Informationи WarningError сообщения.
  • signalR.LogLevel.Trace: трассировка сообщений. Регистрирует все данные, включая данные, транспортируемые между концентратором и клиентом.

Используйте метод configureLogging в Hub Подключение ionBuilder, чтобы настроить уровень журнала. Сообщения записываются в консоль браузера:

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .configureLogging(signalR.LogLevel.Information)
    .build();

Повторное подключение клиентов

Автоматическое повторное подключение

Клиент SignalR JavaScript можно настроить для автоматического повторного подключения с помощью метода WithAutomaticReconnect в Hub Подключение ionBuilder. По умолчанию он не будет автоматически повторно подключаться.

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withAutomaticReconnect()
    .build();

Без параметров WithAutomaticReconnect настраивает клиент ждать 0, 2, 10 и 30 секунд соответственно, прежде чем пытаться повторно подключиться. После четырех неудачных попыток он останавливает попытку повторного подключения.

Перед началом любых попыток повторного подключения :HubConnection

  • Переходит в HubConnectionState.Reconnecting состояние и запускает обратные onreconnecting вызовы.
  • Не переходит в Disconnected состояние и запускает обратные onclose вызовы, такие как HubConnection без автоматического повторного подключения.

Подход повторного подключения предоставляет возможность:

  • Предупреждать пользователей о том, что подключение было потеряно.
  • Отключить элементы пользовательского интерфейса.
connection.onreconnecting(error => {
    console.assert(connection.state === signalR.HubConnectionState.Reconnecting);

    document.getElementById("messageInput").disabled = true;

    const li = document.createElement("li");
    li.textContent = `Connection lost due to error "${error}". Reconnecting.`;
    document.getElementById("messageList").appendChild(li);
});

Если клиент успешно повторно подключается в рамках первых четырех попыток, HubConnection переход обратно в Connected состояние и обратный onreconnected вызов. Это дает возможность сообщить пользователям, что подключение было восстановлено.

Так как подключение выглядит совершенно новым на сервере, connectionId новое предоставляется обратному вызову onreconnected .

Параметр onreconnectedобратного вызова connectionId не определен, если HubConnection настроено пропустить согласование.

connection.onreconnected(connectionId => {
    console.assert(connection.state === signalR.HubConnectionState.Connected);

    document.getElementById("messageInput").disabled = false;

    const li = document.createElement("li");
    li.textContent = `Connection reestablished. Connected with connectionId "${connectionId}".`;
    document.getElementById("messageList").appendChild(li);
});

withAutomaticReconnect не настраивает повторные HubConnection начальные сбои запуска, поэтому при выполнении сбоев запуска необходимо обрабатывать вручную:

async function start() {
    try {
        await connection.start();
        console.assert(connection.state === signalR.HubConnectionState.Connected);
        console.log("SignalR Connected.");
    } catch (err) {
        console.assert(connection.state === signalR.HubConnectionState.Disconnected);
        console.log(err);
        setTimeout(() => start(), 5000);
    }
};

Если клиент не успешно пересоединяется в течение первых четырех попыток, HubConnection переход Disconnected к состоянию и запускает обратные вызовы onclose . Это дает возможность информировать пользователей:

  • Подключение было окончательно потеряно.
  • Попробуйте обновить страницу:
connection.onclose(error => {
    console.assert(connection.state === signalR.HubConnectionState.Disconnected);

    document.getElementById("messageInput").disabled = true;

    const li = document.createElement("li");
    li.textContent = `Connection closed due to error "${error}". Try refreshing this page to restart the connection.`;
    document.getElementById("messageList").appendChild(li);
});

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

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withAutomaticReconnect([0, 0, 10000])
    .build();

    // .withAutomaticReconnect([0, 2000, 10000, 30000]) yields the default behavior

В предыдущем примере настраивается HubConnection запуск повторного подключения сразу после потери подключения. Конфигурация по умолчанию также ожидает повторного подключения к нулю секунд.

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

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

Настроенное время повторного подключения отличается от поведения по умолчанию путем остановки после сбоя третьей попытки повторного подключения вместо попытки повторного подключения в течение еще 30 секунд.

Для получения большего контроля над временем и количеством попыток withAutomaticReconnect автоматического повторного подключения принимает объект, реализующий IRetryPolicy интерфейс, имеющий один метод с именем nextRetryDelayInMilliseconds.

nextRetryDelayInMilliseconds принимает один аргумент с типом RetryContext. Имеет RetryContext три свойства: previousRetryCountelapsedMilliseconds и retryReason которые являются number, number а также соответственноError. Перед первой попыткой previousRetryCount повторного подключения оба и elapsedMilliseconds будет нулевым, и retryReason будет ошибка, которая привела к потере подключения. После каждой неудачной попытки previousRetryCount повторных попыток будет увеличиваться по одному, elapsedMilliseconds будет обновлено, чтобы отразить время повторного подключения до сих пор в миллисекундах, и retryReason будет ошибка, которая вызвала последнюю попытку повторного подключения.

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

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withAutomaticReconnect({
        nextRetryDelayInMilliseconds: retryContext => {
            if (retryContext.elapsedMilliseconds < 60000) {
                // If we've been reconnecting for less than 60 seconds so far,
                // wait between 0 and 10 seconds before the next reconnect attempt.
                return Math.random() * 10000;
            } else {
                // If we've been reconnecting for more than 60 seconds so far, stop reconnecting.
                return null;
            }
        }
    })
    .build();

Кроме того, можно написать код, который повторно подключает клиент вручную, как показано в следующем разделе.

Повторное подключение вручную

Следующий код демонстрирует типичный подход повторного подключения вручную:

  1. Функция (в данном случае start функция) создается для запуска подключения.
  2. Вызовите функцию start в обработчике событий подключения onclose .
async function start() {
    try {
        await connection.start();
        console.log("SignalR Connected.");
    } catch (err) {
        console.log(err);
        setTimeout(start, 5000);
    }
};

connection.onclose(async () => {
    await start();
});

В производственных реализациях обычно используется экспоненциальное обратное или повторное выполнение указанного количества раз.

Вкладка "Спящий браузер"

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

  • Воспроизведение звука
  • Хранение веб-блокировки
  • IndexedDB Хранение блокировки
  • Подключение к USB-устройству
  • Запись видео или звука
  • Зеркало
  • Запись окна или отображения

Эвристики браузера могут меняться со временем и могут отличаться между браузерами. Проверьте матрицу поддержки и узнайте, какой метод лучше всего подходит для ваших сценариев.

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

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

var lockResolver;
if (navigator && navigator.locks && navigator.locks.request) {
    const promise = new Promise((res) => {
        lockResolver = res;
    });

    navigator.locks.request('unique_lock_name', { mode: "shared" }, () => {
        return promise;
    });
}

В приведенном выше примере кода:

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

Дополнительные ресурсы

Автор: Рэйчел Аппель (Rachel Appel)

Клиентская библиотека JavaScript для ASP.NET Core SignalR позволяет разработчикам вызывать код концентратора на стороне сервера.

Просмотреть или скачать образец кода (описание загрузки)

Установка клиентского SignalR пакета

Клиентская SignalR библиотека JavaScript поставляется в виде пакета npm . В следующих разделах описаны различные способы установки клиентской библиотеки.

Установка с помощью npm

Для Visual Studio выполните следующие команды из консоли диспетчер пакетов во время работы в корневой папке. Для Visual Studio Code выполните следующие команды из интегрированного терминала.

npm init -y
npm install @microsoft/signalr

npm устанавливает содержимое пакета в папке node_modules\@microsoft\signalr\dist\browser . Создайте новую папку с именем signalr в папке wwwroot\lib . Скопируйте файл в signalr.js папку wwwroot\lib\signalr .

Ссылка на SignalR клиент JavaScript в элементе <script> . Например:

<script src="~/lib/signalr/signalr.js"></script>

Использование сеть доставки содержимого (CDN)

Чтобы использовать клиентную библиотеку без предварительных требований npm, наведите ссылку на размещенную в CDN копию клиентской библиотеки. Например:

<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/3.1.7/signalr.js"></script>

Клиентская библиотека доступна на следующих CDN:

Установка с помощью LibMan

LibMan можно использовать для установки определенных файлов клиентской библиотеки из клиентской библиотеки, размещенной в CDN. Например, добавьте в проект только минифицированный файл JavaScript. Дополнительные сведения об этом подходе см. в разделе "Добавление клиентской SignalR библиотеки".

Подключение в концентратор

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

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .configureLogging(signalR.LogLevel.Information)
    .build();

async function start() {
    try {
        await connection.start();
        console.log("SignalR Connected.");
    } catch (err) {
        console.log(err);
        setTimeout(start, 5000);
    }
};

connection.onclose(async () => {
    await start();
});

// Start the connection.
start();

Подключения между источниками

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

Важно!

Клиентский код должен использовать абсолютный URL-адрес вместо относительного URL-адреса. Измените .withUrl("/chathub") на .withUrl("https://myappurl/chathub").

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

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using SignalRChat.Hubs;

namespace SignalRChat
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddRazorPages();
            services.AddSignalR();

            services.AddCors(options =>
            {
                options.AddDefaultPolicy(builder =>
                {
                    builder.WithOrigins("https://example.com")
                        .AllowCredentials();
                });
            });
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
            }

            app.UseStaticFiles();
            app.UseRouting();

            app.UseCors();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapRazorPages();
                endpoints.MapHub<ChatHub>("/chathub");
            });
        }
    }
}

Методы концентратора вызовов от клиента

Клиенты JavaScript вызывают общедоступные методы в центрах с помощью метода вызова концентратора Подключение ion. Метод invoke принимает:

  • Имя метода концентратора.
  • Все аргументы, определенные в методе концентратора.

В следующем примере имя метода в концентраторе равно SendMessage. Второй и третий аргументы, передаваемые для invoke сопоставления с аргументами и message аргументами концентратораuser:

try {
    await connection.invoke("SendMessage", user, message);
} catch (err) {
    console.error(err);
}

Примечание.

Вызов методов концентратора из клиента поддерживается только при использовании службы Azure SignalR в режиме по умолчанию . Дополнительные сведения см. в разделе Часто задаваемые вопросы (репозиторий GitHub azure-signalr).

Метод invoke возвращает JavaScript Promise. Разрешение Promise выполняется с возвращаемым значением (при наличии), когда метод на сервере возвращается. Если метод на сервере выдает ошибку, Promise он отклоняется с сообщением об ошибке. Используйте async и методы и thenawaitPromisecatch методы для обработки этих случаев.

Клиенты JavaScript также могут вызывать общедоступные методы в центрах с помощью метода отправкиHubConnection. invoke В отличие от метода, send метод не ожидает ответа от сервера. Метод send возвращает JavaScript Promise. Разрешается Promise при отправке сообщения на сервер. Если сообщение отправляет сообщение об ошибке, Promise сообщение об ошибке отклоняется. Используйте async и методы и thenawaitPromisecatch методы для обработки этих случаев.

Примечание.

Использование send не ожидает, пока сервер не получит сообщение. Следовательно, невозможно вернуть данные или ошибки с сервера.

Вызов клиентских методов из концентратора

Чтобы получать сообщения из концентратора, определите метод с помощью метода .HubConnection

  • Имя клиентского метода JavaScript.
  • Аргументы концентратора передаются методу.

В следующем примере используется ReceiveMessageимя метода. Имена аргументов:usermessage

connection.on("ReceiveMessage", (user, message) => {
    const li = document.createElement("li");
    li.textContent = `${user}: ${message}`;
    document.getElementById("messageList").appendChild(li);
});

Приведенный выше код выполняется при connection.on вызове кода на стороне SendAsync сервера с помощью метода:

public async Task SendMessage(string user, string message)
{
    await Clients.All.SendAsync("ReceiveMessage", user, message);
}

SignalR определяет, какой метод клиента следует вызывать путем сопоставления имени метода и аргументов, определенных в SendAsync и connection.on.

Примечание.

В качестве оптимальной практики вызовите метод start в послеonHubConnection. Это гарантирует регистрацию обработчиков перед получением сообщений.

Обработка ошибок и ведение журнала

Используйте и с методом или catchawaitPromiseметодом для обработки ошибок на стороне клиента.asynccatchtry Используется console.error для вывода ошибок в консоли браузера:

try {
    await connection.invoke("SendMessage", user, message);
} catch (err) {
    console.error(err);
}

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

  • signalR.LogLevel.Error: сообщения об ошибках. Error Регистрирует только сообщения.
  • signalR.LogLevel.Warning: предупреждают сообщения о потенциальных ошибках. Журналы Warningи Error сообщения.
  • signalR.LogLevel.Information: сообщения о состоянии без ошибок. Журналы Informationи WarningError сообщения.
  • signalR.LogLevel.Trace: трассировка сообщений. Регистрирует все данные, включая данные, транспортируемые между концентратором и клиентом.

Используйте метод configureLogging в Hub Подключение ionBuilder, чтобы настроить уровень журнала. Сообщения записываются в консоль браузера:

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .configureLogging(signalR.LogLevel.Information)
    .build();

Повторное подключение клиентов

Автоматическое повторное подключение

Клиент SignalR JavaScript можно настроить для автоматического withAutomaticReconnect повторного подключения с помощью метода в Hub Подключение ionBuilder. По умолчанию он не будет автоматически повторно подключаться.

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withAutomaticReconnect()
    .build();

Без параметров withAutomaticReconnect() клиент настраивает ожидание 0, 2, 10 и 30 секунд соответственно, прежде чем пытаться выполнить каждую попытку повторного подключения, остановившись после четырех неудачных попыток.

Перед началом любых попыток HubConnection повторного подключения он перейдет в HubConnectionState.Reconnecting состояние и запускает обратные onreconnecting вызовы вместо перехода к Disconnected состоянию и активации обратных onclose вызовов, таких как HubConnection без автоматического повторного подключения. Это дает возможность предупредить пользователей о том, что подключение потеряно и отключает элементы пользовательского интерфейса.

connection.onreconnecting(error => {
    console.assert(connection.state === signalR.HubConnectionState.Reconnecting);

    document.getElementById("messageInput").disabled = true;

    const li = document.createElement("li");
    li.textContent = `Connection lost due to error "${error}". Reconnecting.`;
    document.getElementById("messageList").appendChild(li);
});

Если клиент успешно повторно подключается в течение первых четырех попыток, HubConnection он вернется к Connected состоянию и вызовет обратный onreconnected вызов. Это дает возможность сообщить пользователям, что подключение было восстановлено.

Так как подключение выглядит совершенно новым на сервере, новое connectionId будет предоставлено обратному вызову onreconnected .

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

Параметр onreconnected обратного вызова connectionId не определен, если HubConnection настроено пропустить согласование.

connection.onreconnected(connectionId => {
    console.assert(connection.state === signalR.HubConnectionState.Connected);

    document.getElementById("messageInput").disabled = false;

    const li = document.createElement("li");
    li.textContent = `Connection reestablished. Connected with connectionId "${connectionId}".`;
    document.getElementById("messageList").appendChild(li);
});

withAutomaticReconnect() не настраивает повторные HubConnection начальные сбои запуска, поэтому при выполнении сбоев запуска необходимо обрабатывать вручную:

async function start() {
    try {
        await connection.start();
        console.assert(connection.state === signalR.HubConnectionState.Connected);
        console.log("SignalR Connected.");
    } catch (err) {
        console.assert(connection.state === signalR.HubConnectionState.Disconnected);
        console.log(err);
        setTimeout(() => start(), 5000);
    }
};

Если клиент не успешно пересоединяется в течение первых четырех попыток, HubConnection он перейдет в Disconnected состояние и запустит обратные вызовы onclose . Это дает возможность информировать пользователей о том, что подключение было окончательно потеряно и рекомендуется обновить страницу:

connection.onclose(error => {
    console.assert(connection.state === signalR.HubConnectionState.Disconnected);

    document.getElementById("messageInput").disabled = true;

    const li = document.createElement("li");
    li.textContent = `Connection closed due to error "${error}". Try refreshing this page to restart the connection.`;
    document.getElementById("messageList").appendChild(li);
});

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

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withAutomaticReconnect([0, 0, 10000])
    .build();

    // .withAutomaticReconnect([0, 2000, 10000, 30000]) yields the default behavior

В предыдущем примере настраивается HubConnection запуск повторного подключения сразу после потери подключения. Это также верно для конфигурации по умолчанию.

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

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

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

Если требуется еще больше контроля над временем и количеством попыток автоматического повторного подключения, withAutomaticReconnect принимает объект, реализующий IRetryPolicy интерфейс, имеющий один метод с именем nextRetryDelayInMilliseconds.

nextRetryDelayInMilliseconds принимает один аргумент с типом RetryContext. Имеет RetryContext три свойства: previousRetryCountelapsedMilliseconds и retryReason которые являются number, number а также соответственноError. Перед первой попыткой previousRetryCount повторного подключения оба и elapsedMilliseconds будет нулевым, и retryReason будет ошибка, которая привела к потере подключения. После каждой неудачной попытки previousRetryCount повторных попыток будет увеличиваться по одному, elapsedMilliseconds будет обновлено, чтобы отразить время повторного подключения до сих пор в миллисекундах, и retryReason будет ошибка, которая вызвала последнюю попытку повторного подключения.

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

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withAutomaticReconnect({
        nextRetryDelayInMilliseconds: retryContext => {
            if (retryContext.elapsedMilliseconds < 60000) {
                // If we've been reconnecting for less than 60 seconds so far,
                // wait between 0 and 10 seconds before the next reconnect attempt.
                return Math.random() * 10000;
            } else {
                // If we've been reconnecting for more than 60 seconds so far, stop reconnecting.
                return null;
            }
        }
    })
    .build();

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

Повторное подключение вручную

Следующий код демонстрирует типичный подход повторного подключения вручную:

  1. Функция (в данном случае start функция) создается для запуска подключения.
  2. Вызовите функцию start в обработчике событий подключения onclose .
async function start() {
    try {
        await connection.start();
        console.log("SignalR Connected.");
    } catch (err) {
        console.log(err);
        setTimeout(start, 5000);
    }
};

connection.onclose(async () => {
    await start();
});

В производственных реализациях обычно используется экспоненциальное обратное или повторное выполнение указанного количества раз.

Вкладка "Спящий браузер"

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

  • Воспроизведение звука
  • Хранение веб-блокировки
  • IndexedDB Хранение блокировки
  • Подключение к USB-устройству
  • Запись видео или звука
  • Зеркало
  • Запись окна или отображения

Примечание.

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

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

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

var lockResolver;
if (navigator && navigator.locks && navigator.locks.request) {
    const promise = new Promise((res) => {
        lockResolver = res;
    });

    navigator.locks.request('unique_lock_name', { mode: "shared" }, () => {
        return promise;
    });
}

В приведенном выше примере кода:

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

Дополнительные ресурсы