Evento
Campionato do Mundo de Power BI DataViz
Feb 14, 4 PM - Mar 31, 4 PM
Con 4 posibilidades de entrar, poderías gañar un paquete de conferencias e facelo ao Live Grand Finale en Las Vegas
Máis informaciónEste explorador xa non é compatible.
Actualice a Microsoft Edge para dispoñer das funcionalidades máis recentes, as actualizacións de seguranza e a asistencia técnica.
Nota
Esta no es la versión más reciente de este artículo. Para la versión actual, consulte la versión de .NET 9 de este artículo.
Aviso
Esta versión de ASP.NET Core ya no se admite. Para obtener más información, consulte la directiva de compatibilidad de .NET y .NET Core. Para la versión actual, consulte la versión de .NET 9 de este artículo.
Importante
Esta información hace referencia a un producto en versión preliminar, el cual puede sufrir importantes modificaciones antes de que se publique la versión comercial. Microsoft no proporciona ninguna garantía, expresa o implícita, con respecto a la información proporcionada aquí.
Para la versión actual, consulte la versión de .NET 9 de este artículo.
En este artículo se ofrece una introducción a WebSockets en ASP.NET Core. WebSocket (RFC 6455) es un protocolo que habilita canales de comunicación bidireccional persistentes a través de conexiones TCP. Se usa en aplicaciones que sacan partido de comunicaciones rápidas y en tiempo real, como las aplicaciones de chat, panel y juegos.
Consulta o descarga el código de ejemplo (cómo descargarlo, cómo ejecutarlo).
El uso de WebSockets mediante HTTP/2 aprovecha las nuevas características, como las siguientes:
Estas características admitidas están disponibles en Kestrel en todas las plataformas habilitadas para HTTP/2. La negociación de versiones es automática en exploradores y Kestrel, por lo que no se necesitan API nuevas.
En .NET 7 se ha incorporado Websockets mediante la compatibilidad con HTTP/2 para Kestrel, el cliente de JavaScript SignalR y SignalR con Blazor WebAssembly.
Nota
En WebSockets Http/2 se usan solicitudes CONNECT en lugar de GET, por lo que es posible que tenga que actualizar las rutas y los controladores propios. Para obtener más información, consulta Adición de compatibilidad con WebSockets HTTP/2 para controladores existentes en este artículo.
En Chrome y Edge WebSockets HTTP/2 está habilitado de manera predeterminada, y en FireFox se puede habilitar en la página about:config
con la marca network.http.spdy.websockets
.
WebSockets se diseñó originalmente para HTTP/1.1, pero desde entonces se ha adaptado para funcionar en HTTP/2. (RFC 8441)
ASP.NET CoreSignalR es una biblioteca que simplifica la adición de funcionalidad web en tiempo real a las aplicaciones. Usa WebSockets siempre que sea posible.
Para la mayoría de las aplicaciones, se recomienda SignalR en lugar de WebSockets sin procesar. SignalR:
WebSockets sobre HTTP/2 es compatible con:
En algunas aplicaciones, gRPC en .NET proporciona una alternativa a WebSockets.
Agregue el middleware de WebSockets en Program.cs
:
app.UseWebSockets();
Se pueden configurar estas opciones:
var webSocketOptions = new WebSocketOptions
{
KeepAliveInterval = TimeSpan.FromMinutes(2)
};
app.UseWebSockets(webSocketOptions);
En algún momento posterior del ciclo de las solicitudes (más adelante en Program.cs
o en un método de acción, por ejemplo) debe comprobar si se trata de una solicitud WebSocket y aceptarla.
El ejemplo siguiente es de un momento posterior en 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);
}
});
Una solicitud WebSocket puede proceder de cualquier dirección URL, pero este código de ejemplo solo acepta solicitudes de /ws
.
Se puede adoptar un enfoque similar en un método de controlador:
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;
}
}
Cuando se usa un WebSocket, debe mantener la canalización de middleware en ejecución durante la duración de la conexión. Si intenta enviar o recibir un mensaje de WebSocket después de que finalice la canalización de middleware, es posible que obtenga una excepción como la siguiente:
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'.
Si utiliza un servicio en segundo plano para escribir datos en un WebSocket, asegúrese de mantener en ejecución el canal de middleware. Para ello, utilice un TaskCompletionSource<TResult>. Pase TaskCompletionSource
a su servicio de segundo plano y pídale que llame a TrySetResult cuando termine con WebSocket. Después, espere (con await
) la propiedad Task durante la solicitud, como se muestra en el ejemplo siguiente:
app.Run(async (context) =>
{
using var webSocket = await context.WebSockets.AcceptWebSocketAsync();
var socketFinishedTcs = new TaskCompletionSource<object>();
BackgroundSocketProcessor.AddSocket(webSocket, socketFinishedTcs);
await socketFinishedTcs.Task;
});
La excepción de cierre de WebSocket también puede producirse si la devolución de un método de acción ocurre demasiado pronto. Al aceptar un socket en un método de acción, espere a que finalice el código que usa el socket antes de devolver el método de acción.
No use nunca Task.Wait
, Task.Result
ni llamadas de bloqueo similares para esperar a que se complete el socket, ya que pueden causar graves problemas de subprocesamiento. Use siempre await
.
En .NET 7 se ha incorporado Websockets mediante la compatibilidad con HTTP/2 para Kestrel, el cliente de JavaScript SignalR y SignalR con Blazor WebAssembly. En WebSockets HTTP/2 se usan solicitudes CONNECT en lugar de GET. Si anteriormente usó [HttpGet("/path")]
en el método de acción del controlador para las solicitudes de Websocket, actualícelo para usar [Route("/path")]
en su lugar.
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;
}
}
Aviso
La habilitación de la compresión a través de conexiones cifradas puede hacer que una aplicación esté sujeta a ataques CRIME/BREACH.
Si envía información confidencial, evite habilitar la compresión o use WebSocketMessageFlags.DisableCompression
al llamar a WebSocket.SendAsync
.
Esta recomendación se aplica a ambos lados de WebSocket. Tenga en cuenta que la API de WebSockets en el explorador no tiene configuración para deshabilitar la compresión por envío.
Si se quiere la compresión de mensajes a través de WebSockets, el código de aceptación debe especificar que permite la compresión de la siguiente manera:
using (var webSocket = await context.WebSockets.AcceptWebSocketAsync(
new WebSocketAcceptContext { DangerousEnableCompression = true }))
{
}
WebSocketAcceptContext.ServerMaxWindowBits
y WebSocketAcceptContext.DisableServerContextTakeover
son opciones avanzadas que controlan cómo funciona la compresión.
La compresión se negocia entre el cliente y el servidor al establecer por primera vez una conexión. Puede leer más sobre la negociación en Extensiones de compresión para WebSocket RFC.
Nota
Aunque el servidor o el cliente no acepten la negociación de compresión, la conexión se puede establecer. Sin embargo, la conexión no usa compresión al enviar y recibir mensajes.
El método AcceptWebSocketAsync
actualiza la conexión TCP a una conexión de WebSocket y proporciona un objeto WebSocket. Use el objeto WebSocket
para enviar y recibir mensajes.
El código antes mostrado que acepta la solicitud WebSocket pasa el objeto WebSocket
a un método Echo
. El código recibe un mensaje y devuelve inmediatamente el mismo mensaje. Los mensajes se envían y reciben en un bucle hasta que el cliente cierra la conexión:
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);
}
Cuando la conexión WebSocket se acepta antes de que el bucle comience, la canalización de middleware finaliza. Tras cerrar el socket, se desenreda la canalización. Es decir, la solicitud deja de avanzar en la canalización cuando WebSocket se acepta, pero cuando el bucle termina y el socket se cierra, la solicitud vuelve a recorrer la canalización.
No se informa automáticamente al servidor cuando el cliente se desconecta debido a la pérdida de conectividad. El servidor recibe un mensaje de desconexión solo si el cliente lo envía, acción que no se puede realizar si se pierde la conexión a Internet. Si desea realizar alguna acción cuando eso suceda, establezca un tiempo de expiración después de que no se reciba del cliente dentro de un determinado período.
Si el cliente no está siempre enviando mensajes y no quiere que se agote el tiempo de espera solo porque la conexión está inactiva, haga que el cliente utilice un temporizador para enviar un mensaje de ping cada X segundos. En el servidor, si aún no ha llegado un mensaje transcurridos 2*X segundos desde el anterior, termine la conexión e informe de que el cliente se ha desconectado. Espere el doble del intervalo de tiempo esperado para dejar tiempo extra para los retrasos de la red que podrían retener el mensaje de ping.
Las protecciones proporcionadas por CORS no se aplican a WebSockets. Los exploradores no hacen lo siguiente:
Access-Control
al efectuar solicitudes de WebSocket.En cambio, sí que envían el encabezado Origin
al emitir solicitudes de WebSocket. Las aplicaciones deben configurarse para validar estos encabezados a fin de garantizar que solo se permitan los WebSockets procedentes de los orígenes esperados.
Si hospeda su servidor en "https://server.com"" y su cliente en "https://client.com"", agregue "https://client.com"" a la lista AllowedOrigins de WebSockets para comprobarlo.
var webSocketOptions = new WebSocketOptions
{
KeepAliveInterval = TimeSpan.FromMinutes(2)
};
webSocketOptions.AllowedOrigins.Add("https://client.com");
webSocketOptions.AllowedOrigins.Add("https://www.client.com");
app.UseWebSockets(webSocketOptions);
Nota
El encabezado Origin
está controlado por el cliente y, al igual que el encabezado Referer
, se puede falsificar. No use estos encabezados como mecanismo de autenticación.
El protocolo WebSocket se puede usar en Windows Server 2012 o versiones posteriores, y en Windows 8 versiones posteriores con IIS o IIS Express 8 versiones posteriores, pero no para WebSockets sobre HTTP/2.
Nota
WebSocket siempre está habilitado cuando se usa IIS Express.
Para habilitar la compatibilidad con el protocolo WebSocket en Windows Server 2012 o posterior:
Nota
Estos pasos no son necesarios cuando se usa IIS Express
Para habilitar la compatibilidad con el protocolo WebSocket en Windows Server 8 o posterior:
Nota
Estos pasos no son necesarios cuando se usa IIS Express
Si usa la compatibilidad con WebSocket en socket.io en Node.js, deshabilite el módulo WebSocket predeterminado de IIS mediante el elemento webSocket
de web.config o applicationHost.config. Si no se realiza este paso, el módulo WebSocket de IIS intenta controlar la comunicación de WebSocket en lugar de Node.js y la aplicación.
<system.webServer>
<webSocket enabled="false" />
</system.webServer>
La aplicación de ejemplo que acompaña a este artículo es una aplicación de eco. Tiene una página web que realiza las conexiones de WebSocket y el servidor reenvía de vuelta al cliente todos los mensajes que recibe. La aplicación de ejemplo admite WebSockets sobre HTTP/2 cuando se usa un marco de destino con .NET 7 o versiones posteriores.
Ejecute la aplicación:
dotnet run
y vaya a http://localhost:<port>
en un explorador.La página web muestra el estado de la conexión:
Seleccione Connect (Conectar) para enviar una solicitud WebSocket para la URL mostrada. Escriba un mensaje de prueba y seleccione Send (Enviar). Cuando haya terminado, seleccione Close Socket (Cerrar socket). Los informes de la sección Communication Log (Registro de comunicación) informan de cada acción de abrir, enviar y cerrar a medida que se producen.
En este artículo se ofrece una introducción a WebSockets en ASP.NET Core. WebSocket (RFC 6455) es un protocolo que habilita canales de comunicación bidireccional persistentes a través de conexiones TCP. Se usa en aplicaciones que sacan partido de comunicaciones rápidas y en tiempo real, como las aplicaciones de chat, panel y juegos.
Consulta o descarga el código de ejemplo (cómo descargarlo, cómo ejecutarlo).
El uso de WebSockets mediante HTTP/2 aprovecha las nuevas características, como las siguientes:
Estas características admitidas están disponibles en Kestrel en todas las plataformas habilitadas para HTTP/2. La negociación de versiones es automática en exploradores y Kestrel, por lo que no se necesitan API nuevas.
En .NET 7 se ha incorporado Websockets mediante la compatibilidad con HTTP/2 para Kestrel, el cliente de JavaScript SignalR y SignalR con Blazor WebAssembly.
Nota
En WebSockets Http/2 se usan solicitudes CONNECT en lugar de GET, por lo que es posible que tenga que actualizar las rutas y los controladores propios. Para obtener más información, consulta Adición de compatibilidad con WebSockets HTTP/2 para controladores existentes en este artículo.
En Chrome y Edge WebSockets HTTP/2 está habilitado de manera predeterminada, y en FireFox se puede habilitar en la página about:config
con la marca network.http.spdy.websockets
.
WebSockets se diseñó originalmente para HTTP/1.1, pero desde entonces se ha adaptado para funcionar en HTTP/2. (RFC 8441)
ASP.NET CoreSignalR es una biblioteca que simplifica la adición de funcionalidad web en tiempo real a las aplicaciones. Usa WebSockets siempre que sea posible.
Para la mayoría de las aplicaciones, se recomienda SignalR en lugar de WebSockets sin procesar. SignalR:
WebSockets sobre HTTP/2 es compatible con:
En algunas aplicaciones, gRPC en .NET proporciona una alternativa a WebSockets.
Agregue el middleware de WebSockets en Program.cs
:
app.UseWebSockets();
Se pueden configurar estas opciones:
var webSocketOptions = new WebSocketOptions
{
KeepAliveInterval = TimeSpan.FromMinutes(2)
};
app.UseWebSockets(webSocketOptions);
En algún momento posterior del ciclo de las solicitudes (más adelante en Program.cs
o en un método de acción, por ejemplo) debe comprobar si se trata de una solicitud WebSocket y aceptarla.
El ejemplo siguiente es de un momento posterior en 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);
}
});
Una solicitud WebSocket puede proceder de cualquier dirección URL, pero este código de ejemplo solo acepta solicitudes de /ws
.
Se puede adoptar un enfoque similar en un método de controlador:
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;
}
}
Cuando se usa un WebSocket, debe mantener la canalización de middleware en ejecución durante la duración de la conexión. Si intenta enviar o recibir un mensaje de WebSocket después de que finalice la canalización de middleware, es posible que obtenga una excepción como la siguiente:
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'.
Si utiliza un servicio en segundo plano para escribir datos en un WebSocket, asegúrese de mantener en ejecución el canal de middleware. Para ello, utilice un TaskCompletionSource<TResult>. Pase TaskCompletionSource
a su servicio de segundo plano y pídale que llame a TrySetResult cuando termine con WebSocket. Después, espere (con await
) la propiedad Task durante la solicitud, como se muestra en el ejemplo siguiente:
app.Run(async (context) =>
{
using var webSocket = await context.WebSockets.AcceptWebSocketAsync();
var socketFinishedTcs = new TaskCompletionSource<object>();
BackgroundSocketProcessor.AddSocket(webSocket, socketFinishedTcs);
await socketFinishedTcs.Task;
});
La excepción de cierre de WebSocket también puede producirse si la devolución de un método de acción ocurre demasiado pronto. Al aceptar un socket en un método de acción, espere a que finalice el código que usa el socket antes de devolver el método de acción.
No use nunca Task.Wait
, Task.Result
ni llamadas de bloqueo similares para esperar a que se complete el socket, ya que pueden causar graves problemas de subprocesamiento. Use siempre await
.
En .NET 7 se ha incorporado Websockets mediante la compatibilidad con HTTP/2 para Kestrel, el cliente de JavaScript SignalR y SignalR con Blazor WebAssembly. En WebSockets HTTP/2 se usan solicitudes CONNECT en lugar de GET. Si anteriormente usó [HttpGet("/path")]
en el método de acción del controlador para las solicitudes de Websocket, actualícelo para usar [Route("/path")]
en su lugar.
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;
}
}
Aviso
La habilitación de la compresión a través de conexiones cifradas puede hacer que una aplicación esté sujeta a ataques CRIME/BREACH.
Si envía información confidencial, evite habilitar la compresión o use WebSocketMessageFlags.DisableCompression
al llamar a WebSocket.SendAsync
.
Esta recomendación se aplica a ambos lados de WebSocket. Tenga en cuenta que la API de WebSockets en el explorador no tiene configuración para deshabilitar la compresión por envío.
Si se quiere la compresión de mensajes a través de WebSockets, el código de aceptación debe especificar que permite la compresión de la siguiente manera:
using (var webSocket = await context.WebSockets.AcceptWebSocketAsync(
new WebSocketAcceptContext { DangerousEnableCompression = true }))
{
}
WebSocketAcceptContext.ServerMaxWindowBits
y WebSocketAcceptContext.DisableServerContextTakeover
son opciones avanzadas que controlan cómo funciona la compresión.
La compresión se negocia entre el cliente y el servidor al establecer por primera vez una conexión. Puede leer más sobre la negociación en Extensiones de compresión para WebSocket RFC.
Nota
Aunque el servidor o el cliente no acepten la negociación de compresión, la conexión se puede establecer. Sin embargo, la conexión no usa compresión al enviar y recibir mensajes.
El método AcceptWebSocketAsync
actualiza la conexión TCP a una conexión de WebSocket y proporciona un objeto WebSocket. Use el objeto WebSocket
para enviar y recibir mensajes.
El código antes mostrado que acepta la solicitud WebSocket pasa el objeto WebSocket
a un método Echo
. El código recibe un mensaje y devuelve inmediatamente el mismo mensaje. Los mensajes se envían y reciben en un bucle hasta que el cliente cierra la conexión:
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);
}
Cuando la conexión WebSocket se acepta antes de que el bucle comience, la canalización de middleware finaliza. Tras cerrar el socket, se desenreda la canalización. Es decir, la solicitud deja de avanzar en la canalización cuando WebSocket se acepta, pero cuando el bucle termina y el socket se cierra, la solicitud vuelve a recorrer la canalización.
No se informa automáticamente al servidor cuando el cliente se desconecta debido a la pérdida de conectividad. El servidor recibe un mensaje de desconexión solo si el cliente lo envía, acción que no se puede realizar si se pierde la conexión a Internet. Si desea realizar alguna acción cuando eso suceda, establezca un tiempo de expiración después de que no se reciba del cliente dentro de un determinado período.
Si el cliente no está siempre enviando mensajes y no quiere que se agote el tiempo de espera solo porque la conexión está inactiva, haga que el cliente utilice un temporizador para enviar un mensaje de ping cada X segundos. En el servidor, si aún no ha llegado un mensaje transcurridos 2*X segundos desde el anterior, termine la conexión e informe de que el cliente se ha desconectado. Espere el doble del intervalo de tiempo esperado para dejar tiempo extra para los retrasos de la red que podrían retener el mensaje de ping.
Las protecciones proporcionadas por CORS no se aplican a WebSockets. Los exploradores no hacen lo siguiente:
Access-Control
al efectuar solicitudes de WebSocket.En cambio, sí que envían el encabezado Origin
al emitir solicitudes de WebSocket. Las aplicaciones deben configurarse para validar estos encabezados a fin de garantizar que solo se permitan los WebSockets procedentes de los orígenes esperados.
Si hospeda su servidor en "https://server.com"" y su cliente en "https://client.com"", agregue "https://client.com"" a la lista AllowedOrigins de WebSockets para comprobarlo.
var webSocketOptions = new WebSocketOptions
{
KeepAliveInterval = TimeSpan.FromMinutes(2)
};
webSocketOptions.AllowedOrigins.Add("https://client.com");
webSocketOptions.AllowedOrigins.Add("https://www.client.com");
app.UseWebSockets(webSocketOptions);
Nota
El encabezado Origin
está controlado por el cliente y, al igual que el encabezado Referer
, se puede falsificar. No use estos encabezados como mecanismo de autenticación.
El protocolo WebSocket se puede usar en Windows Server 2012 o versiones posteriores, y en Windows 8 versiones posteriores con IIS o IIS Express 8 versiones posteriores, pero no para WebSockets sobre HTTP/2.
Nota
WebSocket siempre está habilitado cuando se usa IIS Express.
Para habilitar la compatibilidad con el protocolo WebSocket en Windows Server 2012 o posterior:
Nota
Estos pasos no son necesarios cuando se usa IIS Express
Para habilitar la compatibilidad con el protocolo WebSocket en Windows Server 8 o posterior:
Nota
Estos pasos no son necesarios cuando se usa IIS Express
Si usa la compatibilidad con WebSocket en socket.io en Node.js, deshabilite el módulo WebSocket predeterminado de IIS mediante el elemento webSocket
de web.config o applicationHost.config. Si no se realiza este paso, el módulo WebSocket de IIS intenta controlar la comunicación de WebSocket en lugar de Node.js y la aplicación.
<system.webServer>
<webSocket enabled="false" />
</system.webServer>
La aplicación de ejemplo que acompaña a este artículo es una aplicación de eco. Tiene una página web que realiza las conexiones de WebSocket y el servidor reenvía de vuelta al cliente todos los mensajes que recibe. La aplicación de ejemplo admite WebSockets sobre HTTP/2 cuando se usa un marco de destino con .NET 7 o versiones posteriores.
Ejecute la aplicación:
dotnet run
y vaya a http://localhost:<port>
en un explorador.La página web muestra el estado de la conexión:
Seleccione Connect (Conectar) para enviar una solicitud WebSocket para la URL mostrada. Escriba un mensaje de prueba y seleccione Send (Enviar). Cuando haya terminado, seleccione Close Socket (Cerrar socket). Los informes de la sección Communication Log (Registro de comunicación) informan de cada acción de abrir, enviar y cerrar a medida que se producen.
En este artículo se ofrece una introducción a WebSockets en ASP.NET Core. WebSocket (RFC 6455) es un protocolo que habilita canales de comunicación bidireccional persistentes a través de conexiones TCP. Se usa en aplicaciones que sacan partido de comunicaciones rápidas y en tiempo real, como las aplicaciones de chat, panel y juegos.
Consulta o descarga el código de ejemplo (cómo descargarlo, cómo ejecutarlo).
ASP.NET CoreSignalR es una biblioteca que simplifica la adición de funcionalidad web en tiempo real a las aplicaciones. Usa WebSockets siempre que sea posible.
Para la mayoría de las aplicaciones, se recomienda SignalR en lugar de WebSockets sin procesar. SignalR proporciona transporte de reserva para entornos donde WebSockets no está disponible. También proporciona un modelo básico de aplicación de llamada a procedimiento remoto. Además, en la mayoría de los escenarios, SignalR no tiene ninguna desventaja significativa de rendimiento en comparación con WebSockets sin procesar.
En algunas aplicaciones, gRPC en .NET proporciona una alternativa a WebSockets.
Agregue el middleware de WebSockets en Program.cs
:
app.UseWebSockets();
Se pueden configurar estas opciones:
var webSocketOptions = new WebSocketOptions
{
KeepAliveInterval = TimeSpan.FromMinutes(2)
};
app.UseWebSockets(webSocketOptions);
En algún momento posterior del ciclo de las solicitudes (más adelante en Program.cs
o en un método de acción, por ejemplo) debe comprobar si se trata de una solicitud WebSocket y aceptarla.
El ejemplo siguiente es de un momento posterior en 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);
}
});
Una solicitud WebSocket puede proceder de cualquier dirección URL, pero este código de ejemplo solo acepta solicitudes de /ws
.
Se puede adoptar un enfoque similar en un método de controlador:
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;
}
}
Cuando se usa un WebSocket, debe mantener la canalización de middleware en ejecución durante la duración de la conexión. Si intenta enviar o recibir un mensaje de WebSocket después de que finalice la canalización de middleware, es posible que obtenga una excepción como la siguiente:
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'.
Si utiliza un servicio en segundo plano para escribir datos en un WebSocket, asegúrese de mantener en ejecución el canal de middleware. Para ello, utilice un TaskCompletionSource<TResult>. Pase TaskCompletionSource
a su servicio de segundo plano y pídale que llame a TrySetResult cuando termine con WebSocket. Después, espere (con await
) la propiedad Task durante la solicitud, como se muestra en el ejemplo siguiente:
app.Run(async (context) =>
{
using var webSocket = await context.WebSockets.AcceptWebSocketAsync();
var socketFinishedTcs = new TaskCompletionSource<object>();
BackgroundSocketProcessor.AddSocket(webSocket, socketFinishedTcs);
await socketFinishedTcs.Task;
});
La excepción de cierre de WebSocket también puede producirse si la devolución de un método de acción ocurre demasiado pronto. Al aceptar un socket en un método de acción, espere a que finalice el código que usa el socket antes de devolver el método de acción.
No use nunca Task.Wait
, Task.Result
ni llamadas de bloqueo similares para esperar a que se complete el socket, ya que pueden causar graves problemas de subprocesamiento. Use siempre await
.
Aviso
La habilitación de la compresión a través de conexiones cifradas puede hacer que una aplicación esté sujeta a ataques CRIME/BREACH.
Si envía información confidencial, evite habilitar la compresión o use WebSocketMessageFlags.DisableCompression
al llamar a WebSocket.SendAsync
.
Esta recomendación se aplica a ambos lados de WebSocket. Tenga en cuenta que la API de WebSockets en el explorador no tiene configuración para deshabilitar la compresión por envío.
Si se quiere la compresión de mensajes a través de WebSockets, el código de aceptación debe especificar que permite la compresión de la siguiente manera:
using (var webSocket = await context.WebSockets.AcceptWebSocketAsync(
new WebSocketAcceptContext { DangerousEnableCompression = true }))
{
}
WebSocketAcceptContext.ServerMaxWindowBits
y WebSocketAcceptContext.DisableServerContextTakeover
son opciones avanzadas que controlan cómo funciona la compresión.
La compresión se negocia entre el cliente y el servidor al establecer por primera vez una conexión. Puede leer más sobre la negociación en Extensiones de compresión para WebSocket RFC.
Nota
Aunque el servidor o el cliente no acepten la negociación de compresión, la conexión se puede establecer. Sin embargo, la conexión no usa compresión al enviar y recibir mensajes.
El método AcceptWebSocketAsync
actualiza la conexión TCP a una conexión de WebSocket y proporciona un objeto WebSocket. Use el objeto WebSocket
para enviar y recibir mensajes.
El código antes mostrado que acepta la solicitud WebSocket pasa el objeto WebSocket
a un método Echo
. El código recibe un mensaje y devuelve inmediatamente el mismo mensaje. Los mensajes se envían y reciben en un bucle hasta que el cliente cierra la conexión:
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);
}
Cuando la conexión WebSocket se acepta antes de que el bucle comience, la canalización de middleware finaliza. Tras cerrar el socket, se desenreda la canalización. Es decir, la solicitud deja de avanzar en la canalización cuando WebSocket se acepta, pero cuando el bucle termina y el socket se cierra, la solicitud vuelve a recorrer la canalización.
No se informa automáticamente al servidor cuando el cliente se desconecta debido a la pérdida de conectividad. El servidor recibe un mensaje de desconexión solo si el cliente lo envía, acción que no se puede realizar si se pierde la conexión a Internet. Si desea realizar alguna acción cuando eso suceda, establezca un tiempo de expiración después de que no se reciba del cliente dentro de un determinado período.
Si el cliente no está siempre enviando mensajes y no quiere que se agote el tiempo de espera solo porque la conexión está inactiva, haga que el cliente utilice un temporizador para enviar un mensaje de ping cada X segundos. En el servidor, si aún no ha llegado un mensaje transcurridos 2*X segundos desde el anterior, termine la conexión e informe de que el cliente se ha desconectado. Espere el doble del intervalo de tiempo esperado para dejar tiempo extra para los retrasos de la red que podrían retener el mensaje de ping.
Las protecciones proporcionadas por CORS no se aplican a WebSockets. Los exploradores no hacen lo siguiente:
Access-Control
al efectuar solicitudes de WebSocket.En cambio, sí que envían el encabezado Origin
al emitir solicitudes de WebSocket. Las aplicaciones deben configurarse para validar estos encabezados a fin de garantizar que solo se permitan los WebSockets procedentes de los orígenes esperados.
Si hospeda su servidor en "https://server.com"" y su cliente en "https://client.com"", agregue "https://client.com"" a la lista AllowedOrigins de WebSockets para comprobarlo.
var webSocketOptions = new WebSocketOptions
{
KeepAliveInterval = TimeSpan.FromMinutes(2)
};
webSocketOptions.AllowedOrigins.Add("https://client.com");
webSocketOptions.AllowedOrigins.Add("https://www.client.com");
app.UseWebSockets(webSocketOptions);
Nota
El encabezado Origin
está controlado por el cliente y, al igual que el encabezado Referer
, se puede falsificar. No use estos encabezados como mecanismo de autenticación.
El protocolo WebSocket se puede usar en Windows Server 2012 o posterior, y en Windows 8 o posterior con IIS o IIS Express 8 o posterior.
Nota
WebSocket siempre está habilitado cuando se usa IIS Express.
Para habilitar la compatibilidad con el protocolo WebSocket en Windows Server 2012 o posterior:
Nota
Estos pasos no son necesarios cuando se usa IIS Express
Para habilitar la compatibilidad con el protocolo WebSocket en Windows Server 8 o posterior:
Nota
Estos pasos no son necesarios cuando se usa IIS Express
Si usa la compatibilidad con WebSocket en socket.io en Node.js, deshabilite el módulo WebSocket predeterminado de IIS mediante el elemento webSocket
de web.config o applicationHost.config. Si no se realiza este paso, el módulo WebSocket de IIS intenta controlar la comunicación de WebSocket en lugar de Node.js y la aplicación.
<system.webServer>
<webSocket enabled="false" />
</system.webServer>
La aplicación de ejemplo que acompaña a este artículo es una aplicación de eco. Tiene una página web que realiza las conexiones de WebSocket y el servidor reenvía de vuelta al cliente todos los mensajes que recibe. La aplicación de ejemplo no está configurada para ejecutarse desde Visual Studio con IIS Express, por lo que debe ejecutarla en un shell de comandos con dotnet run
e ir a http://localhost:<port>
en un explorador. La página web muestra el estado de la conexión:
Seleccione Connect (Conectar) para enviar una solicitud WebSocket para la URL mostrada. Escriba un mensaje de prueba y seleccione Send (Enviar). Cuando haya terminado, seleccione Close Socket (Cerrar socket). Los informes de la sección Communication Log (Registro de comunicación) informan de cada acción de abrir, enviar y cerrar a medida que se producen.
En este artículo se ofrece una introducción a WebSockets en ASP.NET Core. WebSocket (RFC 6455) es un protocolo que habilita canales de comunicación bidireccional persistentes a través de conexiones TCP. Se usa en aplicaciones que sacan partido de comunicaciones rápidas y en tiempo real, como las aplicaciones de chat, panel y juegos.
Vea o descargue el código de ejemplo (cómo descargarlo). Cómo ejecutar.
ASP.NET CoreSignalR es una biblioteca que simplifica la adición de funcionalidad web en tiempo real a las aplicaciones. Usa WebSockets siempre que sea posible.
Para la mayoría de las aplicaciones, se recomienda SignalR en lugar de WebSockets sin procesar. SignalR proporciona transporte de reserva para entornos donde WebSockets no está disponible. También proporciona un modelo básico de aplicación de llamada a procedimiento remoto. Además, en la mayoría de los escenarios, SignalR no tiene ninguna desventaja significativa de rendimiento en comparación con WebSockets sin procesar.
En algunas aplicaciones, gRPC en .NET proporciona una alternativa a WebSockets.
Agregue el middleware de WebSockets al método Configure
de la clase Startup
:
app.UseWebSockets();
Nota
Si quiere aceptar las solicitudes de WebSocket en un controlador, la llamada a app.UseWebSockets
debe producirse antes de app.UseEndpoints
.
Se pueden configurar estas opciones:
var webSocketOptions = new WebSocketOptions()
{
KeepAliveInterval = TimeSpan.FromSeconds(120),
};
app.UseWebSockets(webSocketOptions);
En algún momento posterior del ciclo de solicitudes (más adelante en el método Configure
o en un método de acción, por ejemplo) debe comprobar si se trata de una solicitud WebSocket y aceptarla.
El siguiente ejemplo se corresponde con un momento más adelante en el método 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();
}
});
Una solicitud WebSocket puede proceder de cualquier dirección URL, pero este código de ejemplo solo acepta solicitudes de /ws
.
Se puede adoptar un enfoque similar en un método de controlador:
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;
}
}
Cuando se usa un WebSocket, debe mantener la canalización de middleware en ejecución durante la duración de la conexión. Si intenta enviar o recibir un mensaje de WebSocket después de que finalice la canalización de middleware, es posible que obtenga una excepción como la siguiente:
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'.
Si utiliza un servicio en segundo plano para escribir datos en un WebSocket, asegúrese de mantener en ejecución el canal de middleware. Para ello, utilice un TaskCompletionSource<TResult>. Pase TaskCompletionSource
a su servicio de segundo plano y pídale que llame a TrySetResult cuando termine con WebSocket. Después, espere (con await
) la propiedad Task durante la solicitud, como se muestra en el ejemplo siguiente:
app.Use(async (context, next) =>
{
using (WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync())
{
var socketFinishedTcs = new TaskCompletionSource<object>();
BackgroundSocketProcessor.AddSocket(webSocket, socketFinishedTcs);
await socketFinishedTcs.Task;
}
});
La excepción de cierre de WebSocket también puede producirse si la devolución de un método de acción ocurre demasiado pronto. Al aceptar un socket en un método de acción, espere a que finalice el código que usa el socket antes de devolver el método de acción.
No use nunca Task.Wait
, Task.Result
ni llamadas de bloqueo similares para esperar a que se complete el socket, ya que pueden causar graves problemas de subprocesamiento. Use siempre await
.
El método AcceptWebSocketAsync
actualiza la conexión TCP a una conexión de WebSocket y proporciona un objeto WebSocket. Use el objeto WebSocket
para enviar y recibir mensajes.
El código antes mostrado que acepta la solicitud WebSocket pasa el objeto WebSocket
a un método Echo
. El código recibe un mensaje y devuelve inmediatamente el mismo mensaje. Los mensajes se envían y reciben en un bucle hasta que el cliente cierra la conexión:
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);
}
Cuando la conexión WebSocket se acepta antes de que el bucle comience, la canalización de middleware finaliza. Tras cerrar el socket, se desenreda la canalización. Es decir, la solicitud deja de avanzar en la canalización cuando WebSocket se acepta, pero cuando el bucle termina y el socket se cierra, la solicitud vuelve a recorrer la canalización.
No se informa automáticamente al servidor cuando el cliente se desconecta debido a la pérdida de conectividad. El servidor recibe un mensaje de desconexión solo si el cliente lo envía, acción que no se puede realizar si se pierde la conexión a Internet. Si desea realizar alguna acción cuando eso suceda, establezca un tiempo de expiración después de que no se reciba del cliente dentro de un determinado período.
Si el cliente no está siempre enviando mensajes y no quiere que se agote el tiempo de espera solo porque la conexión está inactiva, haga que el cliente utilice un temporizador para enviar un mensaje de ping cada X segundos. En el servidor, si aún no ha llegado un mensaje transcurridos 2*X segundos desde el anterior, termine la conexión e informe de que el cliente se ha desconectado. Espere el doble del intervalo de tiempo esperado para dejar tiempo extra para los retrasos de la red que podrían retener el mensaje de ping.
Nota
El ManagedWebSocket
interno controla los fotogramas Ping/Pong implícitamente para mantener activa la conexión si la opción KeepAliveInterval
es mayor que cero, que tiene como valor predeterminado 30 segundos (TimeSpan.FromSeconds(30)
).
Las protecciones proporcionadas por CORS no se aplican a WebSockets. Los exploradores no hacen lo siguiente:
Access-Control
al efectuar solicitudes de WebSocket.En cambio, sí que envían el encabezado Origin
al emitir solicitudes de WebSocket. Las aplicaciones deben configurarse para validar estos encabezados a fin de garantizar que solo se permitan los WebSockets procedentes de los orígenes esperados.
Si hospeda su servidor en "https://server.com"" y su cliente en "https://client.com"", agregue "https://client.com"" a la lista AllowedOrigins de WebSockets para comprobarlo.
var webSocketOptions = new WebSocketOptions()
{
KeepAliveInterval = TimeSpan.FromSeconds(120),
};
webSocketOptions.AllowedOrigins.Add("https://client.com");
webSocketOptions.AllowedOrigins.Add("https://www.client.com");
app.UseWebSockets(webSocketOptions);
Nota
El encabezado Origin
está controlado por el cliente y, al igual que el encabezado Referer
, se puede falsificar. No use estos encabezados como mecanismo de autenticación.
El protocolo WebSocket se puede usar en Windows Server 2012 o posterior, y en Windows 8 o posterior con IIS o IIS Express 8 o posterior.
Nota
WebSocket siempre está habilitado cuando se usa IIS Express.
Para habilitar la compatibilidad con el protocolo WebSocket en Windows Server 2012 o posterior:
Nota
Estos pasos no son necesarios cuando se usa IIS Express
Para habilitar la compatibilidad con el protocolo WebSocket en Windows Server 8 o posterior:
Nota
Estos pasos no son necesarios cuando se usa IIS Express
Si usa la compatibilidad con WebSocket en socket.io en Node.js, deshabilite el módulo WebSocket predeterminado de IIS mediante el elemento webSocket
de web.config o applicationHost.config. Si no se realiza este paso, el módulo WebSocket de IIS intenta controlar la comunicación de WebSocket en lugar de Node.js y la aplicación.
<system.webServer>
<webSocket enabled="false" />
</system.webServer>
La aplicación de ejemplo que acompaña a este artículo es una aplicación de eco. Tiene una página web que realiza las conexiones de WebSocket y el servidor reenvía de vuelta al cliente todos los mensajes que recibe. La aplicación de ejemplo no está configurada para ejecutarse desde Visual Studio con IIS Express, por lo que debe ejecutarla en un shell de comandos con dotnet run
e ir a http://localhost:5000
en un explorador. La página web muestra el estado de la conexión:
Seleccione Connect (Conectar) para enviar una solicitud WebSocket para la URL mostrada. Escriba un mensaje de prueba y seleccione Send (Enviar). Cuando haya terminado, seleccione Close Socket (Cerrar socket). Los informes de la sección Communication Log (Registro de comunicación) informan de cada acción de abrir, enviar y cerrar a medida que se producen.
Comentarios de ASP.NET Core
ASP.NET Core é un proxecto de código aberto. Selecciona unha ligazón para ofrecer comentarios:
Evento
Campionato do Mundo de Power BI DataViz
Feb 14, 4 PM - Mar 31, 4 PM
Con 4 posibilidades de entrar, poderías gañar un paquete de conferencias e facelo ao Live Grand Finale en Las Vegas
Máis información