AyCode.Core/AyCode.Services.Server/docs/SIGNALR_SERVER.md

105 lines
4.0 KiB
Markdown

# SignalR Server
Server-side SignalR hub infrastructure: method dispatch, session management, broadcast, and diagnostics. Source: `SignalRs/` in this project.
> For client-side transport (tags, wire protocol, client base) see [`AyCode.Services/docs/SIGNALR.md`](../../AyCode.Services/docs/SIGNALR.md).
> For the DataSource collection see [`SIGNALR_DATASOURCE.md`](SIGNALR_DATASOURCE.md).
## Server Processing
```
6. OnReceiveMessage(tag, bytes, requestId)
7. DynamicMethodRegistry.GetMethodByMessageTag(tag) ← ConcurrentDictionary lookup
8. DeserializeParameters(bytes):
├─ DeserializeFromBinary<SignalPostJsonMessage>() ← unwrap Binary envelope
├─ IdMessage format? → parse each Ids[i] as JSON per parameter type
└─ Complex object? → json.JsonTo(paramType) ⚠️ tech debt: JSON parse
9. MethodInfo.InvokeMethod(instance, params) ← unwraps Task/ValueTask
10. CreateResponseMessage(tag, Success, result) ← pure Binary serialization
11. ResponseToCaller(tag, message, requestId)
12. If SendToOtherClientType != None:
└─ SendMessageToOthers(sendToOtherClientTag, result) ← uses sendToOtherClientTag, not messageTag
```
## Dynamic Method Dispatch
See also: [`AyCode.Models.Server/DynamicMethods/README.md`](../../AyCode.Models.Server/DynamicMethods/README.md)
### Server-Side Lookup
```
1. OnReceiveMessage(tag=100, bytes, requestId)
2. DynamicMethodRegistry.GetMethodByMessageTag(100)
├─ Check static ConcurrentDictionary<int, (Type, AcMethodInfoModel)?> cache
├─ Hit? → find instance of cached Type from registered instances
├─ Miss? → scan all registered instances' methods for [SignalR(100)]
│ cache the result (including negative = null)
└─ Return (instance, methodInfoModel) or null
3. AcMethodInfoModel contains:
├─ MethodInfo (the method to invoke)
├─ SignalRAttribute (tag, sendToOtherClientTag, sendToOtherClientType)
└─ ParamInfos[] (ParameterInfo for deserialization)
```
The `DynamicMethodRegistry` uses a static `ConcurrentDictionary` for the global tag→method cache.
### Registration
The hub registers service instances during initialization:
```csharp
DynamicMethodRegistry.Register(myService); // scans [SignalR(tag)] methods lazily
DynamicMethodRegistry.Register(anotherService);
```
Reflection runs lazily per tag on first request, then results are cached statically.
## Session Management
`AcSessionService<TSessionItem, TSessionItemId>` tracks connected clients:
```csharp
ConcurrentDictionary<TSessionItemId, TSessionItem> Sessions
```
`IAcSessionItem<TSessionItemId>` requires `SessionId` property. Used for targeting messages to specific users/connections.
## Broadcast Service
`AcSignalRSendToClientService<THub, TTags, TLogger>` provides server-push methods:
| Method | Target |
|--------|--------|
| `SendMessageToAllClients` | All connected |
| `SendMessageToConnection(connectionId)` | Single connection |
| `SendMessageToUser(userId)` | User (all connections) |
| `SendMessageToUsers(userIds)` | Multiple users |
All messages wrapped in `SignalResponseDataMessage` → binary serialized → `OnReceiveMessage`.
## Hub Events
- `OnConnectedAsync()` — log connection
- `OnDisconnectedAsync(exception)` — log disconnection, cleanup session
## Diagnostics
Enable with `AcWebSignalRHubBase.EnableBinaryDiagnostics = true`.
Logs: hex dump (500 byte sample), header parsing (version, marker), property count + names via VarUInt reading.
`SignalResponseDataMessage.DiagnosticLogger` — per-response logging: target type info, property list, inheritance chain, hex dump.
## Key Source Files
| Component | Path |
|-----------|------|
| Hub base | `SignalRs/AcWebSignalRHubBase.cs` |
| Session service | `SignalRs/AcSessionService.cs` |
| Broadcast service | `SignalRs/AcSignalRSendToClientService.cs` |
| Logger hub | `SignalRs/AcLoggerSignalRHub.cs` |
| Tracking helpers | `SignalRs/TrackingItemHelpers.cs` |
| Dynamic dispatch | `AyCode.Models.Server/DynamicMethods/AcDynamicMethodRegistry.cs` |