# 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 args** — `SignalParams` AcBinary-serialized; `data` zero-copy to pipe via `AcBinarySerializer`, or raw `byte[]` via protocol fast-path. ``` Client: Server: AcSignalRClientBase AcWebSignalRHubBase ├─ HubConnection (WebSocket) ├─ Hub ├─ AyCodeBinaryHubProtocol ├─ DynamicMethodRegistry ├─ Pending request tracking ├─ Parameter deserialization └─ Response callbacks └─ Broadcast to other clients ``` ## Tag System Base tags in AyCode.Core: ```csharp 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): ```csharp 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:** ```csharp var orders = await client.PostAsync>(MyTags.GetOrders, companyId); // single param var result = await client.PostAsync(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` 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.SignalDataType` → `AcBinaryDeserializer.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()` performs direct cast. ## Request/Response Flow ### Client → Server ``` 1. PostAsync(tag, param) / PostAsync(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→await │ └─ GetResponseData(): 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() (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(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` |