Transports & Negotiation
SignalR uses pluggable transports to carry frames between client and server. aiosignalr implements the three standard ASP.NET Core transports.
Transport overview
| Transport | Duplex | Server → client | Client → server | Binary (MessagePack) |
|---|---|---|---|---|
WebSockets | ✔ | WebSocket frames | WebSocket frames | ✔ |
ServerSentEvents | ✘ | SSE stream | HTTP POST | ✘ (Text only) |
LongPolling | ✘ | Long poll response | HTTP POST | ✔ |
Only WebSockets is truly full-duplex; the other two are half-transports that pair a receive path with HTTP POST for sending.
Negotiation
The client begins every connection with:
POST {endpoint}/negotiate[?negotiateVersion=1[&useStatefulReconnect=true]]
The server responds with connectionId, connectionToken, negotiateVersion,
availableTransports, and — when agreed — useStatefulReconnect: true.
{
"connectionToken": "05265228-...",
"connectionId": "807809a5-...",
"negotiateVersion": 1,
"useStatefulReconnect": true,
"availableTransports": [
{ "transport": "WebSockets", "transferFormats": ["Text", "Binary"] },
{ "transport": "ServerSentEvents", "transferFormats": ["Text"] },
{ "transport": "LongPolling", "transferFormats": ["Text", "Binary"] }
]
}
Transport selection
- The server lists its transports in preference order.
- The client filters by:
- transports the client is configured to allow,
- the negotiated hub protocol's transfer format compatibility (MessagePack needs a Binary-capable transport; SSE is Text-only).
- The first compatible transport wins and the client connects to
{endpoint}?id={connectionToken}.
WebSocket
connection = HubConnection() # WebSocket is tried first by default
await connection.start("ws://host:8080/hub")
- Text frames carry JSON protocol messages; binary frames carry MessagePack.
- The client also sends HTTP headers (
Authorization: Bearer ..., custom headers) on the upgrade request. - Only the WebSocket transport supports stateful reconnect.
Server-Sent Events
Server → client events arrive as SSE data: blocks (JSON only); the client
sends via HTTP POST to the same ?id= URL.
await connection.start(url, transports={"ServerSentEvents"})
Long Polling
The client issues a GET that the server holds until it has frames to deliver
(or a timeout elapses); sending uses HTTP POST.
await connection.start(url, transports={"LongPolling"})
Transfer formats
The negotiated hub protocol determines which transfer format the transport must support:
- JSON →
Text - MessagePack →
Binary
For WebSocket, the transfer format also selects frame type (text vs. binary). aiosignalr encodes each frame according to the active protocol regardless of transport.
Forcing a transport
# Allow only the listed transports
await connection.start(url, transports={"WebSockets", "LongPolling"})