Skip to main content

Custom Protocol | WebSocket | SignalR | CSharp

  • Exemplo de protocolo customizado usando IHubProtocol

O que é um protocolo no SignalR

No SignalR, o Hub Protocol define como as mensagens do Hub são serializadas e desserializadas.

Exemplos nativos:

  • JSON
  • MessagePack

O protocolo decide como transformar isto:

await Clients.All.SendAsync("ReceberMensagem", "Olá");

em bytes/texto trafegando pela conexão.


Quando usar custom protocol

Use apenas se você realmente precisa de:

  • formato binário próprio
  • compatibilidade com protocolo legado
  • redução extrema de payload
  • controle total da serialização
  • interoperabilidade com cliente não oficial

Não use apenas para “melhorar performance”. Para isso, normalmente MessagePack já resolve melhor.


Estrutura geral


Exemplo: criando um protocolo customizado simples

Este exemplo cria um protocolo chamado custom-json.

Ele não é melhor que JSON. Ele serve para demonstrar como plugar um protocolo próprio.


Classe do protocolo

using System.Buffers;
using System.Text;
using System.Text.Json;

using Microsoft.AspNetCore.SignalR.Protocol;

public sealed class CustomJsonHubProtocol : IHubProtocol
{
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};

public string Name => "custom-json";

public int Version => 1;

public TransferFormat TransferFormat => TransferFormat.Text;

public bool IsVersionSupported(int version)
{
return version == Version;
}

public ReadOnlyMemory<byte> GetMessageBytes(HubMessage message)
{
var json = JsonSerializer.Serialize(message, message.GetType(), JsonOptions);

var payload = $"CUSTOM|{json}\u001e";

return Encoding.UTF8.GetBytes(payload);
}

public bool TryParseMessage(
ref ReadOnlySequence<byte> input,
IInvocationBinder binder,
out HubMessage? message)
{
message = null;

var text = Encoding.UTF8.GetString(input.ToArray());

if (!text.EndsWith('\u001e'))
return false;

text = text[..^1];

if (!text.StartsWith("CUSTOM|"))
throw new InvalidDataException("Mensagem inválida para o protocolo custom-json.");

var json = text["CUSTOM|".Length..];

using var document = JsonDocument.Parse(json);

if (!document.RootElement.TryGetProperty("type", out var typeProperty))
throw new InvalidDataException("Campo type não encontrado.");

var type = typeProperty.GetInt32();

message = type switch
{
1 => JsonSerializer.Deserialize<InvocationMessage>(json, JsonOptions),
6 => JsonSerializer.Deserialize<PingMessage>(json, JsonOptions),
7 => CloseMessage.Empty,
_ => throw new NotSupportedException($"Tipo de mensagem não suportado: {type}")
};

input = input.Slice(input.End);

return true;
}

public void WriteMessage(HubMessage message, IBufferWriter<byte> output)
{
var bytes = GetMessageBytes(message);

output.Write(bytes.Span);
}
}

Registro no servidor

using Microsoft.AspNetCore.SignalR.Protocol;

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

builder.Services.AddSignalR();

builder.Services.AddSingleton<IHubProtocol, CustomJsonHubProtocol>();

WebApplication app = builder.Build();

app.MapHub<ComandoHub>("/hub/comandos");

app.Run();

Hub de exemplo

using Microsoft.AspNetCore.SignalR;

public sealed class ComandoHub : Hub
{
public async Task EnviarComando(string comando)
{
await Clients.Caller.SendAsync("ReceberComando", comando);
}
}

Cliente .NET usando o protocolo

using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.AspNetCore.SignalR.Protocol;
using Microsoft.Extensions.DependencyInjection;

var connection = new HubConnectionBuilder()
.WithUrl("https://localhost:5001/hub/comandos")
.AddHubOptions(options =>
{
options.AdditionalProtocols.Add(new CustomJsonHubProtocol());
})
.Build();

connection.On<string>("ReceberComando", mensagem =>
{
Console.WriteLine($"Mensagem recebida: {mensagem}");
});

await connection.StartAsync();

await connection.InvokeAsync("EnviarComando", "SELECT * FROM PRODUTOS");

Console.ReadLine();

Observação importante

Esse exemplo mostra o conceito, mas um protocolo customizado real precisa tratar corretamente:

  • InvocationMessage
  • StreamInvocationMessage
  • StreamItemMessage
  • CompletionMessage
  • CancelInvocationMessage
  • PingMessage
  • CloseMessage

Ou seja: implementar IHubProtocol completo é trabalhoso.


Alternativa recomendada: MessagePack

Servidor:

builder.Services
.AddSignalR()
.AddMessagePackProtocol();

Cliente:

var connection = new HubConnectionBuilder()
.WithUrl("https://localhost:5001/hub/comandos")
.AddMessagePackProtocol()
.Build();

O MessagePack usa formato binário, reduz payload e é suportado oficialmente. O material também destaca que ele precisa de pacote adicional e é mais rígido que JSON, inclusive em case sensitivity .


Regra prática

Para o seu cenário:

  • Cliente .NET + servidor .NET → use MessagePack
  • Payload JSON comum → use JSON padrão
  • Cliente legado/protocolo proprietário → considere IHubProtocol
  • Quer só comprimir ou reduzir tráfego → não crie protocolo customizado; use MessagePack ou compressão no nível adequado