diff --git a/CHANGELOG.md b/CHANGELOG.md index a36417d..b7624a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ - **Soak 目标矩阵([#66](https://github.com/CSJ608/StreamFrame/issues/66))**:显式选择 Ubuntu net8.0/net10.0、Windows net8.0/net10.0/net48,避免 Ubuntu 启动 .NET Framework 测试;job 名、详细日志与始终尝试上传的 TRX 产物按 OS/TFM 区分,测试断言失败继续使 job 失败。普通 CI 必需检查名不变。 ### 变更 +- **发版前文档与注释核对清理**:① 修正 `StreamFrame.Protocols.Xml` 包描述与 DESIGN 中的序列化器旧说法——实现为 XmlReader→XDocument 解码、XmlWriter 编码,从未使用 XmlSerializer;② 删除 `ConnectionMetrics` 关于 netstandard2.0 指标 no-op 兜底的陈旧注释(实际经 `System.Diagnostics.DiagnosticSource` 包提供同款 API,与 csproj/README 一致);③ XML 注释统一全中文——移除 #69 批次遗留的 11 处英文复述行(IStreamConnection/StreamConnectionOptions/IFramer/IStreamingFramer/ICodec),并去掉 `StxEtxFramer` 文档中的内部项目名引用;④ `IFrameDiscardReporting` 措辞修正(该接口由定界器实现,"第三方 codec"→"第三方定界器");⑤ DESIGN 项目结构树补齐现有文件(Diagnostics/Compatibility/RetryDelayScheduler/会话感知类型/bench 与两个 samples),demo 场景数三→五;⑥ CHANGELOG 2.1.0 段去重(新增/变更四处重复条目;经 tag 区间核实 LOGO 随本版本生效为正确说法,错挂的 social preview 句删除——该资产实为 2.2.1 引入)并移除空「暂无」段;⑦ README.en 的 JSON 最简驱动示例从「安装」移至「Codec」小节,与中文版结构对齐。不改变任何代码行为。 - 依赖升级(Dependabot,[PR #60](https://github.com/CSJ608/StreamFrame/pull/60)、[PR #91](https://github.com/CSJ608/StreamFrame/pull/91)、[PR #98](https://github.com/CSJ608/StreamFrame/pull/98),重复的 [#92](https://github.com/CSJ608/StreamFrame/pull/92) 已关闭):① CI Actions——soak 工作流 `actions/checkout` v4→v7、`actions/upload-artifact` v4→v7(全部工作流对齐 Node 24 世代,Node 20 弃用告警清零);② 进包依赖(patch)——`System.IO.Pipelines` / `System.Threading.Channels` / `Microsoft.Bcl.AsyncInterfaces` / `Microsoft.Extensions.Logging.Abstractions` / `System.Diagnostics.DiagnosticSource` 10.0.11→10.0.12;③ 工具链(不进包)——`Meziantou.Analyzer` 3.0.189→3.0.294、`coverlet.collector` 10.0.1→10.1.0、`Microsoft.NET.Test.Sdk` 18.9.0→18.10.1。 - **会话对象化与拆除分段([#68](https://github.com/CSJ608/StreamFrame/issues/68) PR-2)**:会话五元组(socket/Pipe/取消源/接收、解码、发送三任务/编号纪元/建立时刻)收敛为不可变的内部 `Session` 对象——会话任务只操作所属会话的资源(接收循环与发送 worker 绑定本会话 socket,不再读"当前值"字段),直连迁移与拆除竞速中新旧会话互不干扰;拆除只关本会话的 socket(`_socket` 字段降级为诊断/交接镜像)。会话拆除分段:锁内仅快速中止(取消令牌 + **先关 socket 解锁阻塞 I/O**——netfx 上取消令牌无法中止已提交的接收),有界等待(≤2s)移到锁外——高并发重连/停机不再在会话锁上排队,netfx 拆除收敛加快;停机/重连的总阻塞时长与公共 API 的同步/异步行为不变。全套测试(139+139+131)多轮全绿;同机同口径基准抽检(OneWayThroughput vs issue-70 基线):框架路径与 DirectTcp 对照同向漂移(环境噪声),未观测框架特有劣化,每操作分配无系统性增长。 - **状态机与所有权规范([#68](https://github.com/CSJ608/StreamFrame/issues/68) PR-1)**:DESIGN 增补权威清单——状态转换全表与发布段/通知段规则、资源所有权表(会话 socket/监听器/会话五元组/消息通道)、锁与同步原语清单、允许重入点(回调内 Reconnect/Dispose、停机后调用、迟到旧会话任务)及已知限制(拆除同步等待持锁、`_socket` 字段语义——指向后续内部 Session 对象重构);与新增的 `StateOwnershipBaselineTests` 共同构成 #68 会话所有权重构的行为基线。 @@ -108,9 +109,6 @@ - **XML 文档随 NuGet 包发布**(`GenerateDocumentationFile`):此前全量中文 XML 注释从未进包,用户在 IDE 中没有 IntelliSense;现以 `` 补齐全部接口实现成员的文档引用,并由 `TreatWarningsAsErrors` 强制今后不出现未文档化的公共 API。 - 基准测试扩容:`CodecBenchmarks`(XmlDocumentCodec 典型报文编解码开销)与 `EndToEndBenchmarks`(真实 TCP 回环的单向吞吐(双 framer)与往返延迟);基准项目纳入解决方案(IDE 可见,随解决方案构建;`dotnet test` 依 IsTestProject 机制自动跳过)。 -### 修复 -- 暂无 - ## [2.1.0] - 2026-08-27 ### 新增 @@ -123,13 +121,6 @@ ### 变更 - CI Actions 升级到 Node 24 运行时:`actions/checkout@v7`、`actions/setup-dotnet@v6`、`softprops/action-gh-release@v3`(消除 Node 20 弃用告警)。 - `GetMessages`/发送 worker 改用 `WaitToReadAsync+TryRead` 等价循环(ns2.0 的 Channels 包无 `ReadAllAsync`;各目标行为一致)。 -- 帧定界基准测试 `bench/StreamFrame.Benchmarks`(BenchmarkDotNet):LengthPrefix/StxEtx 的流式 vs 纯函数编码对比与切帧吞吐,为"流式零拷贝"说法提供可复现数据。 -- 项目 LOGO(`docs/logo/`,SVG 母版 + PNG),README 双语版接入展示、两个 NuGet 包启用包图标(随下个版本发布生效)。 -- README 新增 CI 构建状态徽章;main 分支保护(必须走 PR 且 CI 通过、禁 force push/删除、强制线性历史)。 -- 英文版 README(`README.en.md`,与中文版互链),便于国际用户检索与阅读。 - -### 修复 -- 暂无 ## [2.0.0] - 2026-08-27 diff --git a/README.en.md b/README.en.md index 0105d67..71b22a6 100644 --- a/README.en.md +++ b/README.en.md @@ -34,28 +34,6 @@ XML message driver (optional): dotnet add package StreamFrame.Protocols.Xml ``` -Minimal JSON driver (System.Text.Json, span-written, AOT-safe): - -```csharp -sealed class SystemTextJsonCodec : ICodec -{ - public static readonly SystemTextJsonCodec Instance = new(); - - public JsonElement Decode(in ReadOnlySequence frame, CancellationToken ct = default) - { - using var document = JsonDocument.Parse(frame.ToArray()); - return document.RootElement.Clone(); // Own data beyond document disposal. - } - - public void Encode(JsonElement message, IBufferWriter writer, CancellationToken ct = default) - { - var raw = message.GetRawText(); // serialized form (non-ASCII already escaped) - var span = writer.GetSpan(Encoding.UTF8.GetMaxByteCount(raw.Length)); - writer.Advance(Encoding.UTF8.GetBytes(raw, span)); - } -} -``` - ## Quick start This is a **single-connection device integration library**. A passive listener serves one client at a time; `AcceptFirstClientOnly = false` keeps the listener open without adding multi-client handling. The snippets below represent separate processes/roles, with a host-provided `ct`. See the production configuration below and dispose connections with `await using`. @@ -111,7 +89,27 @@ public interface ICodec } ``` -**Onboarding a new device = writing one driver**: implement `ICodec`, define your message type, pick a framer — everything else is reused. See `StreamFrame.Protocols.Xml` for the official example. +**Onboarding a new device = writing one driver**: implement `ICodec`, define your message type, pick a framer — everything else is reused. See `StreamFrame.Protocols.Xml` for the official example. A minimal JSON-message driver (System.Text.Json, span-written, AOT-safe): + +```csharp +sealed class SystemTextJsonCodec : ICodec +{ + public static readonly SystemTextJsonCodec Instance = new(); + + public JsonElement Decode(in ReadOnlySequence frame, CancellationToken ct = default) + { + using var document = JsonDocument.Parse(frame.ToArray()); + return document.RootElement.Clone(); // Own data beyond document disposal. + } + + public void Encode(JsonElement message, IBufferWriter writer, CancellationToken ct = default) + { + var raw = message.GetRawText(); // serialized form (non-ASCII already escaped) + var span = writer.GetSpan(Encoding.UTF8.GetMaxByteCount(raw.Length)); + writer.Advance(Encoding.UTF8.GetBytes(raw, span)); + } +} +``` ### Connection (`IStreamConnection`) — the transport diff --git a/docs/DESIGN.md b/docs/DESIGN.md index 3482d3c..77f52cf 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -11,28 +11,41 @@ ``` StreamFrame/ ├─ StreamFrame.slnx -├─ src/StreamFrame/ # 核心库(无业务依赖);所有公共类型都在 StreamFrame 命名空间 -│ ├─ ConnectionState.cs # Connecting / Connected / Retry / Disconnected -│ ├─ IStreamConnection.cs # IStreamConnection +├─ src/StreamFrame/ # 核心库(无业务依赖);所有公共类型都在 StreamFrame 命名空间 +│ ├─ ConnectionState.cs # Connecting / Connected / Retry / Disconnected +│ ├─ IStreamConnection.cs # IStreamConnection +│ ├─ ISessionAwareStreamConnection.cs # 会话感知收发(可选能力接口) │ ├─ StreamConnectionOptions.cs +│ ├─ DecodeErrorPolicy.cs # 帧内容解码失败策略(Disconnect / SkipFrame) +│ ├─ FrameErrorKind.cs / FrameErrorEventArgs.cs # 帧层诊断事件 +│ ├─ SessionExpiredException.cs # 会话绑定发送失效异常 +│ ├─ SessionMessage.cs # 带会话编号的接收元素 │ ├─ Framing/ -│ │ ├─ IFramer.cs # 帧定界接口(EncodeFrame/TryDecodeFrame/MaxPayloadBytes) -│ │ ├─ LengthPrefixFramer.cs # 4 字节大端长度头 -│ │ ├─ StxEtxFramer.cs # STX 0x02 … ETX 0x03(plain,不做转义) -│ │ ├─ IStreamingFramer.cs # 可选:流式单缓冲编码(Begin/EndFrame) -│ │ ├─ IFrameDiscardReporting.cs # 可选:重同步丢弃字节上报 -│ │ └─ IWrittenBufferWriter.cs # 流式编码用的可回写缓冲抽象 +│ │ ├─ IFramer.cs # 帧定界接口(EncodeFrame/TryDecodeFrame/MaxPayloadBytes) +│ │ ├─ LengthPrefixFramer.cs # 4 字节大端长度头 +│ │ ├─ StxEtxFramer.cs # STX 0x02 … ETX 0x03(plain,不做转义) +│ │ ├─ IStreamingFramer.cs # 可选:流式单缓冲编码(Begin/EndFrame) +│ │ ├─ IFrameDiscardReporting.cs # 可选:重同步丢弃字节上报 +│ │ └─ IWrittenBufferWriter.cs # 流式编码用的可回写缓冲抽象 │ ├─ Codec/ -│ │ ├─ ICodec.cs # ICodec(驱动实现) -│ │ ├─ BufferWriterStream.cs # IBufferWriter→Stream,供 XmlWriter 直写 -│ │ └─ PooledBufferWriter.cs # 内部:ArrayPool 租用的 IWrittenBufferWriter -│ └─ Connection/ -│ ├─ StreamConnection.cs # 连接实现(双模式 + 状态机 + 重连 + 生命周期) -│ ├─ FrameDecoder.cs # 解码循环(切帧 + codec 解码) -│ └─ SessionFaultException.cs # 会话级故障(内部) -├─ src/StreamFrame.Protocols.Xml/ # 官方示例驱动:XML 报文 -├─ test/StreamFrame.Tests/ # xUnit 单测 -└─ samples/StreamFrame.Demo/ # 控制台端到端 demo +│ │ ├─ ICodec.cs # ICodec(驱动实现) +│ │ ├─ BufferWriterStream.cs # IBufferWriter→Stream,供 XmlWriter 直写 +│ │ └─ PooledBufferWriter.cs # 内部:ArrayPool 租用的 IWrittenBufferWriter +│ ├─ Connection/ +│ │ ├─ StreamConnection.cs # 连接实现(双模式 + 状态机 + 重连 + 生命周期 + Session 对象) +│ │ ├─ FrameDecoder.cs # 解码循环(切帧 + codec 解码) +│ │ ├─ RetryDelayScheduler.cs # 内部:重连退避调度(倍增封顶 + 抖动) +│ │ └─ SessionFaultException.cs # 会话级故障(内部) +│ ├─ Diagnostics/ +│ │ └─ ConnectionMetrics.cs # 内部:内置指标(System.Diagnostics.Metrics) +│ └─ Compatibility/ +│ └─ NetStandard20Shims.cs # netstandard2.0 兼容层(Task.WaitAsync / Socket Memory 重载) +├─ src/StreamFrame.Protocols.Xml/ # 官方示例驱动:XML 报文 +├─ test/StreamFrame.Tests/ # xUnit 单测 +├─ samples/StreamFrame.Demo/ # 控制台端到端 demo(五场景) +├─ samples/StreamFrame.Ownership/ # 缓冲所有权示例(源文件纳入测试工程三目标编译) +├─ samples/StreamFrame.AotSmoke/ # NativeAOT 发布/运行冒烟(CI 非必需检查) +└─ bench/StreamFrame.Benchmarks/ # BenchmarkDotNet 基准 ``` 命名空间约定:公共类型统一收敛在根命名空间 `StreamFrame`(2.0 起不再有 `StreamFrame.Abstractions`),用户只需一个 `using StreamFrame;`;目录仅按角色组织文件。 @@ -72,7 +85,7 @@ public interface ICodec } ``` -驱动=实现 codec + 定义业务消息类。官方示例:`StreamFrame.Protocols.Xml.XmlDocumentCodec`(XmlSerializer 解码 / BufferWriterStream+XmlWriter 编码)。用户按此骨架替换即可支持自定义二进制协议。 +驱动=实现 codec + 定义业务消息类。官方示例:`StreamFrame.Protocols.Xml.XmlDocumentCodec`(XmlReader→XDocument 解码 / BufferWriterStream+XmlWriter 编码)。用户按此骨架替换即可支持自定义二进制协议。 ### 3. 连接核心(客户端/服务端双模式) @@ -209,6 +222,6 @@ socket --ReceiveAsync--> pipe.Writer --[Pipe]--> FrameDecoder.RunAsync - XML codec:往返、DTD 拒绝(XXE 防御) - 连接端到端(真实 TCP 回环):断线重连后消息送达、解码失败断线/跳帧、调试钩子抛异常会话存活、接收空闲超时、发送失败重连、未完成帧超限断线、单客户端拒绝 - 易用性与生命周期:等待连接就绪(跨重试/挂起/Dispose 取消)、Start 重入防护、选项构造校验、慢消费者有界接收队列、Start 令牌取消停机(Disconnected 终态/通道关闭)、对端地址语义、双栈监听 IPv4 接入 -- 端到端:`samples/StreamFrame.Demo` 演示 XML+LengthPrefix、文本+STX/ETX、断线重连三场景 +- 端到端:`samples/StreamFrame.Demo` 演示五场景:XML+LengthPrefix、文本+STX/ETX、断线自动重连、心跳保活+接收空闲超时、会话感知收发 运行:`dotnet build StreamFrame.slnx`、`dotnet test`、`dotnet run --project samples/StreamFrame.Demo` diff --git a/src/StreamFrame.Protocols.Xml/StreamFrame.Protocols.Xml.csproj b/src/StreamFrame.Protocols.Xml/StreamFrame.Protocols.Xml.csproj index 594dcf0..cdc2ba6 100644 --- a/src/StreamFrame.Protocols.Xml/StreamFrame.Protocols.Xml.csproj +++ b/src/StreamFrame.Protocols.Xml/StreamFrame.Protocols.Xml.csproj @@ -33,7 +33,7 @@ StreamFrame.Protocols.Xml 2.6.0 - StreamFrame 的 XML 报文示例驱动:XmlDocumentCodec(XmlSerializer / XmlWriter 编解码)。 + StreamFrame 的 XML 报文示例驱动:XmlDocumentCodec(XmlReader/XDocument 解码、XmlWriter 编码)。 Marci MIT icon.png diff --git a/src/StreamFrame/Codec/ICodec.cs b/src/StreamFrame/Codec/ICodec.cs index 01e293f..8d3afb6 100644 --- a/src/StreamFrame/Codec/ICodec.cs +++ b/src/StreamFrame/Codec/ICodec.cs @@ -8,12 +8,9 @@ namespace StreamFrame; /// 由驱动实现——例如 XML 驱动把负载解析为强类型消息,自定义二进制协议驱动手写字节布局。 /// /// -/// 并发 / Concurrency: 同一会话的发送 worker 串行编码,但 Encode 与 Decode 可并行。 +/// 并发:同一会话的发送 worker 串行编码,但 Encode 与 Decode 可并行。 /// 会话拆除只作有界等待,旧任务可能与新会话重叠(包括 Decode/Decode,阻塞的 Encode 也不能假定已退出)。 /// 实现应无共享可变状态或自行同步;跨连接共享实例的调用方负责确认实现支持并发,框架不提供实例级锁。 -/// A send worker serializes its own calls, not the codec instance. Encode/Decode and old/new -/// session calls can overlap. Use stateless implementations or synchronization; callers sharing -/// an instance across connections must ensure it is safe. Avoid blocking synchronous methods. /// /// 业务消息类型,贯穿一条连接。 public interface ICodec @@ -23,8 +20,6 @@ public interface ICodec /// frame 仅供同步调用期间借用;返回对象(包括嵌套字段)必须独立拥有数据。 /// 不得未经拷贝保留 frame、其切片或底层 Memory;管线随后推进/释放,异步消费者可能更晚读取。 /// ReadOnlyMemory 只限制写入接口,不转移所有权。本接口不转移管线内存所有权。 - /// Borrow frame only during this synchronous call. Copy retained bytes into message-owned storage; - /// never return pipeline slices, even as ReadOnlyMemory. No pipeline ownership transfer is provided. /// TMessage Decode(in ReadOnlySequence frame, CancellationToken ct = default); @@ -32,8 +27,6 @@ public interface ICodec /// /// 必须同步完成写入;不得保留 writer 或其缓冲供返回后/异步使用,也不得释放框架拥有的 writer。 /// 调用方应保持已入队消息及其底层数据不变,普通 SendAsync 完成仅表示入队,编码可能尚未开始。 - /// Complete writes synchronously. Do not retain/dispose the writer or retain its buffers after return. - /// Queued messages must remain unchanged: SendAsync completion does not mean encoding has finished. /// void Encode(TMessage message, IBufferWriter writer, CancellationToken ct = default); } diff --git a/src/StreamFrame/Diagnostics/ConnectionMetrics.cs b/src/StreamFrame/Diagnostics/ConnectionMetrics.cs index ebc6c34..24ae95f 100644 --- a/src/StreamFrame/Diagnostics/ConnectionMetrics.cs +++ b/src/StreamFrame/Diagnostics/ConnectionMetrics.cs @@ -6,7 +6,7 @@ namespace StreamFrame; /// 连接级内置指标( 名 "StreamFrame",零外部依赖): /// 帧收发计数、字节收发计数、重连次数、会话时长、发送队列水位。生产部署用 /// MeterListener / OpenTelemetry 订阅 "StreamFrame" 即可观测,不接入则开销为每次记录 -/// 数纳秒级;netstandard2.0 目标为 no-op 兜底(无 Metrics API,记录全部空操作)。 +/// 数纳秒级;netstandard2.0 目标经 System.Diagnostics.DiagnosticSource 包获得同款 API(netfx 运行时可用)。 /// /// 标签:单一 "endpoint"(构造时的 ip:port)——每条连接的地址固定,标签基数受控。 /// diff --git a/src/StreamFrame/Framing/IFrameDiscardReporting.cs b/src/StreamFrame/Framing/IFrameDiscardReporting.cs index c1ed45a..2c4ec50 100644 --- a/src/StreamFrame/Framing/IFrameDiscardReporting.cs +++ b/src/StreamFrame/Framing/IFrameDiscardReporting.cs @@ -11,7 +11,7 @@ namespace StreamFrame; /// 正常切帧时 discarded 为空。 /// /// 连接层检测到本接口后,会把这些字节通过 FrameError 事件(Kind=DiscardedByResync) -/// 交给上层调试;不实现本接口的第三方 codec 不受影响,只是没有丢弃上报。 +/// 交给上层调试;不实现本接口的第三方定界器不受影响,只是没有丢弃上报。 /// public interface IFrameDiscardReporting { diff --git a/src/StreamFrame/Framing/IFramer.cs b/src/StreamFrame/Framing/IFramer.cs index 3f705e8..e25fb61 100644 --- a/src/StreamFrame/Framing/IFramer.cs +++ b/src/StreamFrame/Framing/IFramer.cs @@ -10,8 +10,6 @@ namespace StreamFrame; /// /// 同连接编码与切帧可并行,新旧会话也可能重叠;跨连接共享实例前须确认线程安全。 /// 应无共享可变解析状态或自行同步,框架不提供实例级锁。 -/// Encode and decode may overlap, as may old/new sessions. Shared instances must be thread-safe; -/// keep parsing state local or synchronize it yourself. The framework does not lock the instance. /// public interface IFramer { @@ -21,7 +19,6 @@ public interface IFramer /// /// 对完整的帧内负载(不含定界字节)加帧定界并写入 。 /// payload、writer 及其缓冲仅供同步调用期间借用,不得留存供异步使用或释放 writer。 - /// Borrow payload/writer only for this call; do not retain buffers or dispose the writer. /// void EncodeFrame(ReadOnlySpan payload, IBufferWriter writer); @@ -36,8 +33,6 @@ public interface IFramer /// 连接层在长度减少时继续解析剩余缓冲,未消费任何字节时等待更多输入。 /// 返回 true 时必须消费完整帧的线上字节(包括空负载帧的定界字节)。 /// payload 可以是输入切片,供连接层紧接着同步 Decode;实现不得留存 buffer/payload。 - /// Payload may borrow the input for the immediately following synchronous codec call; - /// the framer must not retain either sequence beyond this call. /// bool TryDecodeFrame(ref ReadOnlySequence buffer, out ReadOnlySequence payload); } diff --git a/src/StreamFrame/Framing/IStreamingFramer.cs b/src/StreamFrame/Framing/IStreamingFramer.cs index 585d721..8ddf664 100644 --- a/src/StreamFrame/Framing/IStreamingFramer.cs +++ b/src/StreamFrame/Framing/IStreamingFramer.cs @@ -19,8 +19,6 @@ namespace StreamFrame; /// 沿用 IFramer 的并发与借用约束。BeginFrame/EndFrame 的配对状态应存于本次 writer, /// 不应使用实例字段记录当前帧;两个调用之间可穿插解码或其它会话/连接的调用。 /// writer 及缓冲不得被保留供方法返回后使用;EndFrame 使用调用方重新传入的 writer。 -/// IFramer concurrency rules apply. Keep pair state in the supplied writer, not a current-frame field: -/// other decode/session/connection calls may interleave. Never retain writer/buffers after a method returns. /// public interface IStreamingFramer : IFramer { diff --git a/src/StreamFrame/Framing/StxEtxFramer.cs b/src/StreamFrame/Framing/StxEtxFramer.cs index 8a48b34..3645266 100644 --- a/src/StreamFrame/Framing/StxEtxFramer.cs +++ b/src/StreamFrame/Framing/StxEtxFramer.cs @@ -8,7 +8,7 @@ namespace StreamFrame; /// 注意:plain 模式要求负载不得包含 0x02/0x03,否则会被误判为帧边界。 /// 适合 XML / 纯文本等已知安全的负载;二进制负载请改用 。 /// -/// 边界处理(与 SamSung 一致): +/// 边界处理: /// /// 在待定帧的 ETX 收到之前若再次遇到 STX,则丢弃当前部分帧并从新 STX 处重新开始。 /// 无前导 STX 的孤立 ETX 被视为噪声字节跳过。 diff --git a/src/StreamFrame/IStreamConnection.cs b/src/StreamFrame/IStreamConnection.cs index 4fe6a4a..d48a25e 100644 --- a/src/StreamFrame/IStreamConnection.cs +++ b/src/StreamFrame/IStreamConnection.cs @@ -74,7 +74,6 @@ public interface IStreamConnection : IAsyncDisposable /// 等待连接进入 :已连接时立即完成;否则等到 /// 下一次连接成功、 取消、生命周期取消或连接 Dispose(任务以取消结束)。 /// 终态后的新等待同样以取消结束,即使未传调用方令牌。 - /// Lifetime cancellation/disposal also cancels future waits, even without a caller token. /// 替代轮询 或 Task.Delay 式等待。 /// Task WaitForConnectedAsync(CancellationToken ct = default); @@ -83,8 +82,6 @@ public interface IStreamConnection : IAsyncDisposable /// /// 完成不代表已编码、写入 socket 或远端确认。消息及底层数据应在入队后保持不变。 /// 未出队普通消息可由新会话续发;已出队发送失败的远端结果未知,不保证交付。 - /// Completion means enqueue only, not encoding, socket write or peer acknowledgement. - /// Keep queued data immutable; queued entries can continue in a new session, but delivery is not guaranteed. /// Task SendAsync(TMessage message, CancellationToken ct = default); diff --git a/src/StreamFrame/StreamConnectionOptions.cs b/src/StreamFrame/StreamConnectionOptions.cs index a5cd5a4..4bde987 100644 --- a/src/StreamFrame/StreamConnectionOptions.cs +++ b/src/StreamFrame/StreamConnectionOptions.cs @@ -22,8 +22,7 @@ public sealed class StreamConnectionOptions /// Socket 接收缓冲区大小(字节)。 public int SocketReceiveBufferSize { get; set; } = 65536; - /// 发送队列容量(消息数,非字节预算);超过容量时 SendAsync 将等待(背压)。 - /// Capacity counts messages, not bytes; full queues backpressure SendAsync. + /// 发送队列容量(消息数,非字节预算);超过容量时 SendAsync 将等待(背压)。 public int SendQueueCapacity { get; set; } = 1024; /// 编码缓冲区的初始大小(字节)。 @@ -77,7 +76,6 @@ public sealed class StreamConnectionOptions /// 设为正数后,消费慢时解码循环暂停读取(TCP 背压自然传导到对端),防止慢消费者撑爆内存。 /// 注意:消费完全停滞期间,会话拆除最多等待 2 秒(内部超时)后继续。 /// 容量按消息数而非字节计量;还需预算单条对象大小、处理中消息、等待入队的生产者及管线/socket 缓冲。 - /// Capacity counts messages, not bytes. Budget message size, in-flight work, waiting producers and I/O buffers too. /// public int ReceiveQueueCapacity { get; set; }