Skip to main content

Transports & Negotiation

SignalR uses pluggable transports to carry frames between client and server. aiosignalr implements the three standard ASP.NET Core transports.

Transport overview

TransportDuplexServer → clientClient → serverBinary (MessagePack)
WebSocketsWebSocket framesWebSocket frames
ServerSentEventsSSE streamHTTP POST✘ (Text only)
LongPollingLong poll responseHTTP 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

  1. The server lists its transports in preference order.
  2. 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).
  3. 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"})