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

Авторы: Tom Dykstra (Том Дикстра) и Andrew Stanton-Nurse (Эндрю Стэнтон-Нёрс)By Tom Dykstra and Andrew Stanton-Nurse

Эта статья описывает начало работы с WebSocket в ASP.NET Core.This article explains how to get started with WebSockets in ASP.NET Core. WebSocket (RFC 6455) — это протокол, предоставляющий сохраняемые двусторонние каналы связи по TCP-подключениям.WebSocket (RFC 6455) is a protocol that enables two-way persistent communication channels over TCP connections. Он используется в приложениях, где нужна быстрая связь в режиме реального времени, например в чатах, панелях мониторинга и играх.It's used in apps that benefit from fast, real-time communication, such as chat, dashboard, and game apps.

Просмотреть или скачать пример кода (описание скачивания).View or download sample code (how to download). Процедура выполнения.How to run.

SignalRSignalR

ASP.NET Core SignalR — это библиотека, которая упрощает добавление веб-функций в приложения в режиме реального времени.ASP.NET Core SignalR is a library that simplifies adding real-time web functionality to apps. Она использует WebSocket, когда это возможно.It uses WebSockets whenever possible.

Для большинства приложений рекомендуется использовать SignalR вместо прямых соединений WebSocket.For most applications, we recommend SignalR over raw WebSockets. Служба SignalR обеспечивает резервный транспорт для сред, в которых соединения WebSocket недоступны.SignalR provides transport fallback for environments where WebSockets is not available. Она также предоставляет простую модель приложений на основе удаленного вызова процедур.It also provides a simple remote procedure call app model. В большинстве сценариев SignalR по производительности не уступает прямым соединениям WebSocket.And in most scenarios, SignalR has no significant performance disadvantage compared to using raw WebSockets.

Предварительные требованияPrerequisites

  • ASP.NET Core 1.1 или более поздней версииASP.NET Core 1.1 or later

  • Любая ОС с поддержкой ASP.NET Core:Any OS that supports ASP.NET Core:

    • Windows 7/Windows Server 2008 или более поздних версийWindows 7 / Windows Server 2008 or later
    • LinuxLinux
    • macOSmacOS
  • Если приложение выполняется в Windows с IIS:If the app runs on Windows with IIS:

    • Windows 8/Windows Server 2012 или более поздних версийWindows 8 / Windows Server 2012 or later
    • IIS 8/IIS 8 ExpressIIS 8 / IIS 8 Express
    • Необходимо включить WebSockets (см. раздел Поддержка IIS и IIS Express).WebSockets must be enabled (See the IIS/IIS Express support section.).
  • Если приложение выполняется в HTTP.sys:If the app runs on HTTP.sys:

    • Windows 8/Windows Server 2012 или более поздних версийWindows 8 / Windows Server 2012 or later
  • Список поддерживаемых обозревателей см. на странице https://caniuse.com/#feat=websockets.For supported browsers, see https://caniuse.com/#feat=websockets.

Пакет NuGetNuGet package

Установка пакета Microsoft.AspNetCore.WebSockets.Install the Microsoft.AspNetCore.WebSockets package.

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

Добавьте ПО промежуточного слоя WebSocket в метод Configure класса Startup:Add the WebSockets middleware in the Configure method of the Startup class:

app.UseWebSockets();

Можно настроить следующие параметры:The following settings can be configured:

  • KeepAliveInterval — как часто нужно отправлять клиенту кадры проверки связи, чтобы прокси-серверы удерживали соединение открытым.KeepAliveInterval - How frequently to send "ping" frames to the client to ensure proxies keep the connection open. Значение по умолчанию — две минуты.The default is two minutes.
  • ReceiveBufferSize — размер буфера, используемого для получения данных.ReceiveBufferSize - The size of the buffer used to receive data. Опытные пользователи могут изменить этот параметр для настройки производительности с учетом размера данных.Advanced users may need to change this for performance tuning based on the size of the data. Значение по умолчанию — 4 КБ.The default is 4 KB.

Можно настроить следующие параметры:The following settings can be configured:

  • KeepAliveInterval — как часто нужно отправлять клиенту кадры проверки связи, чтобы прокси-серверы удерживали соединение открытым.KeepAliveInterval - How frequently to send "ping" frames to the client to ensure proxies keep the connection open. Значение по умолчанию — две минуты.The default is two minutes.
  • ReceiveBufferSize — размер буфера, используемого для получения данных.ReceiveBufferSize - The size of the buffer used to receive data. Опытные пользователи могут изменить этот параметр для настройки производительности с учетом размера данных.Advanced users may need to change this for performance tuning based on the size of the data. Значение по умолчанию — 4 КБ.The default is 4 KB.
  • AllowedOrigins — список допустимых значений заголовка Origin для запросов WebSocket.AllowedOrigins - A list of allowed Origin header values for WebSocket requests. По умолчанию разрешены все источники.By default, all origins are allowed. Дополнительные сведения см. в разделе "Ограничения для источников WebSocket" ниже.See "WebSocket origin restriction" below for details.
var webSocketOptions = new WebSocketOptions() 
{
    KeepAliveInterval = TimeSpan.FromSeconds(120),
    ReceiveBufferSize = 4 * 1024
};

app.UseWebSockets(webSocketOptions);

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

На более позднем этапе жизненного цикла запроса (например, далее в методе Configure или методе действия) проверьте, относится ли запрос к WebSocket, и примите его.Somewhere later in the request life cycle (later in the Configure method or in an action method, for example) check if it's a WebSocket request and accept the WebSocket request.

Следующий пример взят из дальнейшей части метода Configure:The following example is from later in the Configure method:

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

});

Запрос WebSocket может поступить по любому URL-адресу, но этот пример кода принимает только запросы для /ws.A WebSocket request could come in on any URL, but this sample code only accepts requests for /ws.

При использовании WebSocket вам необходимо поддерживать работу конвейера ПО промежуточного слоя, пока соединение активно.When using a WebSocket, you must keep the middleware pipeline running for the duration of the connection. При попытке отправить или получить сообщение WebSocket после того, как конвейер ПО промежуточного слоя завершил работу, вы можете получить исключение такого вида:If you attempt to send or receive a WebSocket message after the middleware pipeline ends, you may get an exception like the following:

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, убедитесь, что конвейер ПО промежуточного слоя продолжает работу.If you're using a background service to write data to a WebSocket, make sure you keep the middleware pipeline running. Это можно сделать с помощью TaskCompletionSource<TResult>.Do this by using a TaskCompletionSource<TResult>. Передайте TaskCompletionSource фоновой службе, а после завершения работы с WebSocket вызовите TrySetResult с ее помощью.Pass the TaskCompletionSource to your background service and have it call TrySetResult when you finish with the WebSocket. Затем используйте конструкцию await для ожидания свойства Task во время запроса, как показано в следующем примере:Then await the Task property during the request, as shown in the following example:

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

    BackgroundSocketProcessor.AddSocket(socket, socketFinishedTcs); 

    await socketFinishedTcs.Task;
});

Исключение закрытия WebSocket может также возникнуть, если метод действия возвращает управление слишком быстро.The WebSocket closed exception can also happen if you return too soon from an action method. Если вы принимаете сокет в методе действия, дождитесь завершения выполнения кода, в котором используется сокет, прежде чем возвращать управление из метода действия.If you accept a socket in an action method, wait for the code that uses the socket to complete before returning from the action method.

Никогда не используйте Task.Wait(), Task.Result или аналогичные блокирующие вызовы для ожидания выполнения сокета, так как это может привести к серьезным проблемам потокового выполнения.Never use Task.Wait(), Task.Result, or similar blocking calls to wait for the socket to complete, as that can cause serious threading issues. Всегда используйте await.Always use await.

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

Метод AcceptWebSocketAsync обновляет TCP-соединение до соединения WebSocket и предоставляет объект WebSocket.The AcceptWebSocketAsync method upgrades the TCP connection to a WebSocket connection and provides a WebSocket object. Используйте объект WebSocket для отправки и получения сообщений.Use the WebSocket object to send and receive messages.

Приведенный выше код, который принимает запрос WebSocket, передает объект WebSocket в метод Echo.The code shown earlier that accepts the WebSocket request passes the WebSocket object to an Echo method. Код принимает сообщение и сразу отправляет такое же сообщение обратно.The code receives a message and immediately sends back the same message. Сообщения отправляются и получаются циклически, пока клиент не закроет подключение:Messages are sent and received in a loop until the client closes the connection:

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 до начала этого цикла, конвейер ПО промежуточного слоя завершается.When accepting the WebSocket connection before beginning the loop, the middleware pipeline ends. После закрытия сокета конвейер развертывается.Upon closing the socket, the pipeline unwinds. То есть запрос перестает перемещаться по конвейеру после принятия WebSocket.That is, the request stops moving forward in the pipeline when the WebSocket is accepted. После завершения цикла и закрытия сокета запрос возвращается в конвейер.When the loop is finished and the socket is closed, the request proceeds back up the pipeline.

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

При отключении клиента из-за потери связи сервер не получает сведения автоматически.The server is not automatically informed when the client disconnects due to loss of connectivity. Сервер получает сообщение об отключении, только если клиент отправляет его. В случае потери подключения к Интернету это невозможно.The server receives a disconnect message only if the client sends it, which can't be done if the internet connection is lost. Если при этом вы хотите выполнить определенное действие, установите время ожидания сигнала клиента с определенным интервалом.If you want to take some action when that happens, set a timeout after nothing is received from the client within a certain time window.

Если клиент не всегда отправляет сообщения, а вы не хотите ожидать истечения времени ожидания только потому, что подключение не используется, тогда клиент может использовать таймер для отправки сообщения проверки связи каждые X секунд.If the client isn't always sending messages and you don't want to timeout just because the connection goes idle, have the client use a timer to send a ping message every X seconds. Если сообщение не было получено на сервере в течение 2*X секунд после предыдущего, завершите подключение и сообщите о том, что клиент отключен.On the server, if a message hasn't arrived within 2*X seconds after the previous one, terminate the connection and report that the client disconnected. Подождите вдвое дольше ожидаемого временного интервала, чтобы оставить дополнительное время на задержки в сети при отправке сообщения проверки связи.Wait for twice the expected time interval to leave extra time for network delays that might hold up the ping message.

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

Варианты защиты, предоставляемые CORS, не применяются к WebSocket.The protections provided by CORS don't apply to WebSockets. Браузеры не поддерживают следующие задачи:Browsers do not:

  • выполнение предварительных запросов CORS;Perform CORS pre-flight requests.
  • использование ограничений, указанных в заголовках Access-Control, при выполнении запросов WebSocket.Respect the restrictions specified in Access-Control headers when making WebSocket requests.

Однако браузеры отправляют заголовок Origin при выпуске запросов WebSocket.However, browsers do send the Origin header when issuing WebSocket requests. Приложения должны быть настроены для проверки этих заголовков, чтобы использовались только WebSocket из ожидаемых источников.Applications should be configured to validate these headers to ensure that only WebSockets coming from the expected origins are allowed.

Если вы размещаете сервер по адресу "https://server.com", а клиент — по адресу "https://client.com", добавьте "https://client.com" в список AllowedOrigins подлежащих проверке WebSocket.If you're hosting your server on "https://server.com" and hosting your client on "https://client.com", add "https://client.com" to the AllowedOrigins list for WebSockets to verify.

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

app.UseWebSockets(webSocketOptions);

Примечание

Заголовок Origin контролируется клиентом и, как и заголовок Referer, может быть подделан.The Origin header is controlled by the client and, like the Referer header, can be faked. Не используйте эти заголовки в качестве механизма проверки подлинности.Do not use these headers as an authentication mechanism.

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

Windows Server 2012 или более поздней версии и Windows 8 или более поздней версии с IIS и IIS Express 8 или более поздней версии поддерживают протокол WebSocket.Windows Server 2012 or later and Windows 8 or later with IIS/IIS Express 8 or later has support for the WebSocket protocol.

Примечание

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

Включение WebSockets в службах IISEnabling WebSockets on IIS

Чтобы включить поддержку протокола WebSocket в Windows Server 2012 или более поздней версии:To enable support for the WebSocket protocol on Windows Server 2012 or later:

Примечание

Эти действия не требуется выполнять при использовании IIS ExpressThese steps are not required when using IIS Express

  1. В меню Управление запустите мастер Добавить роли и компоненты или в окне Диспетчер серверов щелкните соответствующую ссылку.Use the Add Roles and Features wizard from the Manage menu or the link in Server Manager.
  2. Выберите Установка ролей или компонентов.Select Role-based or Feature-based Installation. Выберите Далее.Select Next.
  3. Выберите подходящий сервер (по умолчанию выбирается локальный сервер).Select the appropriate server (the local server is selected by default). Выберите Далее.Select Next.
  4. Разверните Веб-сервер (IIS) в дереве Роли, разверните Веб-сервер, а затем Разработка приложений.Expand Web Server (IIS) in the Roles tree, expand Web Server, and then expand Application Development.
  5. Выберите протокол WebSocket.Select WebSocket Protocol. Выберите Далее.Select Next.
  6. Если дополнительные функции не требуются, нажмите Далее.If additional features aren't needed, select Next.
  7. Нажмите кнопку Установить.Select Install.
  8. По завершении установки выберите Закрыть, чтобы выйти из мастера.When the installation completes, select Close to exit the wizard.

Чтобы включить поддержку протокола WebSocket в Windows 8 или более поздней версии:To enable support for the WebSocket protocol on Windows 8 or later:

Примечание

Эти действия не требуется выполнять при использовании IIS ExpressThese steps are not required when using IIS Express

  1. Последовательно выберите Панель управления > Программы > Программы и компоненты > Включение или отключение компонентов Windows (в левой части экрана).Navigate to Control Panel > Programs > Programs and Features > Turn Windows features on or off (left side of the screen).
  2. Откройте следующие узлы: IIS > Службы Интернета > Компоненты разработки приложений.Open the following nodes: Internet Information Services > World Wide Web Services > Application Development Features.
  3. Выберите компонент Протокол WebSocket.Select the WebSocket Protocol feature. Нажмите кнопку ОК.Select OK.

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

Если используется поддержка WebSocket в socket.io на Node.js, отключите модуль WebSocket IIS по умолчанию с помощью элемента webSocket в web.config или applicationHost.config. Если не выполнить этот шаг, модуль IIS WebSocket попытается обработать соединение WebSocket, а не Node.js и приложение.If using the WebSocket support in socket.io on Node.js, disable the default IIS WebSocket module using the webSocket element in web.config or applicationHost.config. If this step isn't performed, the IIS WebSocket module attempts to handle the WebSocket communication rather than Node.js and the app.

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

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

Пример приложения в этой статье — это эхо-приложение.The sample app that accompanies this article is an echo app. Оно имеет веб-страницу, которая устанавливает соединения WebSocket, а сервер перенаправляет все полученные сообщения обратно клиенту.It has a web page that makes WebSocket connections, and the server resends any messages it receives back to the client. Запустите приложение из командной строки (оно не предназначено для запуска из Visual Studio с IIS Express) и перейдите по адресу http://localhost:5000.Run the app from a command prompt (it's not set up to run from Visual Studio with IIS Express) and navigate to http://localhost:5000. В верхнем левом углу веб-страницы отображается состояние подключения:The web page shows the connection status in the upper left:

Начальное состояние веб-страницы

Выберите Connect (Подключить), чтобы отправить запрос WebSocket на показанный URL-адрес.Select Connect to send a WebSocket request to the URL shown. Введите тестовое сообщение и выберите Send (Отправить).Enter a test message and select Send. После этого выберите Close Socket (Закрыть сокет).When done, select Close Socket. В разделе Communication Log (Журнал связи) выводится каждое выполняемое действие открытия, отправки и закрытия.The Communication Log section reports each open, send, and close action as it happens.

Начальное состояние веб-страницы