Refactor: clarify and implement protocol serialization modes
Refactored binary protocol to support three explicit serialization/transport strategies via BinaryProtocolMode: Bytes (byte[]), Segment (zerocopy PipeWriter), and AsyncSegment (async PipeWriter with pipeline parallelism). Updated AcBinaryHubProtocol and AyCodeBinaryHubProtocol to select serialization/deserialization paths based on mode. Improved documentation and XML comments to describe each mode's behavior and performance. DI registration now explicitly selects AsyncSegment mode for AyCodeBinaryHubProtocol. Default remains Bytes mode. These changes clarify protocol mechanics and enable better performance tuning.
This commit is contained in:
parent
8ff75de55c
commit
83350e43f6
|
|
@ -120,7 +120,7 @@ Same cached chunk pattern (`GetMemory` → `TryGetArray` → direct array writes
|
|||
|
||||
### Usage
|
||||
|
||||
Selected via `BinaryProtocolMode.Segment` in `AcBinaryHubProtocol`. The protocol casts `IBufferWriter<byte> output` to `PipeWriter` (safe — SignalR always provides `PipeWriter`).
|
||||
Selected via `BinaryProtocolMode.AsyncSegment` in `AcBinaryHubProtocol`. The protocol casts `IBufferWriter<byte> output` to `PipeWriter` (safe — SignalR always provides `PipeWriter`).
|
||||
|
||||
```csharp
|
||||
AcBinarySerializer.Serialize(value, (PipeWriter)output, options) // AsyncPipeWriterOutput path
|
||||
|
|
|
|||
|
|
@ -412,14 +412,23 @@ public class AcBinaryHubProtocol : IHubProtocol
|
|||
return;
|
||||
}
|
||||
|
||||
// Flush BWO to pipe, then serialize directly to the pipe via AcBinarySerializer
|
||||
// Bytes mode: serialize to byte[], write through BWO (no FlushAndReset needed)
|
||||
if (_protocolMode == BinaryProtocolMode.Bytes)
|
||||
{
|
||||
var serialized = AcBinarySerializer.Serialize(value, _options);
|
||||
bw.WriteRaw(serialized.Length);
|
||||
bw.WriteBytes(serialized);
|
||||
return;
|
||||
}
|
||||
|
||||
// Segment / AsyncSegment: serialize directly to the pipe
|
||||
bw.FlushAndReset();
|
||||
|
||||
// Reserve arg length prefix directly on the pipe
|
||||
var argLenSpan = output.GetSpan(LengthPrefixSize);
|
||||
output.Advance(LengthPrefixSize);
|
||||
|
||||
var argBytes = _protocolMode == BinaryProtocolMode.Segment
|
||||
var argBytes = _protocolMode == BinaryProtocolMode.AsyncSegment
|
||||
? AcBinarySerializer.Serialize(value, (System.IO.Pipelines.PipeWriter)output, _options)
|
||||
: AcBinarySerializer.Serialize(value, output, _options);
|
||||
|
||||
|
|
@ -482,6 +491,13 @@ public class AcBinaryHubProtocol : IHubProtocol
|
|||
return SequenceToByteArray(argSlice.Slice(1));
|
||||
}
|
||||
|
||||
// Bytes mode: linearize to byte[] → ArrayBinaryInput (fastest deser, no segment overhead)
|
||||
if (_protocolMode == BinaryProtocolMode.Bytes)
|
||||
{
|
||||
var bytes = SequenceToByteArray(argSlice);
|
||||
return AcBinaryDeserializer.Deserialize(bytes, targetType, _options);
|
||||
}
|
||||
|
||||
return DeserializeFromSequence(argSlice, targetType, _options);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ namespace AyCode.Services.SignalRs
|
|||
var binaryOptions = AcBinarySerializerOptions.Default;
|
||||
binaryOptions.BufferWriterChunkSize = 4096;
|
||||
|
||||
return new AyCodeBinaryHubProtocol(binaryOptions);
|
||||
return new AyCodeBinaryHubProtocol(binaryOptions, BinaryProtocolMode.AsyncSegment);
|
||||
});
|
||||
|
||||
//Vagy ha az options-t is DI-ből:
|
||||
|
|
|
|||
|
|
@ -60,6 +60,13 @@ public class AyCodeBinaryHubProtocol : AcBinaryHubProtocol
|
|||
targetType = dataType;
|
||||
}
|
||||
|
||||
// Bytes mode: linearize to byte[] → ArrayBinaryInput (fastest deser, no segment overhead)
|
||||
if (_protocolMode == BinaryProtocolMode.Bytes)
|
||||
{
|
||||
var bytes = SequenceToByteArray(argSlice);
|
||||
return AcBinaryDeserializer.Deserialize(bytes, targetType, Options);
|
||||
}
|
||||
|
||||
return DeserializeFromSequence(argSlice, targetType, Options);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,44 @@
|
|||
namespace AyCode.Services.SignalRs;
|
||||
|
||||
/// <summary>
|
||||
/// Controls how the binary protocol transports serialized data over the network.
|
||||
/// Controls how the binary protocol serializes and transports data over the network.
|
||||
/// <para>
|
||||
/// <b>Bytes</b>: Serialize via <c>ArrayBinaryOutput</c> → single contiguous <c>byte[]</c>,
|
||||
/// written to the pipe as a raw blob. Deserialize via <c>SequenceReader.ToArray()</c> →
|
||||
/// <c>ArrayBinaryInput</c> (single buffer, <c>TryAdvanceSegment</c> always false → JIT-eliminated).
|
||||
/// Fastest individual ser/deser, no zerocopy, no pipeline overlap.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>Segment</b>: Serialize via <c>BufferWriterBinaryOutput</c> directly to the <c>PipeWriter</c>,
|
||||
/// chunk-by-chunk with a single <c>Flush</c> at the end. Deserialize via <c>SequenceBinaryInput</c>
|
||||
/// from multi-segment <c>ReadOnlySequence<byte></c> (lazy <c>TryGet</c> iteration, cross-boundary scratch).
|
||||
/// Zerocopy write, but no pipeline overlap.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>AsyncSegment</b>: Serialize via <c>AsyncPipeWriterOutput</c> directly to the <c>PipeWriter</c>,
|
||||
/// per-chunk <c>FlushAsync</c> sends data to the network during serialization. Deserialize via
|
||||
/// <c>PipeReaderBinaryInput</c> with on-demand <c>ReadAsync</c> (processes chunks as they arrive).
|
||||
/// Zerocopy write + pipeline parallelism (ser/network/deser overlap), highest roundtrip potential
|
||||
/// for large payloads.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public enum BinaryProtocolMode
|
||||
{
|
||||
/// <summary>Standard: serialize → egyben küld/fogad.</summary>
|
||||
/// <summary>
|
||||
/// ArrayBinaryOutput → byte[] → pipe. Deser: ToArray() → ArrayBinaryInput.
|
||||
/// Fastest ser/deser, no zerocopy, no pipeline overlap.
|
||||
/// </summary>
|
||||
Bytes = 0,
|
||||
|
||||
/// <summary>Szinkron segment streaming: flush Grow()-ban → chunk-onként hálózatra.</summary>
|
||||
/// <summary>
|
||||
/// BufferWriterBinaryOutput → PipeWriter, single Flush at end. Deser: SequenceBinaryInput (multi-segment).
|
||||
/// Zerocopy write, no pipeline overlap.
|
||||
/// </summary>
|
||||
Segment = 1,
|
||||
|
||||
/// <summary>Async segment streaming: async serializer + async output (jövő).</summary>
|
||||
/// <summary>
|
||||
/// AsyncPipeWriterOutput → PipeWriter, per-chunk FlushAsync. Deser: PipeReaderBinaryInput (on-demand ReadAsync).
|
||||
/// Zerocopy write + pipeline parallelism (ser/network/deser overlap).
|
||||
/// </summary>
|
||||
AsyncSegment = 2,
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -160,14 +160,14 @@ Typical overhead for 225KB payload with 4096-byte segments: ~224.5KB zero-copy,
|
|||
|
||||
## BinaryProtocolMode
|
||||
|
||||
`enum BinaryProtocolMode` — constructor parameter for `AcBinaryHubProtocol`, selects transport strategy:
|
||||
`enum BinaryProtocolMode` — constructor parameter for `AcBinaryHubProtocol`, selects serialization + transport strategy:
|
||||
|
||||
| Value | Behavior |
|
||||
|-------|----------|
|
||||
| `Bytes` (default) | Standard: serialize to `BufferWriterBinaryOutput`, single flush at end. |
|
||||
| `Segment` | Segment streaming: serialize to `AsyncPipeWriterOutput`, flush per 4096-byte chunk via `PipeWriter.FlushAsync().Forget()`. Network transfer overlaps with serialization. |
|
||||
| `AsyncSegment` | Reserved for future async serializer. |
|
||||
| Value | Serialize | Deserialize | Characteristics |
|
||||
|-------|-----------|-------------|-----------------|
|
||||
| `Bytes` (default) | `ArrayBinaryOutput` → `byte[]` → write to pipe as raw blob | `SequenceReader.ToArray()` → `ArrayBinaryInput` (single contiguous buffer, `TryAdvanceSegment` → false, JIT-eliminated) | Fastest individual ser/deser. No zerocopy. No pipeline overlap. |
|
||||
| `Segment` | `BufferWriterBinaryOutput` → directly to `PipeWriter`, chunk-by-chunk, single `Flush` at end | `SequenceBinaryInput` → multi-segment `ReadOnlySequence<byte>` (lazy `TryGet` iteration, cross-boundary scratch) | Zerocopy write. No pipeline overlap. |
|
||||
| `AsyncSegment` | `AsyncPipeWriterOutput` → directly to `PipeWriter`, per-chunk `FlushAsync().Forget()` with backpressure | `PipeReaderBinaryInput` → on-demand `ReadAsync`, processes chunks as they arrive from the network | Zerocopy write + pipeline parallelism (ser/network/deser overlap). Highest roundtrip potential for large payloads. |
|
||||
|
||||
In `Segment` mode, `WriteArgument` casts `IBufferWriter<byte> output` to `PipeWriter` and calls `AcBinarySerializer.Serialize(value, pipeWriter, options)` which uses `AsyncPipeWriterOutput` internally. The reader side currently uses the same `SequenceBinaryInput` path (SignalR delivers complete messages via `TryParseMessage`). `PipeReaderBinaryInput` is available for future direct-pipe deserialization.
|
||||
In `AsyncSegment` mode, `WriteArgument` casts `IBufferWriter<byte> output` to `PipeWriter` and calls `AcBinarySerializer.Serialize(value, pipeWriter, options)` which uses `AsyncPipeWriterOutput` internally. In `Bytes` and `Segment` mode, the standard `AcBinarySerializer.Serialize(value, output, options)` path is used (BWO on `IBufferWriter<byte>`).
|
||||
|
||||
**Source:** `AyCode.Services/SignalRs/AcBinaryHubProtocol.cs` (base), `AyCode.Services/SignalRs/AyCodeBinaryHubProtocol.cs` (consumer logic), `AyCode.Services/SignalRs/BinaryProtocolMode.cs` (enum)
|
||||
|
|
|
|||
Loading…
Reference in New Issue