Поддержка WebSockets в ASP.NET Core

Note

Это не последняя версия этой статьи. В текущем выпуске см. версию этой статьи для .NET 10.

Warning

Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в политике поддержки .NET и .NET Core. Сведения о текущем выпуске см. в версии этой статьи для .NET 10.

Эта статья описывает начало работы с WebSocket в ASP.NET Core. WebSocket (RFC 6455) — это протокол, предоставляющий сохраняемые двусторонние каналы связи по TCP-подключениям. Он используется в приложениях, где нужна быстрая связь в режиме реального времени, например в чатах, панелях мониторинга и играх.

Просмотрите или скачайте пример кода (описание процедуры скачивания, описание процедуры выполнения).

Поддержка HTTP/2 WebSockets

При использовании соединений WebSocket по HTTP/2 доступны новые возможности, в том числе следующие:

  • Сжатие заголовков.
  • мультиплексирование, которое сокращает время и ресурсы, необходимые для выполнения нескольких запросов к серверу.

Эти функции доступны в Kestrel на всех платформах с поддержкой HTTP/2. Согласование версии выполняется в браузерах и Kestrel автоматически, поэтому новые интерфейсы API не требуются.

В .NET 7 появилась поддержка WebSockets через HTTP/2 для Kestrel, клиента JavaScript SignalR и SignalR с Blazor WebAssembly.

Note

Соединения WebSocket по HTTP/2 используют запросы CONNECT, а не GET, поэтому ваши собственные маршруты и контроллеры могут потребовать обновления. Дополнительные сведения см. в разделе Добавление поддержки WebSocket по HTTP/2 для существующих контроллеров в этой статье.

Chrome, Edge и Firefox (версия 128 и более поздние версии) включают HTTP/2 WebSockets по умолчанию. Вы можете проверить или изменить этот параметр в Firefox, открыв about:config и найдя network.http.http2.websockets предпочтения.

Протокол WebSocket изначально был разработан для HTTP/1.1, но с тех пор был адаптирован для работы по протоколу HTTP/2. (RFC 8441)

SignalR

ASP.NET Core SignalR — это библиотека, которая упрощает добавление веб-функций в режиме реального времени в приложения. Она использует WebSocket, когда это возможно.

Для большинства приложений рекомендуется использовать SignalR вместо прямых соединений WebSocket. SignalR:

  • обеспечивает резервный транспорт для сред, в которых соединения WebSocket недоступны;
  • Предоставляет базовую модель приложения для удалённого вызова процедур.
  • В большинстве сценариев не имеет существенного снижения производительности по сравнению с непосредственным использованием WebSocket.

WebSocket по HTTP/2 поддерживается для следующих компонентов:

  • клиент JavaScript ASP.NET Core SignalR
  • ASP.NET Core SignalR с Blazor WebAssembly

Для некоторых приложений gRPC в .NET предоставляет альтернативу WebSockets.

Prerequisites

  • Любая ОС, поддерживающая ASP.NET Core:
    • Windows 7/Windows Server 2008 или более поздних версий
    • Linux
    • macOS
  • Если приложение работает в Windows с помощью IIS:
  • Если приложение работает в HTTP.sys:
    • Windows 8/Windows Server 2012 или более поздних версий
  • Список поддерживаемых обозревателей см. на странице Можно ли использовать.

Настройка ПО промежуточного слоя

Добавьте компонент промежуточного слоя WebSockets в Program.cs:

app.UseWebSockets();

Можно настроить следующие параметры:

  • KeepAliveInterval — как часто отправлять клиенту кадры "ping", чтобы прокси-серверы не закрывали соединение. Значение по умолчанию — две минуты.
  • KeepAliveTimeout — Сколько времени ждать кадра "pong" от клиента после отправки кадра "ping". Если превышено время ожидания, подключение WebSocket прервано и WebSocket.ReceiveAsync вызывает исключение. Значение по умолчанию — Timeout.InfiniteTimeSpan (отключено). Дополнительные сведения см. в разделе "Обработка отключений клиента " в этой статье.
  • AllowedOrigins — список допустимых значений заголовка Origin для запросов WebSocket. По умолчанию разрешены все источники. Дополнительные сведения см. в разделе Ограничения для источников WebSocket в этой статье.
var webSocketOptions = new WebSocketOptions
{
    KeepAliveInterval = TimeSpan.FromMinutes(2)
};

app.UseWebSockets(webSocketOptions);

Принятие запросов WebSocket

На более позднем этапе жизненного цикла запроса (например, далее в методе Program.cs или методе действия) проверьте, относится ли запрос к WebSocket, и примите его.

Следующий пример приведён далее в Program.cs:

app.Use(async (context, next) =>
{
    if (context.Request.Path == "/ws")
    {
        if (context.WebSockets.IsWebSocketRequest)
        {
            using var webSocket = await context.WebSockets.AcceptWebSocketAsync();
            await Echo(webSocket);
        }
        else
        {
            context.Response.StatusCode = StatusCodes.Status400BadRequest;
        }
    }
    else
    {
        await next(context);
    }

});

Запрос WebSocket может поступить по любому URL-адресу, но этот пример кода принимает только запросы для /ws.

Аналогичный подход можно использовать в методе контроллера:

public class WebSocketController : ControllerBase
{
    [Route("/ws")]
    public async Task Get()
    {
        if (HttpContext.WebSockets.IsWebSocketRequest)
        {
            using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
            await Echo(webSocket);
        }
        else
        {
            HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
        }
    }

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

System.Net.WebSockets.WebSocketException (0x80004005): The remote party closed the WebSocket connection without completing the close handshake. ---> System.ObjectDisposedException: Cannot write to the response body, the response has completed.
Object name: 'HttpResponseStream'.

Если вы используете фоновую службу для записи данных в WebSocket, убедитесь, что конвейер ПО промежуточного слоя продолжает работу. Это можно сделать с помощью TaskCompletionSource<TResult>. Передайте TaskCompletionSource фоновой службе и сделайте так, чтобы по завершении работы с WebSocket она вызвала TrySetResult. Затем await свойство Task во время запроса, как показано в следующем примере:

app.Run(async (context) =>
{
    using var webSocket = await context.WebSockets.AcceptWebSocketAsync();
    var socketFinishedTcs = new TaskCompletionSource<object>();

    BackgroundSocketProcessor.AddSocket(webSocket, socketFinishedTcs);

    await socketFinishedTcs.Task;
});

Исключение о закрытии WebSocket также может возникнуть, если слишком рано выйти из метода действия. При приёме сокета в методе обработчика дождитесь, пока завершится код, использующий сокет, прежде чем завершать метод обработчика.

Никогда не используйте Task.Wait, Task.Result или аналогичные блокирующие вызовы для ожидания выполнения сокета, так как это может привести к серьезным проблемам потокового выполнения. Всегда используйте await.

Добавление поддержки WebSocket по HTTP/2 для существующих контроллеров

В .NET 7 появилась поддержка WebSockets через HTTP/2 для Kestrel, клиента JavaScript SignalR и SignalR с Blazor WebAssembly. Соединения WebSocket по HTTP/2 используют запросы CONNECT, а не GET. Если вы ранее использовали [HttpGet("/path")] в методе действия контроллера для запросов WebSocket, обновите код и вместо него используйте [Route("/path")].

public class WebSocketController : ControllerBase
{
    [Route("/ws")]
    public async Task Get()
    {
        if (HttpContext.WebSockets.IsWebSocketRequest)
        {
            using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
            await Echo(webSocket);
        }
        else
        {
            HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
        }
    }

Compression

Warning

Включение сжатия через зашифрованные подключения может сделать приложение уязвимым к атакам CRIME/BREACH. При отправке конфиденциальной информации не включайте сжатие или используйте WebSocketMessageFlags.DisableCompression в вызове WebSocket.SendAsync. Это относится к обеим сторонам подключения по WebSocket. Обратите внимание, что для API WebSockets в браузере нет конфигурации, позволяющей отключить сжатие для операции отправки.

Если требуется реализовать сжатие сообщений через WebSockets, в коде принятия должно быть указано, что сжатие допустимо, как показано ниже:

using (var webSocket = await context.WebSockets.AcceptWebSocketAsync(
    new WebSocketAcceptContext { DangerousEnableCompression = true }))
{

}

WebSocketAcceptContext.ServerMaxWindowBits и WebSocketAcceptContext.DisableServerContextTakeover — это расширенные параметры, которые отвечают за процесс сжатия.

Сжатие согласовывается между клиентом и сервером при установке первого подключения. Дополнительные сведения о согласовании см. в статье Расширения сжатия для WebSocket RFC.

Note

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

Отправка и получение сообщений

Метод AcceptWebSocketAsync обновляет TCP-соединение до соединения WebSocket и предоставляет объект WebSocket. Используйте объект WebSocket для отправки и получения сообщений.

Приведенный выше код, который принимает запрос WebSocket, передает объект WebSocket в метод Echo. Код принимает сообщение и сразу отправляет такое же сообщение обратно. Сообщения отправляются и принимаются в цикле, пока клиент не закроет подключение:

private static async Task Echo(WebSocket webSocket)
{
    var buffer = new byte[1024 * 4];
    var receiveResult = await webSocket.ReceiveAsync(
        new ArraySegment<byte>(buffer), CancellationToken.None);

    while (!receiveResult.CloseStatus.HasValue)
    {
        await webSocket.SendAsync(
            new ArraySegment<byte>(buffer, 0, receiveResult.Count),
            receiveResult.MessageType,
            receiveResult.EndOfMessage,
            CancellationToken.None);

        receiveResult = await webSocket.ReceiveAsync(
            new ArraySegment<byte>(buffer), CancellationToken.None);
    }

    await webSocket.CloseAsync(
        receiveResult.CloseStatus.Value,
        receiveResult.CloseStatusDescription,
        CancellationToken.None);
}

Если принять WebSocket-подключение прежде, чем начать цикл, конвейер промежуточного ПО завершается. После закрытия сокета конвейер развертывается. То есть запрос перестает перемещаться по конвейеру после принятия WebSocket. После завершения цикла и закрытия сокета запрос возвращается в конвейер.

Обработка отключений клиента

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

Если клиент не всегда отправляет сообщения и вы не хотите, чтобы из-за простоя соединения происходил тайм-аут, настройте его так, чтобы он по таймеру отправлял ping-сообщение каждые X секунд. Если сообщение не было получено на сервере в течение 2*X секунд после предыдущего, завершите подключение и сообщите о том, что клиент отключен. Подождите вдвое дольше ожидаемого интервала времени, чтобы предусмотреть дополнительное время из-за сетевых задержек, которые могут задержать ping-сообщение.

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

Промежуточное ПО WebSockets можно настроить, задав тайм-аут проверки активности. Тайм-аут keep-alive прерывает подключение WebSocket и вызывает исключение в WebSocket.ReceiveAsync, если выполняются оба следующих условия:

  • Сервер отправляет ping-кадр с помощью протокола WebSocket.
  • Клиент не отправляет в ответ кадр pong в течение указанного тайм-аута.

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

Параметр тайм-аута keep-alive полезен для выявления соединений, которые могут быть медленными или некорректно разорванными. Значение по умолчанию Timeout.InfiniteTimeSpan— отключает время ожидания.

Тайм-аут поддержания соединения можно глобально настроить для промежуточного ПО WebSocket:

app.UseWebSockets(new WebSocketOptions { KeepAliveTimeout = TimeSpan.FromSeconds(15) });

Или настроено для каждого принятого WebSocket:

using var webSocket = await context.WebSockets.AcceptWebSocketAsync(
    new WebSocketAcceptContext { KeepAliveTimeout = TimeSpan.FromSeconds(15) });

Ограничения для источников WebSocket

Варианты защиты, предоставляемые CORS, не применяются к WebSocket. Браузеры не:

  • Выполните предварительные CORS-запросы.
  • Соблюдайте ограничения, указанные в заголовках Access-Control, при отправке запросов WebSocket.

Однако браузеры отправляют заголовок Origin при выпуске запросов WebSocket. Приложения должны быть настроены на проверку этих заголовков, чтобы разрешались только подключения WebSocket из ожидаемых источников.

Если вы размещаете сервер по адресу "https://server.com", а клиент — по адресу "https://client.com", добавьте "https://client.com" в список AllowedOrigins, подлежащих проверке WebSocket.

var webSocketOptions = new WebSocketOptions
{
    KeepAliveInterval = TimeSpan.FromMinutes(2)
};

webSocketOptions.AllowedOrigins.Add("https://client.com");
webSocketOptions.AllowedOrigins.Add("https://www.client.com");

app.UseWebSockets(webSocketOptions);

Note

Заголовок Origin контролируется клиентом и, как и заголовок Referer, может быть подделан. Не используйте эти заголовки в качестве механизма проверки подлинности.

Поддержка служб IIS/IIS Express

Windows Server 2012 или более поздней версии и Windows 8 или более поздней версии с IIS и IIS Express 8 или более поздней версии поддерживают протокол WebSocket, но не протокол WebSocket по HTTP/2.

Note

Соединения WebSockets всегда включены при использовании IIS Express.

Включение WebSockets в IIS

Чтобы включить поддержку протокола WebSocket в Windows Server 2012 или более поздней версии:

Note

Эти действия не требуется выполнять при использовании IIS Express

  1. В меню Управление запустите мастер Добавить роли и компоненты или в окне Диспетчер серверов щелкните соответствующую ссылку.
  2. Выберите Установка на основе ролей или компонентов. Нажмите кнопку Далее.
  3. Выберите подходящий сервер (по умолчанию выбирается локальный сервер). Нажмите кнопку Далее.
  4. Разверните Веб-сервер (IIS) в дереве Роли, разверните Веб-сервер, а затем Разработка приложений.
  5. Выберите протокол WebSocket. Нажмите кнопку Далее.
  6. Если дополнительные функции не требуются, нажмите Далее.
  7. Нажмите Установить.
  8. По завершении установки выберите Закрыть, чтобы выйти из мастера.

Чтобы включить поддержку протокола WebSocket в Windows 8 или более поздней версии:

Note

Эти действия не требуется выполнять при использовании IIS Express

  1. Последовательно выберите Панель управления>Программы>Программы и компоненты>Включение или отключение компонентов Windows (в левой части экрана).
  2. Откройте следующие узлы: Internet Information Services>Службы World Wide Web>Средства разработки приложений.
  3. Выберите компонент Протокол WebSocket. Нажмите ОК.

Отключите WebSocket при использовании socket.io на Node.js

При использовании поддержки WebSocket в socket.io в Node.jsотключите модуль IIS WebSocket по умолчанию с помощью элемента webSocket в web.config или applicationHost.config. Если этот шаг не выполнен, модуль IIS WebSocket пытается обработать связь WebSocket, а не Node.js и приложение.

<system.webServer>
  <webSocket enabled="false" />
</system.webServer>

Пример приложения

Пример приложения в этой статье — это эхо-приложение. Оно имеет веб-страницу, которая устанавливает соединения WebSocket, а сервер перенаправляет все полученные сообщения обратно клиенту. Пример приложения поддерживает WebSocket по HTTP/2 при использовании целевой платформы .NET 7 или более поздней версии.

Запустите приложение:

  • Чтобы запустить приложение в Visual Studio, откройте пример проекта в этой среде и нажмите клавиши CTRL+F5 для запуска без отладчика.
  • Чтобы запустить приложение в командной оболочке, выполните команду dotnet run и перейдите в браузере по адресу http://localhost:<port>.

На этой веб-странице отображается состояние подключения.

Начальное состояние веб-страницы до подключения WebSocket

Выберите Connect (Подключить), чтобы отправить запрос WebSocket на показанный URL-адрес. Введите тестовое сообщение и выберите Send (Отправить). После этого выберите Close Socket (Закрыть сокет). В разделе Журнал связи фиксируются все действия открытия, отправки и закрытия по мере их выполнения.

Конечное состояние веб-страницы после подключения WebSocket и отправки тестовых сообщений с их последующим приемом

Эта статья описывает начало работы с WebSocket в ASP.NET Core. WebSocket (RFC 6455) — это протокол, предоставляющий сохраняемые двусторонние каналы связи по TCP-подключениям. Он используется в приложениях, где нужна быстрая связь в режиме реального времени, например в чатах, панелях мониторинга и играх.

Просмотрите или скачайте пример кода (описание процедуры скачивания, описание процедуры выполнения).

Поддержка HTTP/2 WebSockets

При использовании соединений WebSocket по HTTP/2 доступны новые возможности, в том числе следующие:

  • Сжатие заголовков.
  • мультиплексирование, которое сокращает время и ресурсы, необходимые для выполнения нескольких запросов к серверу.

Эти функции доступны в Kestrel на всех платформах с поддержкой HTTP/2. Согласование версии выполняется в браузерах и Kestrel автоматически, поэтому новые интерфейсы API не требуются.

В .NET 7 появилась поддержка WebSockets через HTTP/2 для Kestrel, клиента JavaScript SignalR и SignalR с Blazor WebAssembly.

Note

Соединения WebSocket по HTTP/2 используют запросы CONNECT, а не GET, поэтому ваши собственные маршруты и контроллеры могут потребовать обновления. Дополнительные сведения см. в разделе Добавление поддержки WebSocket по HTTP/2 для существующих контроллеров в этой статье.

Chrome, Edge и Firefox (версия 128 и более поздние версии) включают HTTP/2 WebSockets по умолчанию. Вы можете проверить или изменить этот параметр в Firefox, открыв about:config и найдя network.http.http2.websockets предпочтения.

Протокол WebSocket изначально был разработан для HTTP/1.1, но с тех пор был адаптирован для работы по протоколу HTTP/2. (RFC 8441)

SignalR

ASP.NET Core SignalR — это библиотека, которая упрощает добавление веб-функций в режиме реального времени в приложения. Она использует WebSocket, когда это возможно.

Для большинства приложений рекомендуется использовать SignalR вместо прямых соединений WebSocket. SignalR:

  • обеспечивает резервный транспорт для сред, в которых соединения WebSocket недоступны;
  • Предоставляет базовую модель приложения для удалённого вызова процедур.
  • В большинстве сценариев не имеет существенного снижения производительности по сравнению с непосредственным использованием WebSocket.

WebSocket по HTTP/2 поддерживается для следующих компонентов:

  • клиент JavaScript ASP.NET Core SignalR
  • ASP.NET Core SignalR с Blazor WebAssembly

Для некоторых приложений gRPC в .NET предоставляет альтернативу WebSockets.

Prerequisites

  • Любая ОС, поддерживающая ASP.NET Core:
    • Windows 7/Windows Server 2008 или более поздних версий
    • Linux
    • macOS
  • Если приложение работает в Windows с помощью IIS:
  • Если приложение работает в HTTP.sys:
    • Windows 8/Windows Server 2012 или более поздних версий
  • Список поддерживаемых обозревателей см. на странице Можно ли использовать.

Настройка ПО промежуточного слоя

Добавьте компонент промежуточного слоя WebSockets в Program.cs:

app.UseWebSockets();

Можно настроить следующие параметры:

  • KeepAliveInterval — как часто отправлять клиенту кадры "ping", чтобы прокси-серверы не закрывали соединение. Значение по умолчанию — две минуты.
  • AllowedOrigins — список допустимых значений заголовка Origin для запросов WebSocket. По умолчанию разрешены все источники. Дополнительные сведения см. в разделе Ограничения для источников WebSocket в этой статье.
var webSocketOptions = new WebSocketOptions
{
    KeepAliveInterval = TimeSpan.FromMinutes(2)
};

app.UseWebSockets(webSocketOptions);

Принятие запросов WebSocket

На более позднем этапе жизненного цикла запроса (например, далее в методе Program.cs или методе действия) проверьте, относится ли запрос к WebSocket, и примите его.

Следующий пример приведён далее в Program.cs:

app.Use(async (context, next) =>
{
    if (context.Request.Path == "/ws")
    {
        if (context.WebSockets.IsWebSocketRequest)
        {
            using var webSocket = await context.WebSockets.AcceptWebSocketAsync();
            await Echo(webSocket);
        }
        else
        {
            context.Response.StatusCode = StatusCodes.Status400BadRequest;
        }
    }
    else
    {
        await next(context);
    }

});

Запрос WebSocket может поступить по любому URL-адресу, но этот пример кода принимает только запросы для /ws.

Аналогичный подход можно использовать в методе контроллера:

public class WebSocketController : ControllerBase
{
    [Route("/ws")]
    public async Task Get()
    {
        if (HttpContext.WebSockets.IsWebSocketRequest)
        {
            using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
            await Echo(webSocket);
        }
        else
        {
            HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
        }
    }

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

System.Net.WebSockets.WebSocketException (0x80004005): The remote party closed the WebSocket connection without completing the close handshake. ---> System.ObjectDisposedException: Cannot write to the response body, the response has completed.
Object name: 'HttpResponseStream'.

Если вы используете фоновую службу для записи данных в WebSocket, убедитесь, что конвейер ПО промежуточного слоя продолжает работу. Это можно сделать с помощью TaskCompletionSource<TResult>. Передайте TaskCompletionSource фоновой службе и сделайте так, чтобы по завершении работы с WebSocket она вызвала TrySetResult. Затем await свойство Task во время запроса, как показано в следующем примере:

app.Run(async (context) =>
{
    using var webSocket = await context.WebSockets.AcceptWebSocketAsync();
    var socketFinishedTcs = new TaskCompletionSource<object>();

    BackgroundSocketProcessor.AddSocket(webSocket, socketFinishedTcs);

    await socketFinishedTcs.Task;
});

Исключение о закрытии WebSocket также может возникнуть, если слишком рано выйти из метода действия. При приёме сокета в методе обработчика дождитесь, пока завершится код, использующий сокет, прежде чем завершать метод обработчика.

Никогда не используйте Task.Wait, Task.Result или аналогичные блокирующие вызовы для ожидания выполнения сокета, так как это может привести к серьезным проблемам потокового выполнения. Всегда используйте await.

Добавление поддержки WebSocket по HTTP/2 для существующих контроллеров

В .NET 7 появилась поддержка WebSockets через HTTP/2 для Kestrel, клиента JavaScript SignalR и SignalR с Blazor WebAssembly. Соединения WebSocket по HTTP/2 используют запросы CONNECT, а не GET. Если вы ранее использовали [HttpGet("/path")] в методе действия контроллера для запросов WebSocket, обновите код и вместо него используйте [Route("/path")].

public class WebSocketController : ControllerBase
{
    [Route("/ws")]
    public async Task Get()
    {
        if (HttpContext.WebSockets.IsWebSocketRequest)
        {
            using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
            await Echo(webSocket);
        }
        else
        {
            HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
        }
    }

Compression

Warning

Включение сжатия через зашифрованные подключения может сделать приложение уязвимым к атакам CRIME/BREACH. При отправке конфиденциальной информации не включайте сжатие или используйте WebSocketMessageFlags.DisableCompression в вызове WebSocket.SendAsync. Это относится к обеим сторонам подключения по WebSocket. Обратите внимание, что для API WebSockets в браузере нет конфигурации, позволяющей отключить сжатие для операции отправки.

Если требуется реализовать сжатие сообщений через WebSockets, в коде принятия должно быть указано, что сжатие допустимо, как показано ниже:

using (var webSocket = await context.WebSockets.AcceptWebSocketAsync(
    new WebSocketAcceptContext { DangerousEnableCompression = true }))
{

}

WebSocketAcceptContext.ServerMaxWindowBits и WebSocketAcceptContext.DisableServerContextTakeover — это расширенные параметры, которые отвечают за процесс сжатия.

Сжатие согласовывается между клиентом и сервером при установке первого подключения. Дополнительные сведения о согласовании см. в статье Расширения сжатия для WebSocket RFC.

Note

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

Отправка и получение сообщений

Метод AcceptWebSocketAsync обновляет TCP-соединение до соединения WebSocket и предоставляет объект WebSocket. Используйте объект WebSocket для отправки и получения сообщений.

Приведенный выше код, который принимает запрос WebSocket, передает объект WebSocket в метод Echo. Код принимает сообщение и сразу отправляет такое же сообщение обратно. Сообщения отправляются и принимаются в цикле, пока клиент не закроет подключение:

private static async Task Echo(WebSocket webSocket)
{
    var buffer = new byte[1024 * 4];
    var receiveResult = await webSocket.ReceiveAsync(
        new ArraySegment<byte>(buffer), CancellationToken.None);

    while (!receiveResult.CloseStatus.HasValue)
    {
        await webSocket.SendAsync(
            new ArraySegment<byte>(buffer, 0, receiveResult.Count),
            receiveResult.MessageType,
            receiveResult.EndOfMessage,
            CancellationToken.None);

        receiveResult = await webSocket.ReceiveAsync(
            new ArraySegment<byte>(buffer), CancellationToken.None);
    }

    await webSocket.CloseAsync(
        receiveResult.CloseStatus.Value,
        receiveResult.CloseStatusDescription,
        CancellationToken.None);
}

Если принять WebSocket-подключение прежде, чем начать цикл, конвейер промежуточного ПО завершается. После закрытия сокета конвейер развертывается. То есть запрос перестает перемещаться по конвейеру после принятия WebSocket. После завершения цикла и закрытия сокета запрос возвращается в конвейер.

Обработка отключений клиента

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

Если клиент не всегда отправляет сообщения и вы не хотите, чтобы из-за простоя соединения происходил тайм-аут, настройте его так, чтобы он по таймеру отправлял ping-сообщение каждые X секунд. Если сообщение не было получено на сервере в течение 2*X секунд после предыдущего, завершите подключение и сообщите о том, что клиент отключен. Подождите вдвое дольше ожидаемого интервала времени, чтобы предусмотреть дополнительное время из-за сетевых задержек, которые могут задержать ping-сообщение.

Ограничения для источников WebSocket

Варианты защиты, предоставляемые CORS, не применяются к WebSocket. Браузеры не:

  • Выполните предварительные CORS-запросы.
  • Соблюдайте ограничения, указанные в заголовках Access-Control, при отправке запросов WebSocket.

Однако браузеры отправляют заголовок Origin при выпуске запросов WebSocket. Приложения должны быть настроены на проверку этих заголовков, чтобы разрешались только подключения WebSocket из ожидаемых источников.

Если вы размещаете сервер по адресу "https://server.com", а клиент — по адресу "https://client.com", добавьте "https://client.com" в список AllowedOrigins, подлежащих проверке WebSocket.

var webSocketOptions = new WebSocketOptions
{
    KeepAliveInterval = TimeSpan.FromMinutes(2)
};

webSocketOptions.AllowedOrigins.Add("https://client.com");
webSocketOptions.AllowedOrigins.Add("https://www.client.com");

app.UseWebSockets(webSocketOptions);

Note

Заголовок Origin контролируется клиентом и, как и заголовок Referer, может быть подделан. Не используйте эти заголовки в качестве механизма проверки подлинности.

Поддержка служб IIS/IIS Express

Windows Server 2012 или более поздней версии и Windows 8 или более поздней версии с IIS и IIS Express 8 или более поздней версии поддерживают протокол WebSocket, но не протокол WebSocket по HTTP/2.

Note

Соединения WebSockets всегда включены при использовании IIS Express.

Включение WebSockets в IIS

Чтобы включить поддержку протокола WebSocket в Windows Server 2012 или более поздней версии:

Note

Эти действия не требуется выполнять при использовании IIS Express

  1. В меню Управление запустите мастер Добавить роли и компоненты или в окне Диспетчер серверов щелкните соответствующую ссылку.
  2. Выберите Установка на основе ролей или компонентов. Нажмите кнопку Далее.
  3. Выберите подходящий сервер (по умолчанию выбирается локальный сервер). Нажмите кнопку Далее.
  4. Разверните Веб-сервер (IIS) в дереве Роли, разверните Веб-сервер, а затем Разработка приложений.
  5. Выберите протокол WebSocket. Нажмите кнопку Далее.
  6. Если дополнительные функции не требуются, нажмите Далее.
  7. Нажмите Установить.
  8. По завершении установки выберите Закрыть, чтобы выйти из мастера.

Чтобы включить поддержку протокола WebSocket в Windows 8 или более поздней версии:

Note

Эти действия не требуется выполнять при использовании IIS Express

  1. Последовательно выберите Панель управления>Программы>Программы и компоненты>Включение или отключение компонентов Windows (в левой части экрана).
  2. Откройте следующие узлы: Internet Information Services>Службы World Wide Web>Средства разработки приложений.
  3. Выберите компонент Протокол WebSocket. Нажмите ОК.

Отключите WebSocket при использовании socket.io на Node.js

При использовании поддержки WebSocket в socket.io в Node.jsотключите модуль IIS WebSocket по умолчанию с помощью элемента webSocket в web.config или applicationHost.config. Если этот шаг не выполнен, модуль IIS WebSocket пытается обработать связь WebSocket, а не Node.js и приложение.

<system.webServer>
  <webSocket enabled="false" />
</system.webServer>

Пример приложения

Пример приложения в этой статье — это эхо-приложение. Оно имеет веб-страницу, которая устанавливает соединения WebSocket, а сервер перенаправляет все полученные сообщения обратно клиенту. Пример приложения поддерживает WebSocket по HTTP/2 при использовании целевой платформы .NET 7 или более поздней версии.

Запустите приложение:

  • Чтобы запустить приложение в Visual Studio, откройте пример проекта в этой среде и нажмите клавиши CTRL+F5 для запуска без отладчика.
  • Чтобы запустить приложение в командной оболочке, выполните команду dotnet run и перейдите в браузере по адресу http://localhost:<port>.

На этой веб-странице отображается состояние подключения.

Начальное состояние веб-страницы до подключения WebSocket

Выберите Connect (Подключить), чтобы отправить запрос WebSocket на показанный URL-адрес. Введите тестовое сообщение и выберите Send (Отправить). После этого выберите Close Socket (Закрыть сокет). В разделе Журнал связи фиксируются все действия открытия, отправки и закрытия по мере их выполнения.

Конечное состояние веб-страницы после подключения WebSocket и отправки тестовых сообщений с их последующим приемом

Эта статья описывает начало работы с WebSocket в ASP.NET Core. WebSocket (RFC 6455) — это протокол, предоставляющий сохраняемые двусторонние каналы связи по TCP-подключениям. Он используется в приложениях, где нужна быстрая связь в режиме реального времени, например в чатах, панелях мониторинга и играх.

Просмотрите или скачайте пример кода (описание процедуры скачивания, описание процедуры выполнения).

Поддержка HTTP/2 WebSockets

При использовании соединений WebSocket по HTTP/2 доступны новые возможности, в том числе следующие:

  • Сжатие заголовков.
  • мультиплексирование, которое сокращает время и ресурсы, необходимые для выполнения нескольких запросов к серверу.

Эти функции доступны в Kestrel на всех платформах с поддержкой HTTP/2. Согласование версии выполняется в браузерах и Kestrel автоматически, поэтому новые интерфейсы API не требуются.

В .NET 7 появилась поддержка WebSockets через HTTP/2 для Kestrel, клиента JavaScript SignalR и SignalR с Blazor WebAssembly.

Note

Соединения WebSocket по HTTP/2 используют запросы CONNECT, а не GET, поэтому ваши собственные маршруты и контроллеры могут потребовать обновления. Дополнительные сведения см. в разделе Добавление поддержки WebSocket по HTTP/2 для существующих контроллеров в этой статье.

Chrome, Edge и Firefox (версия 128 и более поздние версии) включают HTTP/2 WebSockets по умолчанию. Вы можете проверить или изменить этот параметр в Firefox, открыв about:config и найдя network.http.http2.websockets предпочтения.

Протокол WebSocket изначально был разработан для HTTP/1.1, но с тех пор был адаптирован для работы по протоколу HTTP/2. (RFC 8441)

SignalR

ASP.NET Core SignalR — это библиотека, которая упрощает добавление веб-функций в режиме реального времени в приложения. Она использует WebSocket, когда это возможно.

Для большинства приложений рекомендуется использовать SignalR вместо прямых соединений WebSocket. SignalR:

  • обеспечивает резервный транспорт для сред, в которых соединения WebSocket недоступны;
  • Предоставляет базовую модель приложения для удалённого вызова процедур.
  • В большинстве сценариев не имеет существенного снижения производительности по сравнению с непосредственным использованием WebSocket.

WebSocket по HTTP/2 поддерживается для следующих компонентов:

  • клиент JavaScript ASP.NET Core SignalR
  • ASP.NET Core SignalR с Blazor WebAssembly

Для некоторых приложений gRPC в .NET предоставляет альтернативу WebSockets.

Prerequisites

  • Любая ОС, поддерживающая ASP.NET Core:
    • Windows 7/Windows Server 2008 или более поздних версий
    • Linux
    • macOS
  • Если приложение работает в Windows с помощью IIS:
  • Если приложение работает в HTTP.sys:
    • Windows 8/Windows Server 2012 или более поздних версий
  • Список поддерживаемых обозревателей см. на странице Можно ли использовать.

Настройка ПО промежуточного слоя

Добавьте компонент промежуточного слоя WebSockets в Program.cs:

app.UseWebSockets();

Можно настроить следующие параметры:

  • KeepAliveInterval — как часто отправлять клиенту кадры "ping", чтобы прокси-серверы не закрывали соединение. Значение по умолчанию — две минуты.
  • AllowedOrigins — список допустимых значений заголовка Origin для запросов WebSocket. По умолчанию разрешены все источники. Дополнительные сведения см. в разделе Ограничения для источников WebSocket в этой статье.
var webSocketOptions = new WebSocketOptions
{
    KeepAliveInterval = TimeSpan.FromMinutes(2)
};

app.UseWebSockets(webSocketOptions);

Принятие запросов WebSocket

На более позднем этапе жизненного цикла запроса (например, далее в методе Program.cs или методе действия) проверьте, относится ли запрос к WebSocket, и примите его.

Следующий пример приведён далее в Program.cs:

app.Use(async (context, next) =>
{
    if (context.Request.Path == "/ws")
    {
        if (context.WebSockets.IsWebSocketRequest)
        {
            using var webSocket = await context.WebSockets.AcceptWebSocketAsync();
            await Echo(webSocket);
        }
        else
        {
            context.Response.StatusCode = StatusCodes.Status400BadRequest;
        }
    }
    else
    {
        await next(context);
    }

});

Запрос WebSocket может поступить по любому URL-адресу, но этот пример кода принимает только запросы для /ws.

Аналогичный подход можно использовать в методе контроллера:

public class WebSocketController : ControllerBase
{
    [Route("/ws")]
    public async Task Get()
    {
        if (HttpContext.WebSockets.IsWebSocketRequest)
        {
            using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
            await Echo(webSocket);
        }
        else
        {
            HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
        }
    }

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

System.Net.WebSockets.WebSocketException (0x80004005): The remote party closed the WebSocket connection without completing the close handshake. ---> System.ObjectDisposedException: Cannot write to the response body, the response has completed.
Object name: 'HttpResponseStream'.

Если вы используете фоновую службу для записи данных в WebSocket, убедитесь, что конвейер ПО промежуточного слоя продолжает работу. Это можно сделать с помощью TaskCompletionSource<TResult>. Передайте TaskCompletionSource фоновой службе и сделайте так, чтобы по завершении работы с WebSocket она вызвала TrySetResult. Затем await свойство Task во время запроса, как показано в следующем примере:

app.Run(async (context) =>
{
    using var webSocket = await context.WebSockets.AcceptWebSocketAsync();
    var socketFinishedTcs = new TaskCompletionSource<object>();

    BackgroundSocketProcessor.AddSocket(webSocket, socketFinishedTcs);

    await socketFinishedTcs.Task;
});

Исключение о закрытии WebSocket также может возникнуть, если слишком рано выйти из метода действия. При приёме сокета в методе обработчика дождитесь, пока завершится код, использующий сокет, прежде чем завершать метод обработчика.

Никогда не используйте Task.Wait, Task.Result или аналогичные блокирующие вызовы для ожидания выполнения сокета, так как это может привести к серьезным проблемам потокового выполнения. Всегда используйте await.

Добавление поддержки WebSocket по HTTP/2 для существующих контроллеров

В .NET 7 появилась поддержка WebSockets через HTTP/2 для Kestrel, клиента JavaScript SignalR и SignalR с Blazor WebAssembly. Соединения WebSocket по HTTP/2 используют запросы CONNECT, а не GET. Если вы ранее использовали [HttpGet("/path")] в методе действия контроллера для запросов WebSocket, обновите код и вместо него используйте [Route("/path")].

public class WebSocketController : ControllerBase
{
    [Route("/ws")]
    public async Task Get()
    {
        if (HttpContext.WebSockets.IsWebSocketRequest)
        {
            using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
            await Echo(webSocket);
        }
        else
        {
            HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
        }
    }

Compression

Warning

Включение сжатия через зашифрованные подключения может сделать приложение уязвимым к атакам CRIME/BREACH. При отправке конфиденциальной информации не включайте сжатие или используйте WebSocketMessageFlags.DisableCompression в вызове WebSocket.SendAsync. Это относится к обеим сторонам подключения по WebSocket. Обратите внимание, что для API WebSockets в браузере нет конфигурации, позволяющей отключить сжатие для операции отправки.

Если требуется реализовать сжатие сообщений через WebSockets, в коде принятия должно быть указано, что сжатие допустимо, как показано ниже:

using (var webSocket = await context.WebSockets.AcceptWebSocketAsync(
    new WebSocketAcceptContext { DangerousEnableCompression = true }))
{

}

WebSocketAcceptContext.ServerMaxWindowBits и WebSocketAcceptContext.DisableServerContextTakeover — это расширенные параметры, которые отвечают за процесс сжатия.

Сжатие согласовывается между клиентом и сервером при установке первого подключения. Дополнительные сведения о согласовании см. в статье Расширения сжатия для WebSocket RFC.

Note

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

Отправка и получение сообщений

Метод AcceptWebSocketAsync обновляет TCP-соединение до соединения WebSocket и предоставляет объект WebSocket. Используйте объект WebSocket для отправки и получения сообщений.

Приведенный выше код, который принимает запрос WebSocket, передает объект WebSocket в метод Echo. Код принимает сообщение и сразу отправляет такое же сообщение обратно. Сообщения отправляются и принимаются в цикле, пока клиент не закроет подключение:

private static async Task Echo(WebSocket webSocket)
{
    var buffer = new byte[1024 * 4];
    var receiveResult = await webSocket.ReceiveAsync(
        new ArraySegment<byte>(buffer), CancellationToken.None);

    while (!receiveResult.CloseStatus.HasValue)
    {
        await webSocket.SendAsync(
            new ArraySegment<byte>(buffer, 0, receiveResult.Count),
            receiveResult.MessageType,
            receiveResult.EndOfMessage,
            CancellationToken.None);

        receiveResult = await webSocket.ReceiveAsync(
            new ArraySegment<byte>(buffer), CancellationToken.None);
    }

    await webSocket.CloseAsync(
        receiveResult.CloseStatus.Value,
        receiveResult.CloseStatusDescription,
        CancellationToken.None);
}

Если принять WebSocket-подключение прежде, чем начать цикл, конвейер промежуточного ПО завершается. После закрытия сокета конвейер развертывается. То есть запрос перестает перемещаться по конвейеру после принятия WebSocket. После завершения цикла и закрытия сокета запрос возвращается в конвейер.

Обработка отключений клиента

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

Если клиент не всегда отправляет сообщения и вы не хотите, чтобы из-за простоя соединения происходил тайм-аут, настройте его так, чтобы он по таймеру отправлял ping-сообщение каждые X секунд. Если сообщение не было получено на сервере в течение 2*X секунд после предыдущего, завершите подключение и сообщите о том, что клиент отключен. Подождите вдвое дольше ожидаемого интервала времени, чтобы предусмотреть дополнительное время из-за сетевых задержек, которые могут задержать ping-сообщение.

Ограничения для источников WebSocket

Варианты защиты, предоставляемые CORS, не применяются к WebSocket. Браузеры не:

  • Выполните предварительные CORS-запросы.
  • Соблюдайте ограничения, указанные в заголовках Access-Control, при отправке запросов WebSocket.

Однако браузеры отправляют заголовок Origin при выпуске запросов WebSocket. Приложения должны быть настроены на проверку этих заголовков, чтобы разрешались только подключения WebSocket из ожидаемых источников.

Если вы размещаете сервер по адресу "https://server.com", а клиент — по адресу "https://client.com", добавьте "https://client.com" в список AllowedOrigins, подлежащих проверке WebSocket.

var webSocketOptions = new WebSocketOptions
{
    KeepAliveInterval = TimeSpan.FromMinutes(2)
};

webSocketOptions.AllowedOrigins.Add("https://client.com");
webSocketOptions.AllowedOrigins.Add("https://www.client.com");

app.UseWebSockets(webSocketOptions);

Note

Заголовок Origin контролируется клиентом и, как и заголовок Referer, может быть подделан. Не используйте эти заголовки в качестве механизма проверки подлинности.

Поддержка служб IIS/IIS Express

Windows Server 2012 или более поздней версии и Windows 8 или более поздней версии с IIS и IIS Express 8 или более поздней версии поддерживают протокол WebSocket, но не протокол WebSocket по HTTP/2.

Note

Соединения WebSockets всегда включены при использовании IIS Express.

Включение WebSockets в IIS

Чтобы включить поддержку протокола WebSocket в Windows Server 2012 или более поздней версии:

Note

Эти действия не требуется выполнять при использовании IIS Express

  1. В меню Управление запустите мастер Добавить роли и компоненты или в окне Диспетчер серверов щелкните соответствующую ссылку.
  2. Выберите Установка на основе ролей или компонентов. Нажмите кнопку Далее.
  3. Выберите подходящий сервер (по умолчанию выбирается локальный сервер). Нажмите кнопку Далее.
  4. Разверните Веб-сервер (IIS) в дереве Роли, разверните Веб-сервер, а затем Разработка приложений.
  5. Выберите протокол WebSocket. Нажмите кнопку Далее.
  6. Если дополнительные функции не требуются, нажмите Далее.
  7. Нажмите Установить.
  8. По завершении установки выберите Закрыть, чтобы выйти из мастера.

Чтобы включить поддержку протокола WebSocket в Windows 8 или более поздней версии:

Note

Эти действия не требуется выполнять при использовании IIS Express

  1. Последовательно выберите Панель управления>Программы>Программы и компоненты>Включение или отключение компонентов Windows (в левой части экрана).
  2. Откройте следующие узлы: Internet Information Services>Службы World Wide Web>Средства разработки приложений.
  3. Выберите компонент Протокол WebSocket. Нажмите ОК.

Отключите WebSocket при использовании socket.io на Node.js

При использовании поддержки WebSocket в socket.io в Node.jsотключите модуль IIS WebSocket по умолчанию с помощью элемента webSocket в web.config или applicationHost.config. Если этот шаг не выполнен, модуль IIS WebSocket пытается обработать связь WebSocket, а не Node.js и приложение.

<system.webServer>
  <webSocket enabled="false" />
</system.webServer>

Пример приложения

Пример приложения в этой статье — это эхо-приложение. Оно имеет веб-страницу, которая устанавливает соединения WebSocket, а сервер перенаправляет все полученные сообщения обратно клиенту. Пример приложения поддерживает WebSocket по HTTP/2 при использовании целевой платформы .NET 7 или более поздней версии.

Запустите приложение:

  • Чтобы запустить приложение в Visual Studio, откройте пример проекта в этой среде и нажмите клавиши CTRL+F5 для запуска без отладчика.
  • Чтобы запустить приложение в командной оболочке, выполните команду dotnet run и перейдите в браузере по адресу http://localhost:<port>.

На этой веб-странице отображается состояние подключения.

Начальное состояние веб-страницы до подключения WebSocket

Выберите Connect (Подключить), чтобы отправить запрос WebSocket на показанный URL-адрес. Введите тестовое сообщение и выберите Send (Отправить). После этого выберите Close Socket (Закрыть сокет). В разделе Журнал связи фиксируются все действия открытия, отправки и закрытия по мере их выполнения.

Конечное состояние веб-страницы после подключения WebSocket и отправки тестовых сообщений с их последующим приемом

Эта статья описывает начало работы с WebSocket в ASP.NET Core. WebSocket (RFC 6455) — это протокол, предоставляющий сохраняемые двусторонние каналы связи по TCP-подключениям. Он используется в приложениях, где нужна быстрая связь в режиме реального времени, например в чатах, панелях мониторинга и играх.

Просмотрите или скачайте пример кода (описание процедуры скачивания, описание процедуры выполнения).

SignalR

ASP.NET Core SignalR — это библиотека, которая упрощает добавление веб-функций в режиме реального времени в приложения. Она использует WebSocket, когда это возможно.

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

Для некоторых приложений gRPC в .NET предоставляет альтернативу WebSockets.

Prerequisites

  • Любая ОС, поддерживающая ASP.NET Core:
    • Windows 7/Windows Server 2008 или более поздних версий
    • Linux
    • macOS
  • Если приложение работает в Windows с помощью IIS:
  • Если приложение работает в HTTP.sys:
    • Windows 8/Windows Server 2012 или более поздних версий
  • Список поддерживаемых обозревателей см. на странице Можно ли использовать.

Настройка ПО промежуточного слоя

Добавьте компонент промежуточного слоя WebSockets в Program.cs:

app.UseWebSockets();

Можно настроить следующие параметры:

  • KeepAliveInterval — как часто отправлять клиенту кадры "ping", чтобы прокси-серверы не закрывали соединение. Значение по умолчанию — две минуты.
  • AllowedOrigins — список допустимых значений заголовка Origin для запросов WebSocket. По умолчанию разрешены все источники. Дополнительные сведения см. в разделе Ограничения для источников WebSocket в этой статье.
var webSocketOptions = new WebSocketOptions
{
    KeepAliveInterval = TimeSpan.FromMinutes(2)
};

app.UseWebSockets(webSocketOptions);

Принятие запросов WebSocket

На более позднем этапе жизненного цикла запроса (например, далее в методе Program.cs или методе действия) проверьте, относится ли запрос к WebSocket, и примите его.

Следующий пример приведён далее в Program.cs:

app.Use(async (context, next) =>
{
    if (context.Request.Path == "/ws")
    {
        if (context.WebSockets.IsWebSocketRequest)
        {
            using var webSocket = await context.WebSockets.AcceptWebSocketAsync();
            await Echo(webSocket);
        }
        else
        {
            context.Response.StatusCode = StatusCodes.Status400BadRequest;
        }
    }
    else
    {
        await next(context);
    }

});

Запрос WebSocket может поступить по любому URL-адресу, но этот пример кода принимает только запросы для /ws.

Аналогичный подход можно использовать в методе контроллера:

public class WebSocketController : ControllerBase
{
    [HttpGet("/ws")]
    public async Task Get()
    {
        if (HttpContext.WebSockets.IsWebSocketRequest)
        {
            using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
            await Echo(webSocket);
        }
        else
        {
            HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
        }
    }

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

System.Net.WebSockets.WebSocketException (0x80004005): The remote party closed the WebSocket connection without completing the close handshake. ---> System.ObjectDisposedException: Cannot write to the response body, the response has completed.
Object name: 'HttpResponseStream'.

Если вы используете фоновую службу для записи данных в WebSocket, убедитесь, что конвейер ПО промежуточного слоя продолжает работу. Это можно сделать с помощью TaskCompletionSource<TResult>. Передайте TaskCompletionSource фоновой службе и сделайте так, чтобы по завершении работы с WebSocket она вызвала TrySetResult. Затем await свойство Task во время запроса, как показано в следующем примере:

app.Run(async (context) =>
{
    using var webSocket = await context.WebSockets.AcceptWebSocketAsync();
    var socketFinishedTcs = new TaskCompletionSource<object>();

    BackgroundSocketProcessor.AddSocket(webSocket, socketFinishedTcs);

    await socketFinishedTcs.Task;
});

Исключение о закрытии WebSocket также может возникнуть, если слишком рано выйти из метода действия. При приёме сокета в методе обработчика дождитесь, пока завершится код, использующий сокет, прежде чем завершать метод обработчика.

Никогда не используйте Task.Wait, Task.Result или аналогичные блокирующие вызовы для ожидания выполнения сокета, так как это может привести к серьезным проблемам потокового выполнения. Всегда используйте await.

Compression

Warning

Включение сжатия через зашифрованные подключения может сделать приложение уязвимым к атакам CRIME/BREACH. При отправке конфиденциальной информации не включайте сжатие или используйте WebSocketMessageFlags.DisableCompression в вызове WebSocket.SendAsync. Это относится к обеим сторонам подключения по WebSocket. Обратите внимание, что для API WebSockets в браузере нет конфигурации, позволяющей отключить сжатие для операции отправки.

Если требуется реализовать сжатие сообщений через WebSockets, в коде принятия должно быть указано, что сжатие допустимо, как показано ниже:

using (var webSocket = await context.WebSockets.AcceptWebSocketAsync(
    new WebSocketAcceptContext { DangerousEnableCompression = true }))
{

}

WebSocketAcceptContext.ServerMaxWindowBits и WebSocketAcceptContext.DisableServerContextTakeover — это расширенные параметры, которые отвечают за процесс сжатия.

Сжатие согласовывается между клиентом и сервером при установке первого подключения. Дополнительные сведения о согласовании см. в статье Расширения сжатия для WebSocket RFC.

Note

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

Отправка и получение сообщений

Метод AcceptWebSocketAsync обновляет TCP-соединение до соединения WebSocket и предоставляет объект WebSocket. Используйте объект WebSocket для отправки и получения сообщений.

Приведенный выше код, который принимает запрос WebSocket, передает объект WebSocket в метод Echo. Код принимает сообщение и сразу отправляет такое же сообщение обратно. Сообщения отправляются и принимаются в цикле, пока клиент не закроет подключение:

private static async Task Echo(WebSocket webSocket)
{
    var buffer = new byte[1024 * 4];
    var receiveResult = await webSocket.ReceiveAsync(
        new ArraySegment<byte>(buffer), CancellationToken.None);

    while (!receiveResult.CloseStatus.HasValue)
    {
        await webSocket.SendAsync(
            new ArraySegment<byte>(buffer, 0, receiveResult.Count),
            receiveResult.MessageType,
            receiveResult.EndOfMessage,
            CancellationToken.None);

        receiveResult = await webSocket.ReceiveAsync(
            new ArraySegment<byte>(buffer), CancellationToken.None);
    }

    await webSocket.CloseAsync(
        receiveResult.CloseStatus.Value,
        receiveResult.CloseStatusDescription,
        CancellationToken.None);
}

Если принять WebSocket-подключение прежде, чем начать цикл, конвейер промежуточного ПО завершается. После закрытия сокета конвейер развертывается. То есть запрос перестает перемещаться по конвейеру после принятия WebSocket. После завершения цикла и закрытия сокета запрос возвращается в конвейер.

Обработка отключений клиента

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

Если клиент не всегда отправляет сообщения и вы не хотите, чтобы из-за простоя соединения происходил тайм-аут, настройте его так, чтобы он по таймеру отправлял ping-сообщение каждые X секунд. Если сообщение не было получено на сервере в течение 2*X секунд после предыдущего, завершите подключение и сообщите о том, что клиент отключен. Подождите вдвое дольше ожидаемого интервала времени, чтобы предусмотреть дополнительное время из-за сетевых задержек, которые могут задержать ping-сообщение.

Ограничения для источников WebSocket

Варианты защиты, предоставляемые CORS, не применяются к WebSocket. Браузеры не:

  • Выполните предварительные CORS-запросы.
  • Соблюдайте ограничения, указанные в заголовках Access-Control, при отправке запросов WebSocket.

Однако браузеры отправляют заголовок Origin при выпуске запросов WebSocket. Приложения должны быть настроены на проверку этих заголовков, чтобы разрешались только подключения WebSocket из ожидаемых источников.

Если вы размещаете сервер по адресу "https://server.com", а клиент — по адресу "https://client.com", добавьте "https://client.com" в список AllowedOrigins, подлежащих проверке WebSocket.

var webSocketOptions = new WebSocketOptions
{
    KeepAliveInterval = TimeSpan.FromMinutes(2)
};

webSocketOptions.AllowedOrigins.Add("https://client.com");
webSocketOptions.AllowedOrigins.Add("https://www.client.com");

app.UseWebSockets(webSocketOptions);

Note

Заголовок Origin контролируется клиентом и, как и заголовок Referer, может быть подделан. Не используйте эти заголовки в качестве механизма проверки подлинности.

Поддержка служб IIS/IIS Express

Windows Server 2012 или более поздней версии и Windows 8 или более поздней версии с IIS и IIS Express 8 или более поздней версии поддерживают протокол WebSocket.

Note

Соединения WebSockets всегда включены при использовании IIS Express.

Включение WebSockets в IIS

Чтобы включить поддержку протокола WebSocket в Windows Server 2012 или более поздней версии:

Note

Эти действия не требуется выполнять при использовании IIS Express

  1. В меню Управление запустите мастер Добавить роли и компоненты или в окне Диспетчер серверов щелкните соответствующую ссылку.
  2. Выберите Установка на основе ролей или компонентов. Нажмите кнопку Далее.
  3. Выберите подходящий сервер (по умолчанию выбирается локальный сервер). Нажмите кнопку Далее.
  4. Разверните Веб-сервер (IIS) в дереве Роли, разверните Веб-сервер, а затем Разработка приложений.
  5. Выберите протокол WebSocket. Нажмите кнопку Далее.
  6. Если дополнительные функции не требуются, нажмите Далее.
  7. Нажмите Установить.
  8. По завершении установки выберите Закрыть, чтобы выйти из мастера.

Чтобы включить поддержку протокола WebSocket в Windows 8 или более поздней версии:

Note

Эти действия не требуется выполнять при использовании IIS Express

  1. Последовательно выберите Панель управления>Программы>Программы и компоненты>Включение или отключение компонентов Windows (в левой части экрана).
  2. Откройте следующие узлы: Internet Information Services>Службы World Wide Web>Средства разработки приложений.
  3. Выберите компонент Протокол WebSocket. Нажмите ОК.

Отключите WebSocket при использовании socket.io на Node.js

При использовании поддержки WebSocket в socket.io в Node.jsотключите модуль IIS WebSocket по умолчанию с помощью элемента webSocket в web.config или applicationHost.config. Если этот шаг не выполнен, модуль IIS WebSocket пытается обработать связь WebSocket, а не Node.js и приложение.

<system.webServer>
  <webSocket enabled="false" />
</system.webServer>

Пример приложения

Пример приложения в этой статье — это эхо-приложение. Оно имеет веб-страницу, которая устанавливает соединения WebSocket, а сервер перенаправляет все полученные сообщения обратно клиенту. Этот пример приложения не настроен для запуска из Visual Studio с IIS Express, поэтому его необходимо запустить в командной оболочке с dotnet run и затем перейти в браузере по адресу http://localhost:<port>. На этой веб-странице отображается состояние подключения.

Начальное состояние веб-страницы до подключения WebSocket

Выберите Connect (Подключить), чтобы отправить запрос WebSocket на показанный URL-адрес. Введите тестовое сообщение и выберите Send (Отправить). После этого выберите Close Socket (Закрыть сокет). В разделе Журнал связи фиксируются все действия открытия, отправки и закрытия по мере их выполнения.

Конечное состояние веб-страницы после подключения WebSocket и отправки тестовых сообщений с их последующим приемом

Эта статья описывает начало работы с WebSocket в ASP.NET Core. WebSocket (RFC 6455) — это протокол, предоставляющий сохраняемые двусторонние каналы связи по TCP-подключениям. Он используется в приложениях, где нужна быстрая связь в режиме реального времени, например в чатах, панелях мониторинга и играх.

Просмотреть или скачать пример кода (описание скачивания). Процедура выполнения.

SignalR

ASP.NET Core SignalR — это библиотека, которая упрощает добавление веб-функций в режиме реального времени в приложения. Она использует WebSocket, когда это возможно.

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

Для некоторых приложений gRPC в .NET предоставляет альтернативу WebSockets.

Prerequisites

  • Любая ОС, поддерживающая ASP.NET Core:
    • Windows 7/Windows Server 2008 или более поздних версий
    • Linux
    • macOS
  • Если приложение работает в Windows с помощью IIS:
  • Если приложение работает в HTTP.sys:
    • Windows 8/Windows Server 2012 или более поздних версий
  • Список поддерживаемых обозревателей см. на странице Можно ли использовать.

Настройка ПО промежуточного слоя

Добавьте компонент WebSockets middleware в метод Configure класса Startup:

app.UseWebSockets();

Note

Если вы хотите принимать запросы WebSocket в контроллере, вызов app.UseWebSockets должен предшествовать app.UseEndpoints.

Можно настроить следующие параметры:

  • KeepAliveInterval — как часто отправлять клиенту кадры "ping", чтобы прокси-серверы не закрывали соединение. Значение по умолчанию — две минуты.
  • AllowedOrigins — список допустимых значений заголовка Origin для запросов WebSocket. По умолчанию разрешены все источники. Дополнительные сведения см. в разделе "Ограничения для источников WebSocket" ниже.
var webSocketOptions = new WebSocketOptions()
{
    KeepAliveInterval = TimeSpan.FromSeconds(120),
};

app.UseWebSockets(webSocketOptions);

Принятие запросов WebSocket

На более позднем этапе жизненного цикла запроса (например, далее в методе Configure или методе действия) проверьте, относится ли запрос к WebSocket, и примите его.

Следующий пример взят из дальнейшей части метода Configure:

app.Use(async (context, next) =>
{
    if (context.Request.Path == "/ws")
    {
        if (context.WebSockets.IsWebSocketRequest)
        {
            using (WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync())
            {
                await Echo(context, webSocket);
            }
        }
        else
        {
            context.Response.StatusCode = (int) HttpStatusCode.BadRequest;
        }
    }
    else
    {
        await next();
    }

});

Запрос WebSocket может поступить по любому URL-адресу, но этот пример кода принимает только запросы для /ws.

Аналогичный подход можно использовать в методе контроллера:

public class WebSocketController : ControllerBase
{
    [HttpGet("/ws")]
    public async Task Get()
    {
        if (HttpContext.WebSockets.IsWebSocketRequest)
        {
            using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
            await Echo(webSocket);
        }
        else
        {
            HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
        }
    }

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

System.Net.WebSockets.WebSocketException (0x80004005): The remote party closed the WebSocket connection without completing the close handshake. ---> System.ObjectDisposedException: Cannot write to the response body, the response has completed.
Object name: 'HttpResponseStream'.

Если вы используете фоновую службу для записи данных в WebSocket, убедитесь, что конвейер ПО промежуточного слоя продолжает работу. Это можно сделать с помощью TaskCompletionSource<TResult>. Передайте TaskCompletionSource фоновой службе и сделайте так, чтобы по завершении работы с WebSocket она вызвала TrySetResult. Затем await свойство Task во время запроса, как показано в следующем примере:

app.Use(async (context, next) =>
{
    using (WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync())
    {
        var socketFinishedTcs = new TaskCompletionSource<object>();

        BackgroundSocketProcessor.AddSocket(webSocket, socketFinishedTcs);

        await socketFinishedTcs.Task;
    }
});

Исключение о закрытии WebSocket также может возникнуть, если слишком рано выйти из метода действия. При приёме сокета в методе обработчика дождитесь, пока завершится код, использующий сокет, прежде чем завершать метод обработчика.

Никогда не используйте Task.Wait, Task.Result или аналогичные блокирующие вызовы для ожидания выполнения сокета, так как это может привести к серьезным проблемам потокового выполнения. Всегда используйте await.

Отправка и получение сообщений

Метод AcceptWebSocketAsync обновляет TCP-соединение до соединения WebSocket и предоставляет объект WebSocket. Используйте объект WebSocket для отправки и получения сообщений.

Приведенный выше код, который принимает запрос WebSocket, передает объект WebSocket в метод Echo. Код принимает сообщение и сразу отправляет такое же сообщение обратно. Сообщения отправляются и принимаются в цикле, пока клиент не закроет подключение:

private async Task Echo(HttpContext context, WebSocket webSocket)
{
    var buffer = new byte[1024 * 4];
    WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
    while (!result.CloseStatus.HasValue)
    {
        await webSocket.SendAsync(new ArraySegment<byte>(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None);

        result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
    }
    await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
}

Если принять WebSocket-подключение прежде, чем начать цикл, конвейер промежуточного ПО завершается. После закрытия сокета конвейер развертывается. То есть запрос перестает перемещаться по конвейеру после принятия WebSocket. После завершения цикла и закрытия сокета запрос возвращается в конвейер.

Обработка отключений клиента

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

Если клиент не всегда отправляет сообщения и вы не хотите, чтобы из-за простоя соединения происходил тайм-аут, настройте его так, чтобы он по таймеру отправлял ping-сообщение каждые X секунд. Если сообщение не было получено на сервере в течение 2*X секунд после предыдущего, завершите подключение и сообщите о том, что клиент отключен. Подождите вдвое дольше ожидаемого интервала времени, чтобы предусмотреть дополнительное время из-за сетевых задержек, которые могут задержать ping-сообщение.

Note

Внутренний ManagedWebSocket неявно обрабатывает кадры Ping/Pong, чтобы поддерживать соединение активным, если значение параметра KeepAliveInterval больше нуля; по умолчанию оно составляет 30 секунд (TimeSpan.FromSeconds(30)).

Ограничения для источников WebSocket

Варианты защиты, предоставляемые CORS, не применяются к WebSocket. Браузеры не:

  • Выполните предварительные CORS-запросы.
  • Соблюдайте ограничения, указанные в заголовках Access-Control, при отправке запросов WebSocket.

Однако браузеры отправляют заголовок Origin при выпуске запросов WebSocket. Приложения должны быть настроены на проверку этих заголовков, чтобы разрешались только подключения WebSocket из ожидаемых источников.

Если вы размещаете сервер по адресу "https://server.com", а клиент — по адресу "https://client.com", добавьте "https://client.com" в список AllowedOrigins, подлежащих проверке WebSocket.

var webSocketOptions = new WebSocketOptions()
{
    KeepAliveInterval = TimeSpan.FromSeconds(120),
};
webSocketOptions.AllowedOrigins.Add("https://client.com");
webSocketOptions.AllowedOrigins.Add("https://www.client.com");

app.UseWebSockets(webSocketOptions);

Note

Заголовок Origin контролируется клиентом и, как и заголовок Referer, может быть подделан. Не используйте эти заголовки в качестве механизма проверки подлинности.

Поддержка служб IIS/IIS Express

Windows Server 2012 или более поздней версии и Windows 8 или более поздней версии с IIS и IIS Express 8 или более поздней версии поддерживают протокол WebSocket.

Note

Соединения WebSockets всегда включены при использовании IIS Express.

Включение WebSockets в IIS

Чтобы включить поддержку протокола WebSocket в Windows Server 2012 или более поздней версии:

Note

Эти действия не требуется выполнять при использовании IIS Express

  1. В меню Управление запустите мастер Добавить роли и компоненты или в окне Диспетчер серверов щелкните соответствующую ссылку.
  2. Выберите Установка на основе ролей или компонентов. Нажмите кнопку Далее.
  3. Выберите подходящий сервер (по умолчанию выбирается локальный сервер). Нажмите кнопку Далее.
  4. Разверните Веб-сервер (IIS) в дереве Роли, разверните Веб-сервер, а затем Разработка приложений.
  5. Выберите протокол WebSocket. Нажмите кнопку Далее.
  6. Если дополнительные функции не требуются, нажмите Далее.
  7. Нажмите Установить.
  8. По завершении установки выберите Закрыть, чтобы выйти из мастера.

Чтобы включить поддержку протокола WebSocket в Windows 8 или более поздней версии:

Note

Эти действия не требуется выполнять при использовании IIS Express

  1. Последовательно выберите Панель управления>Программы>Программы и компоненты>Включение или отключение компонентов Windows (в левой части экрана).
  2. Откройте следующие узлы: Internet Information Services>Службы World Wide Web>Средства разработки приложений.
  3. Выберите компонент Протокол WebSocket. Нажмите ОК.

Отключите WebSocket при использовании socket.io на Node.js

При использовании поддержки WebSocket в socket.io в Node.jsотключите модуль IIS WebSocket по умолчанию с помощью элемента webSocket в web.config или applicationHost.config. Если этот шаг не выполнен, модуль IIS WebSocket пытается обработать связь WebSocket, а не Node.js и приложение.

<system.webServer>
  <webSocket enabled="false" />
</system.webServer>

Пример приложения

Пример приложения в этой статье — это эхо-приложение. Оно имеет веб-страницу, которая устанавливает соединения WebSocket, а сервер перенаправляет все полученные сообщения обратно клиенту. Этот пример приложения не настроен для запуска из Visual Studio с IIS Express, поэтому его необходимо запустить в командной оболочке с dotnet run и затем перейти в браузере по адресу http://localhost:5000. На этой веб-странице отображается состояние подключения.

Начальное состояние веб-страницы до подключения WebSocket

Выберите Connect (Подключить), чтобы отправить запрос WebSocket на показанный URL-адрес. Введите тестовое сообщение и выберите Send (Отправить). После этого выберите Close Socket (Закрыть сокет). В разделе Журнал связи фиксируются все действия открытия, отправки и закрытия по мере их выполнения.

Конечное состояние веб-страницы после подключения WebSocket и отправки тестовых сообщений с их последующим приемом