AyCode.Core/AyCode.Services/docs/SIGNALR.md

151 lines
5.0 KiB
Markdown

# SignalR Client
Client-side SignalR transport: custom binary protocol, tag-based dispatch. Source: `SignalRs/`
> Server-side hub, session, broadcast: `AyCode.Services.Server/docs/SIGNALR_SERVER.md`
> DataSource collection: `AyCode.Services.Server/docs/SIGNALR_DATASOURCE.md`
## Design
Single hub method, tag-based dispatch:
```
Client ──OnReceiveMessage(tag, bytes, requestId)──► Server
Client ◄──OnReceiveMessage(tag, bytes, requestId)── Server
```
Tag (int) determines server method. All calls go through `OnReceiveMessage`.
```
Client: Server:
AcSignalRClientBase AcWebSignalRHubBase<TTags, TLogger>
├─ HubConnection (WebSocket) ├─ Hub<IAcSignalRHubItemServer>
├─ AcBinaryHubProtocol ├─ 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<List<Order>>(MyTags.GetOrders, companyId);
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
Custom `IHubProtocol` (`"acbinary"`), replaces JSON. Zero-copy via `BufferWriterBinaryOutput` standalone mode. `byte[]` args bypass serializer.
> Wire format, argument framing, dual BWO pattern, length prefix patching: `SIGNALR_BINARY_PROTOCOL.md`
### Response Message
`SignalResponseDataMessage`:
| Field | Type | Purpose |
|-------|------|---------|
| `MessageTag` | int | Operation tag |
| `Status` | SignalResponseStatus | Success/Error |
| `ResponseData` | byte[] | Serialized payload |
| `DataSerializerType` | AcSerializerType | Binary or Json |
Binary (default): `AcBinarySerializer.ToBinary(data)`. JSON fallback: `ToJson``GzipHelper.Compress`.
## Request/Response Flow
### Client → Server
```
1. PostAsync<T>(tag, postData) / PostDataAsync(tag, data, callback)
2. CreatePostMessage(postData):
├─ Primitives/strings/enums/value types → IdMessage
└─ Complex → SignalPostJsonDataMessage<T> ⚠️ JSON-in-Binary tech debt
3. SerializeToBinary(message)
4. HubConnection.SendAsync("OnReceiveMessage", tag, bytes, requestId)
5. AcBinaryHubProtocol frames on wire
```
### Server → Client
```
OnReceiveMessage(tag, bytes, requestId)
├─ Matching requestId in pending dict:
│ ├─ DeserializeFromBinary<SignalResponseDataMessage>(bytes)
│ ├─ Route: null→sync wait, Action→invoke, Func<Task>→await
│ └─ GetResponseData<T>(): Binary→BinaryTo<T>(), JSON→Decompress→Deserialize
└─ No match (broadcast):
└─ abstract MessageReceived(tag, bytes).Forget()
```
Request pooling: `SignalRRequestModel` via `SignalRRequestModelPool` (ObjectPool + IResettable).
## 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.
## Tech Debt
**JSON-in-Binary:** client→server wraps params in JSON inside binary envelope (`SignalPostJsonDataMessage`). Do NOT fix as side effect — requires coordinated cross-project changes.
## Source Files
| Component | Path |
|-----------|------|
| Client base | `SignalRs/AcSignalRClientBase.cs` |
| Binary protocol | `SignalRs/AcBinaryHubProtocol.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` |
| Serialization | `SignalRs/SignalRSerializationHelper.cs` |