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:
InvocationMessageStreamInvocationMessageStreamItemMessageCompletionMessageCancelInvocationMessagePingMessageCloseMessage
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