Suporte ao WebSockets no ASP.NET Core

Este artigo explica como começar a usar o WebSockets no ASP.NET Core. WebSocket (RFC 6455) é um protocolo que permite canais de comunicação persistentes bidirecionais em conexões TCP. Ele é usado em aplicativos que se beneficiam de comunicação rápida e em tempo real, como chat, painel e aplicativos de jogos.

Exibir ou fazer download do código de exemplo (como fazer download, como executar).

Suporte a WebSockets HTTP/2

O uso de WebSockets por HTTP/2 aproveita novos recursos, como:

  • Compactação de cabeçalho.
  • Multiplexação, que reduz o tempo e os recursos necessários ao fazer várias solicitações ao servidor.

Esses recursos com suporte estão disponíveis no Kestrel em todas as plataformas habilitadas para HTTP/2. A negociação de versão é automática em navegadores e no Kestrel, ou seja, nenhuma nova API é necessária.

O .NET 7 apresentou suporte do Websockets por HTTP/2 a Kestrel, cliente JavaScript SignalR e SignalR com Blazor WebAssembly.

Observação

WebSockets HTTP/2 usam solicitações CONNECT em vez de GET, portanto, suas próprias rotas e controladores podem precisar de atualização. Para obter mais informações, consulte Adicionar suporte a WebSockets HTTP/2 para controladores existentes neste artigo.

O Chrome e o Edge têm WebSockets HTTP/2 habilitados por padrão e você pode habilitá-los no FireFox na página about:config com o sinalizador network.http.spdy.websockets.

WebSockets foram originalmente projetados para HTTP/1.1, mas desde então foram adaptados para funcionar via HTTP/2. (RFC 8441)

SignalR

ASP.NET Core SignalR é uma biblioteca que simplifica a adição da funcionalidade da Web em tempo real aos aplicativos. Ele usa WebSockets sempre que possível.

Para a maioria dos aplicativos, recomendamos o SignalR ao invés de WebSockets brutos. SignalR:

  • Fornece o fallback de transporte para os ambientes em que o WebSocket não está disponível.
  • Fornece um modelo de aplicativo de chamada de procedimento remoto básico.
  • Não tem nenhuma desvantagem de desempenho significativa em comparação ao uso de WebSockets brutos, na maioria dos cenários.

Há suporte para WebSockets via HTTP/2 para:

  • ASP.NET Core SignalR Cliente JavaScript
  • ASP.NET Core SignalR com Blazor WebAssembly

Para alguns aplicativos, o gRPC no .NET fornece uma alternativa para WebSockets.

Pré-requisitos

  • Qualquer sistema operacional compatível com o ASP.NET Core:
    • Windows 7/Windows Server 2008 ou posterior
    • Linux
    • macOS
  • Se o aplicativo é executado no Windows com o IIS:
    • Windows 8/Windows Server 2012 ou posterior
    • IIS 8/IIS 8 Express
    • WebSockets devem estar habilitados. Consulte a seção Suporte do IIS/IIS Express.
  • Se o aplicativo é executado no HTTP.sys:
    • Windows 8/Windows Server 2012 ou posterior
  • Para saber quais são os navegadores compatíveis, consulte Posso usar.

Configurar o middleware

Adicione o middleware de WebSockets em Program.cs:

app.UseWebSockets();

As seguintes configurações podem ser definidas:

  • KeepAliveInterval – a frequência para enviar quadros "ping" ao cliente para garantir que os proxies mantenham a conexão aberta. O padrão é dois minutos.
  • AllowedOrigins – Uma lista de valores de cabeçalho de origem permitidos para solicitações do WebSocket. Por padrão, todas as origens são permitidas. Para obter mais informações, consulte Restrição de origem do WebSocket neste artigo.
var webSocketOptions = new WebSocketOptions
{
    KeepAliveInterval = TimeSpan.FromMinutes(2)
};

app.UseWebSockets(webSocketOptions);

Aceitar solicitações do WebSocket

Futuramente no ciclo de vida da solicitação (mais tarde em Program.cs ou em um método de ação, por exemplo), verifique se é uma solicitação do WebSocket e aceite-a.

O exemplo a seguir é de uma fase posterior em 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);
    }

});

Uma solicitação do WebSocket pode entrar em qualquer URL, mas esse código de exemplo aceita apenas solicitações de /ws.

Uma abordagem semelhante pode ser adotada em um 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;
        }
    }

Ao usar um WebSocket, você deve manter o pipeline de middleware em execução durante a conexão. Se tentar enviar ou receber uma mensagem do WebSocket após o término do pipeline de middleware, você poderá receber uma exceção semelhante à seguinte:

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'.

Se você estiver usando um serviço em segundo plano para gravar dados em um WebSocket, mantenha o pipeline do middleware em execução. Faça isso usando um TaskCompletionSource<TResult>. Passe o TaskCompletionSource ao seu serviço em segundo plano e faça-o chamar TrySetResult quando terminar com o WebSocket. Em seguida await a propriedade Task durante a solicitação, conforme é mostrado no exemplo a seguir:

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

    BackgroundSocketProcessor.AddSocket(webSocket, socketFinishedTcs);

    await socketFinishedTcs.Task;
});

A exceção fechada do WebSocket também poderá ocorrer ao retornar muito rapidamente de um método de ação. Ao aceitar um soquete de um método de ação, aguarde até que o código que usa o soquete seja concluído antes de retornar do método de ação.

Nunca use Task.Wait, Task.Result ou chamadas de bloqueio semelhantes para aguardar a conclusão do soquete, pois isso pode causar sérios problemas de threading. Sempre use await.

Adicionar suporte a WebSockets HTTP/2 para controladores existentes

O .NET 7 apresentou suporte do Websockets por HTTP/2 a Kestrel, cliente JavaScript SignalR e SignalR com Blazor WebAssembly. WebSockets HTTP/2 usam solicitações CONNECT em vez de GET. Se você usou [HttpGet("/path")] anteriormente em seu método de ação do controlador para solicitações Websocket, atualize-o para usar [Route("/path")] em vez disso.

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;
        }
    }

Compactação

Aviso

Habilitar a compactação em conexões criptografadas pode tornar um aplicativo sujeito a ataques CRIME/BREACH. Se estiver enviando informações confidenciais, evite habilitar a compactação ou use WebSocketMessageFlags.DisableCompression ao chamar WebSocket.SendAsync. Isso se aplica a ambos os lados do WebSocket. Observe que a API dos WebSockets no navegador não tem configuração para desabilitar a compactação por envio.

Caso deseje fazer a compactação de mensagens por WebSockets, o código de aceitação deverá especificar que ele permite a compactação da seguinte maneira:

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

}

WebSocketAcceptContext.ServerMaxWindowBits e WebSocketAcceptContext.DisableServerContextTakeover são opções avançadas que controlam como a compactação funciona.

A compactação é negociada entre o cliente e o servidor ao estabelecer uma conexão pela primeira vez. Você pode ler mais sobre a negociação em Extensões de Compactação para WebSocket RFC.

Observação

Se a negociação de compactação não for aceita pelo servidor ou pelo cliente, a conexão ainda será estabelecida. No entanto, a conexão não usa compactação ao enviar e receber mensagens.

Enviar e receber mensagens

O método AcceptWebSocketAsync atualiza a conexão TCP para uma conexão WebSocket e fornece um objeto WebSocket. Use o objeto WebSocket para enviar e receber mensagens.

O código mostrado anteriormente que aceita a solicitação do WebSocket passa o objeto WebSocket para um método Echo. O código recebe uma mensagem e envia de volta imediatamente a mesma mensagem. As mensagens são enviadas e recebidas em um loop até que o cliente feche a conexão:

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);
}

Ao aceitar a conexão WebSocket antes de iniciar o loop, o pipeline de middleware é encerrado. Ao fechar o soquete, o pipeline é desenrolado. Ou seja, a solicitação deixa de avançar no pipeline quando o WebSocket é aceito. Quando o loop é concluído e o soquete é fechado, a solicitação continua a avançar no pipeline.

Tratar desconexões de cliente

O servidor não é informado automaticamente quando o cliente se desconecta devido à perda de conectividade. O servidor recebe uma mensagem de desconexão apenas quando o cliente a envia, e isso é possível somente quando há conexão com a Internet. Se quiser tomar alguma medida quando isso ocorrer, defina um tempo limite, caso não receba nada do cliente após uma determinada janela de tempo.

Se o cliente não envia mensagens com frequência, e você não deseja atingir um tempo limite apenas porque a conexão fica ociosa, peça ao cliente para usar um temporizador a fim de enviar uma mensagem de ping a cada X segundos. No servidor, se uma mensagem não chegar dentro de 2*X segundos após a mensagem anterior, encerre a conexão e informe que o cliente se desconectou. Aguarde o dobro do intervalo de tempo esperado a fim de deixar um tempo extra para atrasos na rede, que possam atrasar a mensagem de ping.

Restrição de origem do WebSocket

As proteções fornecidas pelo CORS não se aplicam ao WebSockets. Navegadores não:

  • Executam solicitações de simulação de CORS.
  • Respeitam as restrições especificadas em cabeçalhos Access-Control ao fazer solicitações de WebSocket.

No entanto, os navegadores enviam o cabeçalho Origin ao emitir solicitações de WebSocket. Os aplicativos devem ser configurados para validar esses cabeçalhos e garantir que apenas WebSockets provenientes de origens esperadas sejam permitidos.

Se você estiver hospedando o servidor em "https://server.com" e hospedando seu cliente em "https://client.com", adicione "https://client.com" à lista AllowedOrigins para que seja feita a verificação pelos WebSockets.

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

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

app.UseWebSockets(webSocketOptions);

Observação

O cabeçalho Origin é controlado pelo cliente e, como o cabeçalho Referer, pode ser falsificado. Não use esses cabeçalhos como um mecanismo de autenticação.

Suporte ao IIS/IIS Express

O Windows Server 2012 ou posterior e o Windows 8 ou posterior com o IIS/IIS Express 8 ou posterior são compatíveis com o protocolo WebSocket, mas não para WebSockets via HTTP/2.

Observação

WebSockets estão sempre habilitados ao usar o IIS Express.

Habilitar WebSockets no IIS

Para habilitar o suporte para o protocolo WebSocket no Windows Server 2012 ou posterior:

Observação

Estas etapas não são necessárias ao usar o IIS Express

  1. Use o assistente Adicionar Funções e Recursos por meio do menu Gerenciar ou do link no Gerenciador do Servidor.
  2. Selecione Instalação baseada em função ou em recurso. Selecione Avançar.
  3. Selecione o servidor apropriado (o servidor local é selecionado por padrão). Selecione Avançar.
  4. Expanda Servidor Web (IIS) na árvore Funções, expanda Servidor Web e, em seguida, expanda Desenvolvimento de Aplicativos.
  5. Selecione o Protocolo WebSocket. Selecione Avançar.
  6. Se não forem necessários recursos adicionais, selecione Avançar.
  7. Selecione Instalar.
  8. Quando a instalação for concluída, selecione Fechar para sair do assistente.

Para habilitar o suporte para o protocolo WebSocket no Windows 8 ou posterior:

Observação

Estas etapas não são necessárias ao usar o IIS Express

  1. Navegue até Painel de Controle>Programas>Programas e Recursos>Ativar ou desativar recursos do Windows (lado esquerdo da tela).
  2. Abra os nós a seguir: Serviços de Informações da Internet>Serviços da World Wide Web>Recursos de Desenvolvimento de Aplicativos.
  3. Selecione o recurso Protocolo WebSocket. Selecione OK.

Desabilite o WebSocket ao usar o socket.io no Node.js

Se estiver usando o suporte para WebSocket em socket.io no Node.js, desabilite o módulo WebSocket do IIS padrão usando o elemento webSocket em web.config ou applicationHost.config. Se essa etapa não for executada, o módulo WebSocket do IIS tentará lidar com a comunicação do WebSocket em vez do Node.js e do aplicativo.

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

Aplicativo de exemplo

O aplicativo de exemplo que acompanha este artigo é um aplicativo de eco. Ele tem uma página da Web que faz conexões do WebSocket e o servidor reenvia para o cliente todas as mensagens recebidas. O aplicativo de amostra dá suporte a WebSockets via HTTP/2 ao usar uma estrutura de destino do .NET 7 ou posterior.

Executar o aplicativo:

  • Para executar o aplicativo no Visual Studio: abra o projeto de amostra no Visual Studio e pressione Ctrl+F5 para ser executado sem o depurador.
  • Para executar o aplicativo em um shell de comando: execute o comando dotnet run e navegue em um navegador até http://localhost:<port>.

A página da Web mostra o status de conexão:

Estado inicial da página da Web antes da conexão dos WebSockets

Selecione Conectar para enviar uma solicitação WebSocket para a URL exibida. Insira uma mensagem de teste e selecione Enviar. Quando terminar, selecione Fechar Soquete. A seção Log de Comunicação relata cada ação de abertura, envio e fechamento conforme ela ocorre.

Estado final da página da Web depois que as mensagens de conexão e de teste dos WebSockets são enviadas e recebidas

Este artigo explica como começar a usar o WebSockets no ASP.NET Core. WebSocket (RFC 6455) é um protocolo que permite canais de comunicação persistentes bidirecionais em conexões TCP. Ele é usado em aplicativos que se beneficiam de comunicação rápida e em tempo real, como chat, painel e aplicativos de jogos.

Exibir ou fazer download do código de exemplo (como fazer download, como executar).

Suporte a WebSockets HTTP/2

O uso de WebSockets por HTTP/2 aproveita novos recursos, como:

  • Compactação de cabeçalho.
  • Multiplexação, que reduz o tempo e os recursos necessários ao fazer várias solicitações ao servidor.

Esses recursos com suporte estão disponíveis no Kestrel em todas as plataformas habilitadas para HTTP/2. A negociação de versão é automática em navegadores e no Kestrel, ou seja, nenhuma nova API é necessária.

O .NET 7 apresentou suporte do Websockets por HTTP/2 a Kestrel, cliente JavaScript SignalR e SignalR com Blazor WebAssembly.

Observação

WebSockets HTTP/2 usam solicitações CONNECT em vez de GET, portanto, suas próprias rotas e controladores podem precisar de atualização. Para obter mais informações, consulte Adicionar suporte a WebSockets HTTP/2 para controladores existentes neste artigo.

O Chrome e o Edge têm WebSockets HTTP/2 habilitados por padrão e você pode habilitá-los no FireFox na página about:config com o sinalizador network.http.spdy.websockets.

WebSockets foram originalmente projetados para HTTP/1.1, mas desde então foram adaptados para funcionar via HTTP/2. (RFC 8441)

SignalR

ASP.NET Core SignalR é uma biblioteca que simplifica a adição da funcionalidade da Web em tempo real aos aplicativos. Ele usa WebSockets sempre que possível.

Para a maioria dos aplicativos, recomendamos o SignalR ao invés de WebSockets brutos. SignalR:

  • Fornece o fallback de transporte para os ambientes em que o WebSocket não está disponível.
  • Fornece um modelo de aplicativo de chamada de procedimento remoto básico.
  • Não tem nenhuma desvantagem de desempenho significativa em comparação ao uso de WebSockets brutos, na maioria dos cenários.

Há suporte para WebSockets via HTTP/2 para:

  • ASP.NET Core SignalR Cliente JavaScript
  • ASP.NET Core SignalR com Blazor WebAssembly

Para alguns aplicativos, o gRPC no .NET fornece uma alternativa para WebSockets.

Pré-requisitos

  • Qualquer sistema operacional compatível com o ASP.NET Core:
    • Windows 7/Windows Server 2008 ou posterior
    • Linux
    • macOS
  • Se o aplicativo é executado no Windows com o IIS:
    • Windows 8/Windows Server 2012 ou posterior
    • IIS 8/IIS 8 Express
    • WebSockets devem estar habilitados. Consulte a seção Suporte do IIS/IIS Express.
  • Se o aplicativo é executado no HTTP.sys:
    • Windows 8/Windows Server 2012 ou posterior
  • Para saber quais são os navegadores compatíveis, consulte Posso usar.

Configurar o middleware

Adicione o middleware de WebSockets em Program.cs:

app.UseWebSockets();

As seguintes configurações podem ser definidas:

  • KeepAliveInterval – a frequência para enviar quadros "ping" ao cliente para garantir que os proxies mantenham a conexão aberta. O padrão é dois minutos.
  • AllowedOrigins – Uma lista de valores de cabeçalho de origem permitidos para solicitações do WebSocket. Por padrão, todas as origens são permitidas. Para obter mais informações, consulte Restrição de origem do WebSocket neste artigo.
var webSocketOptions = new WebSocketOptions
{
    KeepAliveInterval = TimeSpan.FromMinutes(2)
};

app.UseWebSockets(webSocketOptions);

Aceitar solicitações do WebSocket

Futuramente no ciclo de vida da solicitação (mais tarde em Program.cs ou em um método de ação, por exemplo), verifique se é uma solicitação do WebSocket e aceite-a.

O exemplo a seguir é de uma fase posterior em 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);
    }

});

Uma solicitação do WebSocket pode entrar em qualquer URL, mas esse código de exemplo aceita apenas solicitações de /ws.

Uma abordagem semelhante pode ser adotada em um 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;
        }
    }

Ao usar um WebSocket, você deve manter o pipeline de middleware em execução durante a conexão. Se tentar enviar ou receber uma mensagem do WebSocket após o término do pipeline de middleware, você poderá receber uma exceção semelhante à seguinte:

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'.

Se você estiver usando um serviço em segundo plano para gravar dados em um WebSocket, mantenha o pipeline do middleware em execução. Faça isso usando um TaskCompletionSource<TResult>. Passe o TaskCompletionSource ao seu serviço em segundo plano e faça-o chamar TrySetResult quando terminar com o WebSocket. Em seguida await a propriedade Task durante a solicitação, conforme é mostrado no exemplo a seguir:

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

    BackgroundSocketProcessor.AddSocket(webSocket, socketFinishedTcs);

    await socketFinishedTcs.Task;
});

A exceção fechada do WebSocket também poderá ocorrer ao retornar muito rapidamente de um método de ação. Ao aceitar um soquete de um método de ação, aguarde até que o código que usa o soquete seja concluído antes de retornar do método de ação.

Nunca use Task.Wait, Task.Result ou chamadas de bloqueio semelhantes para aguardar a conclusão do soquete, pois isso pode causar sérios problemas de threading. Sempre use await.

Adicionar suporte a WebSockets HTTP/2 para controladores existentes

O .NET 7 apresentou suporte do Websockets por HTTP/2 a Kestrel, cliente JavaScript SignalR e SignalR com Blazor WebAssembly. WebSockets HTTP/2 usam solicitações CONNECT em vez de GET. Se você usou [HttpGet("/path")] anteriormente em seu método de ação do controlador para solicitações Websocket, atualize-o para usar [Route("/path")] em vez disso.

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;
        }
    }

Compactação

Aviso

Habilitar a compactação em conexões criptografadas pode tornar um aplicativo sujeito a ataques CRIME/BREACH. Se estiver enviando informações confidenciais, evite habilitar a compactação ou use WebSocketMessageFlags.DisableCompression ao chamar WebSocket.SendAsync. Isso se aplica a ambos os lados do WebSocket. Observe que a API dos WebSockets no navegador não tem configuração para desabilitar a compactação por envio.

Caso deseje fazer a compactação de mensagens por WebSockets, o código de aceitação deverá especificar que ele permite a compactação da seguinte maneira:

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

}

WebSocketAcceptContext.ServerMaxWindowBits e WebSocketAcceptContext.DisableServerContextTakeover são opções avançadas que controlam como a compactação funciona.

A compactação é negociada entre o cliente e o servidor ao estabelecer uma conexão pela primeira vez. Você pode ler mais sobre a negociação em Extensões de Compactação para WebSocket RFC.

Observação

Se a negociação de compactação não for aceita pelo servidor ou pelo cliente, a conexão ainda será estabelecida. No entanto, a conexão não usa compactação ao enviar e receber mensagens.

Enviar e receber mensagens

O método AcceptWebSocketAsync atualiza a conexão TCP para uma conexão WebSocket e fornece um objeto WebSocket. Use o objeto WebSocket para enviar e receber mensagens.

O código mostrado anteriormente que aceita a solicitação do WebSocket passa o objeto WebSocket para um método Echo. O código recebe uma mensagem e envia de volta imediatamente a mesma mensagem. As mensagens são enviadas e recebidas em um loop até que o cliente feche a conexão:

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);
}

Ao aceitar a conexão WebSocket antes de iniciar o loop, o pipeline de middleware é encerrado. Ao fechar o soquete, o pipeline é desenrolado. Ou seja, a solicitação deixa de avançar no pipeline quando o WebSocket é aceito. Quando o loop é concluído e o soquete é fechado, a solicitação continua a avançar no pipeline.

Tratar desconexões de cliente

O servidor não é informado automaticamente quando o cliente se desconecta devido à perda de conectividade. O servidor recebe uma mensagem de desconexão apenas quando o cliente a envia, e isso é possível somente quando há conexão com a Internet. Se quiser tomar alguma medida quando isso ocorrer, defina um tempo limite, caso não receba nada do cliente após uma determinada janela de tempo.

Se o cliente não envia mensagens com frequência, e você não deseja atingir um tempo limite apenas porque a conexão fica ociosa, peça ao cliente para usar um temporizador a fim de enviar uma mensagem de ping a cada X segundos. No servidor, se uma mensagem não chegar dentro de 2*X segundos após a mensagem anterior, encerre a conexão e informe que o cliente se desconectou. Aguarde o dobro do intervalo de tempo esperado a fim de deixar um tempo extra para atrasos na rede, que possam atrasar a mensagem de ping.

Restrição de origem do WebSocket

As proteções fornecidas pelo CORS não se aplicam ao WebSockets. Navegadores não:

  • Executam solicitações de simulação de CORS.
  • Respeitam as restrições especificadas em cabeçalhos Access-Control ao fazer solicitações de WebSocket.

No entanto, os navegadores enviam o cabeçalho Origin ao emitir solicitações de WebSocket. Os aplicativos devem ser configurados para validar esses cabeçalhos e garantir que apenas WebSockets provenientes de origens esperadas sejam permitidos.

Se você estiver hospedando o servidor em "https://server.com" e hospedando seu cliente em "https://client.com", adicione "https://client.com" à lista AllowedOrigins para que seja feita a verificação pelos WebSockets.

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

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

app.UseWebSockets(webSocketOptions);

Observação

O cabeçalho Origin é controlado pelo cliente e, como o cabeçalho Referer, pode ser falsificado. Não use esses cabeçalhos como um mecanismo de autenticação.

Suporte ao IIS/IIS Express

O Windows Server 2012 ou posterior e o Windows 8 ou posterior com o IIS/IIS Express 8 ou posterior são compatíveis com o protocolo WebSocket, mas não para WebSockets via HTTP/2.

Observação

WebSockets estão sempre habilitados ao usar o IIS Express.

Habilitar WebSockets no IIS

Para habilitar o suporte para o protocolo WebSocket no Windows Server 2012 ou posterior:

Observação

Estas etapas não são necessárias ao usar o IIS Express

  1. Use o assistente Adicionar Funções e Recursos por meio do menu Gerenciar ou do link no Gerenciador do Servidor.
  2. Selecione Instalação baseada em função ou em recurso. Selecione Avançar.
  3. Selecione o servidor apropriado (o servidor local é selecionado por padrão). Selecione Avançar.
  4. Expanda Servidor Web (IIS) na árvore Funções, expanda Servidor Web e, em seguida, expanda Desenvolvimento de Aplicativos.
  5. Selecione o Protocolo WebSocket. Selecione Avançar.
  6. Se não forem necessários recursos adicionais, selecione Avançar.
  7. Selecione Instalar.
  8. Quando a instalação for concluída, selecione Fechar para sair do assistente.

Para habilitar o suporte para o protocolo WebSocket no Windows 8 ou posterior:

Observação

Estas etapas não são necessárias ao usar o IIS Express

  1. Navegue até Painel de Controle>Programas>Programas e Recursos>Ativar ou desativar recursos do Windows (lado esquerdo da tela).
  2. Abra os nós a seguir: Serviços de Informações da Internet>Serviços da World Wide Web>Recursos de Desenvolvimento de Aplicativos.
  3. Selecione o recurso Protocolo WebSocket. Selecione OK.

Desabilite o WebSocket ao usar o socket.io no Node.js

Se estiver usando o suporte para WebSocket em socket.io no Node.js, desabilite o módulo WebSocket do IIS padrão usando o elemento webSocket em web.config ou applicationHost.config. Se essa etapa não for executada, o módulo WebSocket do IIS tentará lidar com a comunicação do WebSocket em vez do Node.js e do aplicativo.

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

Aplicativo de exemplo

O aplicativo de exemplo que acompanha este artigo é um aplicativo de eco. Ele tem uma página da Web que faz conexões do WebSocket e o servidor reenvia para o cliente todas as mensagens recebidas. O aplicativo de amostra dá suporte a WebSockets via HTTP/2 ao usar uma estrutura de destino do .NET 7 ou posterior.

Executar o aplicativo:

  • Para executar o aplicativo no Visual Studio: abra o projeto de amostra no Visual Studio e pressione Ctrl+F5 para ser executado sem o depurador.
  • Para executar o aplicativo em um shell de comando: execute o comando dotnet run e navegue em um navegador até http://localhost:<port>.

A página da Web mostra o status de conexão:

Estado inicial da página da Web antes da conexão dos WebSockets

Selecione Conectar para enviar uma solicitação WebSocket para a URL exibida. Insira uma mensagem de teste e selecione Enviar. Quando terminar, selecione Fechar Soquete. A seção Log de Comunicação relata cada ação de abertura, envio e fechamento conforme ela ocorre.

Estado final da página da Web depois que as mensagens de conexão e de teste dos WebSockets são enviadas e recebidas

Este artigo explica como começar a usar o WebSockets no ASP.NET Core. WebSocket (RFC 6455) é um protocolo que permite canais de comunicação persistentes bidirecionais em conexões TCP. Ele é usado em aplicativos que se beneficiam de comunicação rápida e em tempo real, como chat, painel e aplicativos de jogos.

Exibir ou fazer download do código de exemplo (como fazer download, como executar).

SignalR

ASP.NET Core SignalR é uma biblioteca que simplifica a adição da funcionalidade da Web em tempo real aos aplicativos. Ele usa WebSockets sempre que possível.

Para a maioria dos aplicativos, recomendamos o SignalR ao invés de WebSockets brutos. SignalR fornece o fallback de transporte para os ambientes em que os WebSockets não estão disponíveis. Fornece também um modelo de aplicativo de chamada de procedimento remoto básico. E, na maioria dos cenários, SignalR não tem nenhuma desvantagem de desempenho significativa em comparação ao uso de WebSockets brutos.

Para alguns aplicativos, o gRPC no .NET fornece uma alternativa para WebSockets.

Pré-requisitos

  • Qualquer sistema operacional compatível com o ASP.NET Core:
    • Windows 7/Windows Server 2008 ou posterior
    • Linux
    • macOS
  • Se o aplicativo é executado no Windows com o IIS:
    • Windows 8/Windows Server 2012 ou posterior
    • IIS 8/IIS 8 Express
    • WebSockets devem estar habilitados. Consulte a seção Suporte do IIS/IIS Express.
  • Se o aplicativo é executado no HTTP.sys:
    • Windows 8/Windows Server 2012 ou posterior
  • Para saber quais são os navegadores compatíveis, consulte Posso usar.

Configurar o middleware

Adicione o middleware de WebSockets em Program.cs:

app.UseWebSockets();

As seguintes configurações podem ser definidas:

  • KeepAliveInterval – a frequência para enviar quadros "ping" ao cliente para garantir que os proxies mantenham a conexão aberta. O padrão é dois minutos.
  • AllowedOrigins – Uma lista de valores de cabeçalho de origem permitidos para solicitações do WebSocket. Por padrão, todas as origens são permitidas. Para obter mais informações, consulte Restrição de origem do WebSocket neste artigo.
var webSocketOptions = new WebSocketOptions
{
    KeepAliveInterval = TimeSpan.FromMinutes(2)
};

app.UseWebSockets(webSocketOptions);

Aceitar solicitações do WebSocket

Futuramente no ciclo de vida da solicitação (mais tarde em Program.cs ou em um método de ação, por exemplo), verifique se é uma solicitação do WebSocket e aceite-a.

O exemplo a seguir é de uma fase posterior em 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);
    }

});

Uma solicitação do WebSocket pode entrar em qualquer URL, mas esse código de exemplo aceita apenas solicitações de /ws.

Uma abordagem semelhante pode ser adotada em um 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;
        }
    }

Ao usar um WebSocket, você deve manter o pipeline de middleware em execução durante a conexão. Se tentar enviar ou receber uma mensagem do WebSocket após o término do pipeline de middleware, você poderá receber uma exceção semelhante à seguinte:

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'.

Se você estiver usando um serviço em segundo plano para gravar dados em um WebSocket, mantenha o pipeline do middleware em execução. Faça isso usando um TaskCompletionSource<TResult>. Passe o TaskCompletionSource ao seu serviço em segundo plano e faça-o chamar TrySetResult quando terminar com o WebSocket. Em seguida await a propriedade Task durante a solicitação, conforme é mostrado no exemplo a seguir:

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

    BackgroundSocketProcessor.AddSocket(webSocket, socketFinishedTcs);

    await socketFinishedTcs.Task;
});

A exceção fechada do WebSocket também poderá ocorrer ao retornar muito rapidamente de um método de ação. Ao aceitar um soquete de um método de ação, aguarde até que o código que usa o soquete seja concluído antes de retornar do método de ação.

Nunca use Task.Wait, Task.Result ou chamadas de bloqueio semelhantes para aguardar a conclusão do soquete, pois isso pode causar sérios problemas de threading. Sempre use await.

Compactação

Aviso

Habilitar a compactação em conexões criptografadas pode tornar um aplicativo sujeito a ataques CRIME/BREACH. Se estiver enviando informações confidenciais, evite habilitar a compactação ou use WebSocketMessageFlags.DisableCompression ao chamar WebSocket.SendAsync. Isso se aplica a ambos os lados do WebSocket. Observe que a API dos WebSockets no navegador não tem configuração para desabilitar a compactação por envio.

Caso deseje fazer a compactação de mensagens por WebSockets, o código de aceitação deverá especificar que ele permite a compactação da seguinte maneira:

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

}

WebSocketAcceptContext.ServerMaxWindowBits e WebSocketAcceptContext.DisableServerContextTakeover são opções avançadas que controlam como a compactação funciona.

A compactação é negociada entre o cliente e o servidor ao estabelecer uma conexão pela primeira vez. Você pode ler mais sobre a negociação em Extensões de Compactação para WebSocket RFC.

Observação

Se a negociação de compactação não for aceita pelo servidor ou pelo cliente, a conexão ainda será estabelecida. No entanto, a conexão não usa compactação ao enviar e receber mensagens.

Enviar e receber mensagens

O método AcceptWebSocketAsync atualiza a conexão TCP para uma conexão WebSocket e fornece um objeto WebSocket. Use o objeto WebSocket para enviar e receber mensagens.

O código mostrado anteriormente que aceita a solicitação do WebSocket passa o objeto WebSocket para um método Echo. O código recebe uma mensagem e envia de volta imediatamente a mesma mensagem. As mensagens são enviadas e recebidas em um loop até que o cliente feche a conexão:

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);
}

Ao aceitar a conexão WebSocket antes de iniciar o loop, o pipeline de middleware é encerrado. Ao fechar o soquete, o pipeline é desenrolado. Ou seja, a solicitação deixa de avançar no pipeline quando o WebSocket é aceito. Quando o loop é concluído e o soquete é fechado, a solicitação continua a avançar no pipeline.

Tratar desconexões de cliente

O servidor não é informado automaticamente quando o cliente se desconecta devido à perda de conectividade. O servidor recebe uma mensagem de desconexão apenas quando o cliente a envia, e isso é possível somente quando há conexão com a Internet. Se quiser tomar alguma medida quando isso ocorrer, defina um tempo limite, caso não receba nada do cliente após uma determinada janela de tempo.

Se o cliente não envia mensagens com frequência, e você não deseja atingir um tempo limite apenas porque a conexão fica ociosa, peça ao cliente para usar um temporizador a fim de enviar uma mensagem de ping a cada X segundos. No servidor, se uma mensagem não chegar dentro de 2*X segundos após a mensagem anterior, encerre a conexão e informe que o cliente se desconectou. Aguarde o dobro do intervalo de tempo esperado a fim de deixar um tempo extra para atrasos na rede, que possam atrasar a mensagem de ping.

Restrição de origem do WebSocket

As proteções fornecidas pelo CORS não se aplicam ao WebSockets. Navegadores não:

  • Executam solicitações de simulação de CORS.
  • Respeitam as restrições especificadas em cabeçalhos Access-Control ao fazer solicitações de WebSocket.

No entanto, os navegadores enviam o cabeçalho Origin ao emitir solicitações de WebSocket. Os aplicativos devem ser configurados para validar esses cabeçalhos e garantir que apenas WebSockets provenientes de origens esperadas sejam permitidos.

Se você estiver hospedando o servidor em "https://server.com" e hospedando seu cliente em "https://client.com", adicione "https://client.com" à lista AllowedOrigins para que seja feita a verificação pelos WebSockets.

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

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

app.UseWebSockets(webSocketOptions);

Observação

O cabeçalho Origin é controlado pelo cliente e, como o cabeçalho Referer, pode ser falsificado. Não use esses cabeçalhos como um mecanismo de autenticação.

Suporte ao IIS/IIS Express

O Windows Server 2012 ou posterior e o Windows 8 ou posterior com o IIS/IIS Express 8 ou posterior são compatíveis com o protocolo WebSocket.

Observação

WebSockets estão sempre habilitados ao usar o IIS Express.

Habilitar WebSockets no IIS

Para habilitar o suporte para o protocolo WebSocket no Windows Server 2012 ou posterior:

Observação

Estas etapas não são necessárias ao usar o IIS Express

  1. Use o assistente Adicionar Funções e Recursos por meio do menu Gerenciar ou do link no Gerenciador do Servidor.
  2. Selecione Instalação baseada em função ou em recurso. Selecione Avançar.
  3. Selecione o servidor apropriado (o servidor local é selecionado por padrão). Selecione Avançar.
  4. Expanda Servidor Web (IIS) na árvore Funções, expanda Servidor Web e, em seguida, expanda Desenvolvimento de Aplicativos.
  5. Selecione o Protocolo WebSocket. Selecione Avançar.
  6. Se não forem necessários recursos adicionais, selecione Avançar.
  7. Selecione Instalar.
  8. Quando a instalação for concluída, selecione Fechar para sair do assistente.

Para habilitar o suporte para o protocolo WebSocket no Windows 8 ou posterior:

Observação

Estas etapas não são necessárias ao usar o IIS Express

  1. Navegue até Painel de Controle>Programas>Programas e Recursos>Ativar ou desativar recursos do Windows (lado esquerdo da tela).
  2. Abra os nós a seguir: Serviços de Informações da Internet>Serviços da World Wide Web>Recursos de Desenvolvimento de Aplicativos.
  3. Selecione o recurso Protocolo WebSocket. Selecione OK.

Desabilite o WebSocket ao usar o socket.io no Node.js

Se estiver usando o suporte para WebSocket em socket.io no Node.js, desabilite o módulo WebSocket do IIS padrão usando o elemento webSocket em web.config ou applicationHost.config. Se essa etapa não for executada, o módulo WebSocket do IIS tentará lidar com a comunicação do WebSocket em vez do Node.js e do aplicativo.

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

Aplicativo de exemplo

O aplicativo de exemplo que acompanha este artigo é um aplicativo de eco. Ele tem uma página da Web que faz conexões do WebSocket e o servidor reenvia para o cliente todas as mensagens recebidas. O aplicativo de amostra não está configurado para ser executado no Visual Studio com IIS Express, portanto, execute o aplicativo em um shell de comando com dotnet run e navegue em um navegador até http://localhost:<port>. A página da Web mostra o status de conexão:

Estado inicial da página da Web antes da conexão dos WebSockets

Selecione Conectar para enviar uma solicitação WebSocket para a URL exibida. Insira uma mensagem de teste e selecione Enviar. Quando terminar, selecione Fechar Soquete. A seção Log de Comunicação relata cada ação de abertura, envio e fechamento conforme ela ocorre.

Estado final da página da Web depois que as mensagens de conexão e de teste dos WebSockets são enviadas e recebidas

Este artigo explica como começar a usar o WebSockets no ASP.NET Core. WebSocket (RFC 6455) é um protocolo que permite canais de comunicação persistentes bidirecionais em conexões TCP. Ele é usado em aplicativos que se beneficiam de comunicação rápida e em tempo real, como chat, painel e aplicativos de jogos.

Exibir ou baixar um código de exemplo (como baixar). Como executar.

SignalR

ASP.NET Core SignalR é uma biblioteca que simplifica a adição da funcionalidade da Web em tempo real aos aplicativos. Ele usa WebSockets sempre que possível.

Para a maioria dos aplicativos, recomendamos o SignalR ao invés de WebSockets brutos. SignalR fornece o fallback de transporte para os ambientes em que os WebSockets não estão disponíveis. Fornece também um modelo de aplicativo de chamada de procedimento remoto básico. E, na maioria dos cenários, SignalR não tem nenhuma desvantagem de desempenho significativa em comparação ao uso de WebSockets brutos.

Para alguns aplicativos, o gRPC no .NET fornece uma alternativa para WebSockets.

Pré-requisitos

  • Qualquer sistema operacional compatível com o ASP.NET Core:
    • Windows 7/Windows Server 2008 ou posterior
    • Linux
    • macOS
  • Se o aplicativo é executado no Windows com o IIS:
    • Windows 8/Windows Server 2012 ou posterior
    • IIS 8/IIS 8 Express
    • WebSockets devem estar habilitados. Consulte a seção Suporte do IIS/IIS Express.
  • Se o aplicativo é executado no HTTP.sys:
    • Windows 8/Windows Server 2012 ou posterior
  • Para saber quais são os navegadores compatíveis, consulte Posso usar.

Configurar o middleware

Adicione o middleware do WebSockets no método Configure da classe Startup:

app.UseWebSockets();

Observação

Se você quiser aceitar solicitações de WebSocket em um controlador, a chamada para app.UseWebSockets deverá ocorrer antes de app.UseEndpoints.

As seguintes configurações podem ser definidas:

  • KeepAliveInterval – a frequência para enviar quadros "ping" ao cliente para garantir que os proxies mantenham a conexão aberta. O padrão é dois minutos.
  • AllowedOrigins – Uma lista de valores de cabeçalho de origem permitidos para solicitações do WebSocket. Por padrão, todas as origens são permitidas. Consulte "Restrição de origem do WebSocket" abaixo para obter detalhes.
var webSocketOptions = new WebSocketOptions()
{
    KeepAliveInterval = TimeSpan.FromSeconds(120),
};

app.UseWebSockets(webSocketOptions);

Aceitar solicitações do WebSocket

Futuramente no ciclo de vida da solicitação (mais tarde no método Configure ou em um método de ação, por exemplo), verifique se é uma solicitação do WebSocket e aceite-a.

O exemplo a seguir é de uma fase posterior do 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();
    }

});

Uma solicitação do WebSocket pode entrar em qualquer URL, mas esse código de exemplo aceita apenas solicitações de /ws.

Uma abordagem semelhante pode ser adotada em um 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;
        }
    }

Ao usar um WebSocket, você deve manter o pipeline de middleware em execução durante a conexão. Se tentar enviar ou receber uma mensagem do WebSocket após o término do pipeline de middleware, você poderá receber uma exceção semelhante à seguinte:

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'.

Se você estiver usando um serviço em segundo plano para gravar dados em um WebSocket, mantenha o pipeline do middleware em execução. Faça isso usando um TaskCompletionSource<TResult>. Passe o TaskCompletionSource ao seu serviço em segundo plano e faça-o chamar TrySetResult quando terminar com o WebSocket. Em seguida await a propriedade Task durante a solicitação, conforme é mostrado no exemplo a seguir:

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

        BackgroundSocketProcessor.AddSocket(webSocket, socketFinishedTcs);

        await socketFinishedTcs.Task;
    }
});

A exceção fechada do WebSocket também poderá ocorrer ao retornar muito rapidamente de um método de ação. Ao aceitar um soquete de um método de ação, aguarde até que o código que usa o soquete seja concluído antes de retornar do método de ação.

Nunca use Task.Wait, Task.Result ou chamadas de bloqueio semelhantes para aguardar a conclusão do soquete, pois isso pode causar sérios problemas de threading. Sempre use await.

Enviar e receber mensagens

O método AcceptWebSocketAsync atualiza a conexão TCP para uma conexão WebSocket e fornece um objeto WebSocket. Use o objeto WebSocket para enviar e receber mensagens.

O código mostrado anteriormente que aceita a solicitação do WebSocket passa o objeto WebSocket para um método Echo. O código recebe uma mensagem e envia de volta imediatamente a mesma mensagem. As mensagens são enviadas e recebidas em um loop até que o cliente feche a conexão:

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);
}

Ao aceitar a conexão WebSocket antes de iniciar o loop, o pipeline de middleware é encerrado. Ao fechar o soquete, o pipeline é desenrolado. Ou seja, a solicitação deixa de avançar no pipeline quando o WebSocket é aceito. Quando o loop é concluído e o soquete é fechado, a solicitação continua a avançar no pipeline.

Tratar desconexões de cliente

O servidor não é informado automaticamente quando o cliente se desconecta devido à perda de conectividade. O servidor recebe uma mensagem de desconexão apenas quando o cliente a envia, e isso é possível somente quando há conexão com a Internet. Se quiser tomar alguma medida quando isso ocorrer, defina um tempo limite, caso não receba nada do cliente após uma determinada janela de tempo.

Se o cliente não envia mensagens com frequência, e você não deseja atingir um tempo limite apenas porque a conexão fica ociosa, peça ao cliente para usar um temporizador a fim de enviar uma mensagem de ping a cada X segundos. No servidor, se uma mensagem não chegar dentro de 2*X segundos após a mensagem anterior, encerre a conexão e informe que o cliente se desconectou. Aguarde o dobro do intervalo de tempo esperado a fim de deixar um tempo extra para atrasos na rede, que possam atrasar a mensagem de ping.

Observação

O interno ManagedWebSocket manipula os quadros ping/pong implicitamente para manter a conexão ativa se a opção KeepAliveInterval for maior que zero, o que significa 30 segundos (TimeSpan.FromSeconds(30)).

Restrição de origem do WebSocket

As proteções fornecidas pelo CORS não se aplicam ao WebSockets. Navegadores não:

  • Executam solicitações de simulação de CORS.
  • Respeitam as restrições especificadas em cabeçalhos Access-Control ao fazer solicitações de WebSocket.

No entanto, os navegadores enviam o cabeçalho Origin ao emitir solicitações de WebSocket. Os aplicativos devem ser configurados para validar esses cabeçalhos e garantir que apenas WebSockets provenientes de origens esperadas sejam permitidos.

Se você estiver hospedando o servidor em "https://server.com" e hospedando seu cliente em "https://client.com", adicione "https://client.com" à lista AllowedOrigins para que seja feita a verificação pelos WebSockets.

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

app.UseWebSockets(webSocketOptions);

Observação

O cabeçalho Origin é controlado pelo cliente e, como o cabeçalho Referer, pode ser falsificado. Não use esses cabeçalhos como um mecanismo de autenticação.

Suporte ao IIS/IIS Express

O Windows Server 2012 ou posterior e o Windows 8 ou posterior com o IIS/IIS Express 8 ou posterior são compatíveis com o protocolo WebSocket.

Observação

WebSockets estão sempre habilitados ao usar o IIS Express.

Habilitar WebSockets no IIS

Para habilitar o suporte para o protocolo WebSocket no Windows Server 2012 ou posterior:

Observação

Estas etapas não são necessárias ao usar o IIS Express

  1. Use o assistente Adicionar Funções e Recursos por meio do menu Gerenciar ou do link no Gerenciador do Servidor.
  2. Selecione Instalação baseada em função ou em recurso. Selecione Avançar.
  3. Selecione o servidor apropriado (o servidor local é selecionado por padrão). Selecione Avançar.
  4. Expanda Servidor Web (IIS) na árvore Funções, expanda Servidor Web e, em seguida, expanda Desenvolvimento de Aplicativos.
  5. Selecione o Protocolo WebSocket. Selecione Avançar.
  6. Se não forem necessários recursos adicionais, selecione Avançar.
  7. Selecione Instalar.
  8. Quando a instalação for concluída, selecione Fechar para sair do assistente.

Para habilitar o suporte para o protocolo WebSocket no Windows 8 ou posterior:

Observação

Estas etapas não são necessárias ao usar o IIS Express

  1. Navegue até Painel de Controle>Programas>Programas e Recursos>Ativar ou desativar recursos do Windows (lado esquerdo da tela).
  2. Abra os nós a seguir: Serviços de Informações da Internet>Serviços da World Wide Web>Recursos de Desenvolvimento de Aplicativos.
  3. Selecione o recurso Protocolo WebSocket. Selecione OK.

Desabilite o WebSocket ao usar o socket.io no Node.js

Se estiver usando o suporte para WebSocket em socket.io no Node.js, desabilite o módulo WebSocket do IIS padrão usando o elemento webSocket em web.config ou applicationHost.config. Se essa etapa não for executada, o módulo WebSocket do IIS tentará lidar com a comunicação do WebSocket em vez do Node.js e do aplicativo.

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

Aplicativo de exemplo

O aplicativo de exemplo que acompanha este artigo é um aplicativo de eco. Ele tem uma página da Web que faz conexões do WebSocket e o servidor reenvia para o cliente todas as mensagens recebidas. O aplicativo de amostra não está configurado para ser executado no Visual Studio com IIS Express, portanto, execute o aplicativo em um shell de comando com dotnet run e navegue em um navegador até http://localhost:5000. A página da Web mostra o status de conexão:

Estado inicial da página da Web antes da conexão dos WebSockets

Selecione Conectar para enviar uma solicitação WebSocket para a URL exibida. Insira uma mensagem de teste e selecione Enviar. Quando terminar, selecione Fechar Soquete. A seção Log de Comunicação relata cada ação de abertura, envio e fechamento conforme ela ocorre.

Estado final da página da Web depois que as mensagens de conexão e de teste dos WebSockets são enviadas e recebidas