AyCode.Core/AyCode.Services/docs/SIGNALR
Loretta e7b12a1100 [LOADED_DOCS: 3 files, no new loads]
Switch to FlushPolicy enum for streaming flush control

Replaces the legacy bool waitForFlush with a new FlushPolicy enum (PerChunk, DoubleBuffered, Coalesced) across all binary streaming serialization APIs and SignalR protocol options. Updates all code, configuration, and documentation to use the new policy, clarifies memory/throughput trade-offs, and closes related TODOs. Stream-backed writers remain sequential; only parallel-capable Pipe-based writers honor the policy.
2026-05-03 08:13:59 +02:00
..
README.md [LOADED_DOCS: 3 files, no new loads] 2026-05-01 14:01:23 +02:00
SIGNALR_ISSUES.md [LOADED_DOCS: 3 files, no new loads] 2026-05-03 08:13:59 +02:00
SIGNALR_TODO.md [LOADED_DOCS: 3 files, no new loads] 2026-05-03 08:13:59 +02:00

README.md

SignalR Client

Client-side SignalR transport: custom binary protocol, tag-based dispatch. Source: SignalRs/

Server-side hub, session, broadcast: AyCode.Services.Server/docs/SIGNALR/README.md DataSource collection: AyCode.Services.Server/docs/SIGNALR_DATASOURCE/README.md

Design

Single hub method, tag-based dispatch:

Client ──OnReceiveMessage(tag, requestId, signalParams, data)──► Server
Client ◄──OnReceiveMessage(tag, requestId, signalParams, data)── Server

Tag (int) selects server method; all calls go through OnReceiveMessage. Metadata (SignalParams) and payload (object data) travel as separate hub argsSignalParams AcBinary-serialized; data zero-copy to pipe via AcBinarySerializer, or raw byte[] via protocol fast-path.

Client:                               Server:
AcSignalRClientBase                   AcWebSignalRHubBase<TTags, TLogger>
  ├─ HubConnection (WebSocket)          ├─ Hub<IAcSignalRHubItemServer>
  ├─ AyCodeBinaryHubProtocol            ├─ DynamicMethodRegistry
  ├─ Pending request tracking           ├─ Parameter deserialization
  └─ Response callbacks                 └─ Broadcast to other clients

Tag System

Base tags in AyCode.Core:

public class AcSignalRTags
{
    public const int None = 0;
    public const int PingTag = 90001;
    public const int EchoTag = 90002;
}

Consuming projects inherit and define own tags (plain int constants, must be unique per hub):

public abstract class MyProjectTags : AcSignalRTags
{
    public const int GetOrders = 100;
    public const int GetOrderById = 101;
}

Attributes:

  • [Tag(42)] — base: maps int tag → method
  • [SignalR(messageTag, sendToOtherClientTag, sendToOtherClientType)] — server routing + broadcast
  • [SignalRSendToClient(100)] — client receive dispatch

SendToClientType: None | Others | Caller | All

Usage:

var orders = await client.PostAsync<List<Order>>(MyTags.GetOrders, companyId);        // single param
var result = await client.PostAsync<T>(MyTags.Query, [companyId, filter]);            // object[] params
await client.PostDataAsync(MyTags.SaveOrder, order);
await client.PostDataAsync(MyTags.SaveOrder, order, async response => { ... });       // async callback

CRUD helpers (PostAsync, GetByIdAsync, GetAllAsync, PostDataAsync) are generic transport, not tied to DataSource.

Wire Protocol

AcBinaryHubProtocol / AyCodeBinaryHubProtocol

Custom IHubProtocol ("acbinary") replacing JSON. Zero-copy write: BufferWriterBinaryOutput + AcBinarySerializer.Serialize(value, output) directly to pipe. Zero-copy read: SequenceReader<byte> from pipe's ReadOnlySequence. BinaryProtocolMode ctor param selects transport: Bytes (default, ArrayBinaryOutput → byte[]), Segment (BWO zero-copy to PipeWriter, single flush), AsyncSegment (AsyncPipeWriterOutput, per-chunk FlushAsync + pipeline parallelism).

AcBinaryHubProtocol is the unsealed base — general binary framing. AyCodeBinaryHubProtocol derives with consumer-specific logic: SignalParams capture (OnArgumentRead hook), IsRawBytesData path, SignalDataType resolution. Register AyCodeBinaryHubProtocol on both client and server.

Wire format, argument framing, dual BWO pattern, length prefix patching: ../SIGNALR_BINARY_PROTOCOL/README.md

SignalParams + Payload Separation

SignalParams (separate hub argument, [AcBinarySerializable]):

Field Type Purpose
Status SignalResponseStatus Success/Error
DataSerializerType AcSerializerType Binary or JsonGZip — tells client how to deserialize response data
Parameters byte[]? Serialized byte[][] as single byte[] (protocol fast-path). Null when no parameters.
SignalDataType string? AssemblyQualifiedName of response object type. Server sets before sending. Protocol uses this for eager type-aware deserialization. Null for raw byte[] responses.
IsRawBytesData bool Client sets true when T == byte[] (e.g. DataSource populate/merge). Protocol returns raw byte[] without deserialization.

Typed access via methods (PostDataJson pattern):

  • Client: SetParameterValues(object[]) — packs each param via ToBinary()byte[][]byte[]
  • Server: GetParameterValues(ParameterInfo[]) — unpacks byte[]byte[][] → per-element BinaryTo(targetType)
  • Protocol never sees byte[][] — only byte[].

object data (4th hub argument) — protocol handles three cases on read:

  1. byte[] fast-path: first byte is BinaryTypeCode.ByteArray(0x44) → skip tag, rest is raw payload bytes. No VarUInt (argLength implies size). No deserializer.
  2. IsRawBytesData (AyCodeBinaryHubProtocol): SignalParams.IsRawBytesData == true → return entire argSlice as raw byte[]. No deserialization. Consumer handles deserialization.
  3. Typed deserialization (AyCodeBinaryHubProtocol): resolve type from SignalParams.SignalDataTypeAcBinaryDeserializer.Deserialize(sequence, type) → return typed object.

Parameters and data are independent — both can be null or filled in any direction (SignalR is bidirectional).

Combination Parameters data Example
Request byte[] (packed params) null/empty client calls server method
Response null typed object or byte[] server returns result
Request + data byte[] typed object client responds to server with data
Signal null null/empty ping, status change, broadcast trigger

SignalResponseDataMessage is an internal DTO for client-side callback routing and stream wire format — constructed in-memory from signalParams + data, never serialized as envelope on wire. RawResponseData is object? (typed object or byte[]). GetResponseData<T>() performs direct cast.

Request/Response Flow

Client → Server

1. PostAsync<T>(tag, param) / PostAsync<T>(tag, params[]) / PostDataAsync(tag, data, callback)
2. signalParams.SetParameterValues(object[]):
   Each param ToBinary() → byte[][] → ToBinary() → byte[] (single wire blob)
3. SignalParams { Status = Success, Parameters = byte[], IsRawBytesData = (typeof(T) == typeof(byte[])) }
4. SendCoreAsync → HubConnection.SendAsync("OnReceiveMessage", tag, requestId, signalParams, null)
5. AyCodeBinaryHubProtocol frames on wire (signalParams via AcBinary, data = null for requests)

Server → Client

OnReceiveMessage(tag, requestId, signalParams, object data)
├─ Construct SignalResponseDataMessage in-memory:
│  └─ { MessageTag, Status, DataSerializerType, RawResponseData = data }
├─ Matching requestId in pending dict:
│  ├─ Route: null→sync wait, Action→invoke, Func<Task>→await
│  └─ GetResponseData<T>(): direct cast (T)RawResponseData
│     Protocol already deserialized to correct type via SignalDataType
└─ No match (broadcast):
   └─ abstract MessageReceived(tag, signalParams, object data).Forget()

Request pooling: SignalRRequestModel via SignalRRequestModelPool (ObjectPool + IResettable).

Parameter Deserialization (Server)

Server calls signalParams.GetParameterValues(paramInfos):

GetParameterValues(ParameterInfo[]):
  1. byte[] → BinaryTo<byte[][]>() (cached in _parameterValues)
  2. For each: byte[][i].BinaryTo(paramInfos[i].ParameterType)
  3. Trailing optional params filled with defaults
  Hub validates: missing required params throw ArgumentException.

Type-guided — each parameter individually serialized/deserialized with its concrete type, avoiding the object[] → dictionary problem of untyped binary deserialization.

Known concerns on parameter serialization (per-parameter overhead, AcBinary-only): SIGNALR_ISSUES.md under ACCORE-SIG-I-L5K3 and ACCORE-SIG-I-H8D6.

Response Patterns

Pattern Method Blocking
Sync wait PostAsync<T>(tag, data) Yes (60s timeout)
Async callback PostDataAsync(tag, data, async msg => {...}) No
Fire-and-forget PostDataAsync(tag, data, msg => {...}) No

Connection

  • WebSockets only (SkipNegotiation = true)
  • 30MB transport + 30MB application buffer
  • WithAutomaticReconnect() + WithStatefulReconnect()
  • Keepalive 60s, server timeout 180s

Diagnostics

AcSignalRClientBase.EnableBinaryDiagnostics = true — hex dump, header parsing, property enumeration.

Source Files

Component Path
Client base SignalRs/AcSignalRClientBase.cs
Binary protocol (base) SignalRs/AcBinaryHubProtocol.cs
Binary protocol (derived) SignalRs/AyCodeBinaryHubProtocol.cs
Protocol mode enum SignalRs/BinaryProtocolMode.cs
Tag attributes SignalRs/SignalMessageTagAttribute.cs
Base tags SignalRs/AcSignalRTags.cs
CRUD tags SignalRs/SignalRCrudTags.cs
SendToClientType SignalRs/SendToClientType.cs
Message types SignalRs/IAcSignalRHubClient.cs
Params class SignalRs/ISignalParams.cs
Serialization SignalRs/SignalRSerializationHelper.cs