Introdução | WebSocket | SignalR | CSharp
- Introdução | WebSocket | SignalR | CSharp

WebSocket é um upgrade da conexão HTTP, mas não exatamente uma "versão melhorada" — é uma mudança de protocolo feita após a negociação HTTP inicial.
1- Cliente faz uma request http:
GET /algum-endpoint HTTP/1.1
Host: exemplo.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: xxxxxxxxx==
Sec-WebSocket-Version: 13
2- O servidor se suportar websocket retorna:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: yyyyyyyyy=
Cuidado nos métodos que assinam o evento, o valor precisa ser um object
connection.On<string>("ReceberMensagem", mensagem =>
{
Console.WriteLine($"Mensagem recebida: {mensagem}");
});
Hub
- É um ponto central de conexão no servidor.
- Para criar um hub, a classe deve herdar de
Microsoft.AspNetCore.SignalR.Hub
Escopo do Hub (IMPORTANTE)
Cada Hub é isolado em termos de conexão.
| Conceito | Comportamento |
|---|---|
| Conexões | São específicas por Hub |
| Users | São resolvidos logicamente, mas conexões são por Hub |
| Groups | Existem apenas dentro do Hub |
| Clients | Só enxergam conexões daquele Hub |
Regra prática
- Um usuário pode estar conectado em vários Hubs ao mesmo tempo
- Cada conexão gera um ConnectionId diferente
- Um Hub não acessa diretamente conexões de outro Hub
Exemplo:
User "joao" conectado em:
- ChatHub → ConnectionId A
- NotificationHub → ConnectionId B
ChatHubNÃO enxergaConnectionId BNotificationHubNÃO enxergaConnectionId A
📌 Conclusão arquitetural:
Hub = boundary de conexão (isolamento de transporte)
Clientes

Client é a conexão individual com o SignalR (ConnectionId), enquanto User é a identidade lógica que agrupa múltiplas conexões do mesmo usuário.
- Ao chamar um hub sendo cliente, há duas formas:
| Método | Comportamento |
|---|---|
connection.invoke | Espera retorno (RPC-like) |
connection.send | Fire-and-forget |
Servidor
Como escalar o servidor de WebSocket?
Clients (envio de mensagens - broadcast, unicast, multicast)
| Método | Tipo de envio | Escopo | Inclui Caller? | Observações arquiteturais |
|---|---|---|---|---|
Clients.All | Broadcast (um → todos) | Todos no Hub | ✅ Sim | Envia para todas as conexões do Hub |
Clients.Caller | Unicast (um → um) | Conexão atual | ✅ Sim | Usado como resposta (RPC-like) |
Clients.Others | Broadcast (um → todos - caller) | Todos no Hub | ❌ Não | Evita eco para quem enviou |
Clients.AllExcept(ids) | Broadcast seletivo (um → muitos) | Todos exceto lista | Depende | Controle fino por ConnectionId |
Clients.Client(id) | Unicast (um → um específico) | Conexão específica | ❌ Não | Endereçamento físico (baixo nível) |
Clients.Clients(ids) | Multicast (um → muitos específicos) | Lista de conexões | ❌ Não | Batch de conexões específicas |
Clients.User(userId) | Unicast lógico (um → usuário) | Usuário | ❌ Não | Envia para todas conexões do usuário neste Hub |
Clients.Users(userIds) | Multicast lógico (um → vários usuários) | Usuários | ❌ Não | Múltiplos usuários |
Clients.Group(name) | Multicast (um → grupo) | Grupo | ❌ Não | Grupo é isolado por Hub |
Clients.Groups(names) | Multicast (um → vários grupos) | Vários grupos | ❌ Não | Fan-out para múltiplos grupos |
Clients.GroupExcept(name, ids) | Multicast seletivo (grupo → muitos) | Grupo | ❌ Não | Grupo menos algumas conexões |
Clients.OthersInGroup(name) | Multicast (grupo → todos - caller) | Grupo | ❌ Não | Grupo sem o caller |
Observações críticas
Tudo é por Hub
Mesmo User:
User "joao"
├── ChatHub → recebe
└── NotificationHub → NÃO recebe
Clients.UserNÃO é global entre hubs
Hierarquia de endereçamento
ConnectionId (nível físico)
↓
User (agrupa conexões)
↓
Group (agrupamento dinâmico)
Diferença entre conceitos
| Conceito | Persistência | Escopo | Uso |
|---|---|---|---|
| Connection | ❌ | Hub | Controle técnico |
| User | ✅ (auth) | Hub | Identidade |
| Group | ❌ | Hub | Segmentação dinâmica |
Quando usar cada um
| Cenário | Método |
|---|---|
| Broadcast geral | All |
| Resposta direta | Caller |
| Broadcast sem eco | Others |
| Notificação por usuário | User |
| Sala (chat room) | Group |
| Target específico | Client |
Anatomia de uma conexão
ConnectionId→ Identificador únicoUserIdentifier→ ID lógico do usuárioUser→ ClaimsPrincipalItems→ Metadados da conexãoConnectionAborted→ Cancelamento da conexão
📌 Importante:
- ConnectionId muda a cada reconexão
- Não usar ConnectionId como identidade de negócio
Negotiate
A rota negotiate é parte fundamental do processo de conexão no SignalR.
Propósito
-
Negociação de protocolo (versão, formato)
-
Seleção de transporte:
- WebSocket
- Server-Sent Events
- Long Polling
-
Geração de token de conexão
Fluxo
1. Cliente → GET /hub/negotiate
2. Servidor → retorna JSON:
- connectionId
- availableTransports
- url/token
3. Cliente → inicia transporte (WebSocket, etc)
Exemplo de resposta
{
"connectionId": "abc123",
"availableTransports": [
{ "transport": "WebSockets" },
{ "transport": "ServerSentEvents" },
{ "transport": "LongPolling" }
]
}
Observação arquitetural
-
O negotiate desacopla:
- cliente vs transporte
- protocolo vs infraestrutura
👉 Permite fallback automático
Segurança
- Deve exigir autenticação
- Tokens devem ser temporários
- Evita spoofing de conexão
Resumo mental
SignalR
Hub
├── Connections (isoladas por hub)
├── Users (lógico, mas escopo do hub)
└── Groups (isolados)
Negotiate
└── Define como a conexão será criada