解决MySql布尔型新旧版本兼容问题,采用枚举来表示布尔型的数据表。由正向工程赋值
|
# 网络库架构
> 适用范围:NewLife.Core 的 `NewLife.Net`、`NewLife.Messaging`、`NewLife.Data`(数据包部分)与 `NewLife.Http`(服务端)。
> 目标形态:简单架构覆盖 80% 日常场景,剩余 20% 通过明确的扩展点留给下游。
> 配套专题:《数据包IPacket》《数据管道Pipe》《网络缓冲所有权架构》《消息协议栈》《高级二进制序列化》。
> **v12 过渡说明**:§1-§5 已按新协议栈更新;§6 处置表为 2026-09 历史收敛记录(现行结论见《消息协议栈》§7)。
## 1. 分层与数据通路
```
应用层 NetServer / NetSession / NetClient
↑ 消息与事件
协议层 IMessageCodec(SrmpCodec / LengthFieldCodec / SplitDataCodec / WebSocketCodec)
↑ 消息(IMessage)
帧泵 MessagePump(管道上定界与交付;头到齐即交付,体流式/内存统一)
↑ 字节流
缓冲层 Pipe(窗口 + 背压) | 每轮拥有句柄(SessionBase 轮末裁决)
↑
传输层 SessionBase / TcpSession / TcpServer / UdpServer / UdpSession
↑
接入面 Socket(内置监听与连接) | 外部 Socket 接管(客户端 TcpSession(Socket))
```
**接收数据流(简)**
1. Socket 读回调进入 `SessionBase.ProcessEvent`:缓冲自借出即包装为拥有句柄(`OwnerPacket`)挂在接收槽,轮末按引用计数裁决——`RefCount==1` 时句柄回挂接收槽、下一轮 `Resize` 重设窗口复用(缓冲原地不动),否则解绑换新(零 Rent/Return 快路径;归还只发生在释放槽句柄处)。
2. 连接型会话(`TcpSession`)把本轮数据追加进数据管道(`Pipe`,有界背压);UDP 数据报即完整帧,直接逐条分发。
3. 定界:协议模式由 `MessagePump` 从管道定界交付(头到齐即交付,体为内存视图或流式);UDP 一跳多帧无粘包处理。
4. 消息回流到会话:`Received` 事件 → `NetSession` → 可选的 `INetHandler`(如 `HttpSession`)→ `NetServer.Received`。
**接收模式(二选一)**:打开前设置 `AutoReceive`(`SessionBase`,默认 true)——true 自动启动接收环走事件推送(接收环运行期间 `Receive/ReceiveAsync` 抛出异常);false 只允许拉取直读。两模式互斥;拉取模式下可调用无参 `ReceiveAsync()` 手动转事件模式(单向,不回退)。WebSocket 握手发生在打开过程的接收环启动之前,直读天然合法。
**发送数据流**:`Send` → 协议构建整帧(`Build`/`BuildHeader` + 流式体)→ Socket(或 SSL `_Stream`)→ 网卡。
## 2. 80% 黄金用法
### 2.1 服务端 + 编解码
```csharp
class MyServer : NetServer<MySession> { }
class MySession : NetSession<MyServer>
{
protected override void OnReceive(ReceivedEventArgs e)
{
var pk = e.Packet; // 当前指令的数据帧
if (pk == null || pk.Length == 0) return;
Send(pk); // 回发
}
}
var server = new MyServer { Port = 0 };
server.Protocol = new SrmpCodec(); // 或 LengthFieldCodec / SplitDataCodec / CompressedCodec / 自定义
server.Start();
```
- 内置协议:`SrmpCodec`(新生命标准封包)、`LengthFieldCodec`(长度字段,MQTT 风格)、`SplitDataCodec`(分隔符)、`CompressedCodec`(压缩装饰器,可组合)。
- 也可以不定义会话子类,直接 `server.Received += ...` 集中处理。
### 2.2 客户端
```csharp
var client = new NetUri("tcp://127.0.0.1:1234").CreateRemote();
client.Protocol = new SrmpCodec();
client.Open();
client.Send("Hello".GetBytes());
```
- 请求-响应:`SendMessageAsync`,由会话匹配队列(按协议 `IMessageMatcher` 配对)完成应答配对;匹配是**交付阶段**能力,流式与数据报会话同口径(TCP 在消息泵交付时配对,UDP 在数据报交付时配对)。
- 断线重连等应用级能力可使用 `NetClient`。
- 纯拉取客户端(Modbus/短连接请求-响应风格):打开前设 `client.AutoReceive = false`,用 `Receive()/ReceiveAsync(ct)` 直读 Socket。
### 2.3 自定义协议
- 实现 `IMessageCodec`(`TryParse` 定界构造 / `Build` 整帧 / `BuildHeader` 头部);参考 `Messaging/SrmpCodec.cs` 与 `LengthFieldCodec.cs`。
- 协议实例须无状态(可跨连接共享);可经 `CompressedCodec` 等装饰器组合变换层(压缩/加密),无需多层管道。
### 2.4 流式协议(新协议推荐)
- 连接型会话(`TcpSession`,接口 `IStreamSession`)的 `Pipe` 懒建:`Pipe` 承载字节流窗口与背压,`MessagePump` 定界交付消息(头到齐即交付,体流式/内存统一,跨轮等待)。
- 出站方向:**默认直发 + 一把写锁**(四个入口共用)——`Send(IPacket/byte[]/Span)` 锁内同步直写(写完才返回,0 分配 0 拷贝,链式包散列写一次系统调用)、`SendAsync(IPacket)` 锁内异步直写(等待可写不占线程)、`SendAsync(Stream)` 分块读写(内存恒为一块,内核缓冲即背压)、
`SendFileAsync(path)` 交给内核零拷贝推文件(sendfile/TransmitFile;SSL 与低版本 TFM 降级为分块发送);慢对端由内核发送缓冲形成背压,需要排队/限流时在上层用 `Actor` 组合;批量场景可选走 `TcpSession.SendQueue`(可选第二出口:只异步 `SendQueuedAsync`、所有权转移、积压等待、泵批量写出并取同一把写锁、关闭不排空,见《数据管道Pipe》出站一节);消息协议头 + 流式体:`codec.BuildHeader(msg, length)` 声明长度后接 `SendAsync(Stream)`。
- 传输层大负载:HTTP 响应体 `HttpResponse.BodyStream`(可寻址流→Content-Length,未知长度→`Transfer-Encoding: chunked`;静态文件/嵌入资源已接入,大文件不物化);WebSocket 大帧经 `WebSocketCodec` 构建。
- 细节与示例见《数据管道Pipe》。
### 2.5 消息契约(IMessage / IMessageCodec)
- 消息契约 `IMessage`:`MessageKinds Kind` 四态(与 SRMP 头高 2 位对应,`Reply`/`OneWay` 便捷视图)、`Payload`(消息体句柄视图,流式为 null)、`Body`(内存视图或限长流式读取器)、`SetBody`/`BindBody`、`CreateReply`(应答上返回 null)、`Dispose`。
- 协议契约 `IMessageCodec`:`TryParse`(定界并构造消息,头部不足返回 null)、`Build`(整帧构建,转移语义:构建后消息不再持有体)、`BuildHeader`(头 + 流式体);无状态可跨连接共享。请求-响应配对经 `IMessageMatcher`(如 SRMP 按序列号)。
- 帧定界/构建完全由协议对象承担,消息不感知帧字节;可组合装饰器(如 `CompressedCodec`)实现变换层。并行处理见 `MaxConcurrency`。详见《消息协议栈》。
## 3. 20% 扩展点目录
| 深度 | 扩展方式 | 关键类型 |
|:----:|----------|----------|
| 1 | 自定义协议 | 实现 `IMessageCodec`;`server.Protocol = ...` 设置 |
| 2 | 自定义会话 | `NetServer<TSession>` / `NetSession<TServer>`;override `OnConnected / OnDisconnected / OnReceive` |
| 3 | 会话级业务处理器 | `INetHandler`(`NetServer.CreateHandler` 返回,如 HttpServer → HttpSession) |
| 4 | 请求响应匹配策略 | `IMatchQueue`(`DefaultMatchQueue.Add / Match` 可重载) |
| 5 | 接管外部 Socket | 客户端 `new TcpSession(socket)` + `Open()`(跳过连接直接收发);服务端暂无接管入口,对外部服务器对象用 `NetServer.AttachServer(ISocketServer)` 挂接(见下行) |
| 6 | 自定义传输 | `ITransport`(帧传输抽象,串口/自定义链路接入);`NetServer.AttachServer(ISocketServer)` 挂接自定义服务器 |
| 7 | 数据管道回调 | `TcpSession.CreatePipe` 重载(`IStreamSession`);`MessagePump.ReadAsync` |
## 4. 交付上下文契约(数据可带上传输对象)
- 协议层/处理器:事件参数(`ReceivedEventArgs`,裸模式含 `INetHandler` 旁路)可回溯到会话对象(`ISocketRemote`);裸 Socket 经 `ISocket.Client` 获取。
- 应用层:`NetSession.Session`(`ISocketSession`)→ `ISocket.Client` 获取底层 Socket;`Remote/Local` 等地址信息同源。
- 接管场景:`TcpSession(Socket)` 接管后,会话的 `Client` 即为调用方传入的 Socket;客户端接管会话的 `ISocketSession.Server` 为空。
- 原则:不新增平行的数据上下文对象;所有投递入口(`Received` 事件、`INetHandler`、流式消息体)都在会话作用域内执行,可回溯到会话与传输对象。
## 5. 帧引擎(v12 单引擎)
v12 已整删旧双引擎(`PacketCodec`/`PacketFramer`),统一为 `MessagePump`:管道上定界交付(头到齐即交付、小帧零拷贝快路径、大帧流式体、`MaxCache` 残余防护),协议经 `IMessageCodec` 无状态实现。历史双引擎选型与冻结决策见本文件 git 历史(2026-09-16 记录)。
## 6. 概念处置表(v12 收敛)
| 抽象 | 处置 | 状态 |
|------|------|:----:|
| `SessionBase` | 保留为 TCP/UDP 共用报文核心;字节流路径管道下放 `TcpSession` | ✅ 2026-09-17 |
| `SessionBase.MaxAsync` | 三义拆分删除:接收模式由 `AutoReceive` 二选一(true=接收环事件模式、false=拉取直读);并发待收数内部化(TCP 1,UdpServer 默认 CPU×1.6,`UdpServer.MaxAsync` 可调) | ✅ 2026-09-18 |
| 流式收发(`Pipe`/`SendAsync(Stream)`) | 从 `SessionBase` 全部下放 `TcpSession`(新接口 `IStreamSession`):轮数据投递并入 `OnPreReceive`、背压暂停并入 `OnReceiveAsync`、关闭收尾并入 `CloseAsync` 重写;基类不保留任何流式概念 | ✅ 2026-09-17 |
| 流式专有配置与消息泵(B9) | `MaxCache`/`MaxFrameSize`/`RequireFullFrame` 与消息泵(启动/停止、泵循环、帧交付编排、并发信号量)从 `SessionBase` 下沉 `TcpSession`(新分部 `TcpSession.Pump.cs`);流式发送 `SendMessageAsync(IMessage, Stream)` 随同下放。基类保留协议、匹配队列/超时与并发度,经 `StartReceiveFlow`/`StopReceiveFlow`/`RawReceiveTaken` 三个钩子接入(数据报会话不覆盖即无动作) | ✅ 2026-10-08 |
| 出站发送队列(`SendPipe`/`SendPump`/`TrySend`) | 旧形态(粘性单出口:泵发布后 `Send` 系列改入队)**整类删除**;改为可选的第二出口 `TcpSession.SendQueue` + `SendQueuedAsync`(只异步、所有权转移、积压等待、泵批量写出取同一把写锁、关闭不排空)。理由:粘性单出口会与直发形成两条写入路径需锁协商,且入队对借用视图与 `byte[]`/`Span` 必须拷贝,而直发 0 分配 0 拷贝;背压由内核发送缓冲承担 | ✅ 2026-10-01 |
| `ITransport` | 保留:下游已有实现者(Modbus `UdpTransport`、XCom `SerialTransport`),定位为"自定义传输"扩展点 | ✅ 2026-09-16 生态扫描确认 |
| `TcpServer.EnableHttp` | 死开关(只写不读),已删除;HTTP 由 `HttpServer.CreateHandler` 在处理器管道处理 | ✅ 2026-09-25 已删除(生态源码扫描零引用) |
| `PacketCodec` | 评估"单一引擎外观化"(内部复用 Pipe + PacketFramer),带验证门;评估结论:带验证门条件不可达(见 §5),先冻结为遗留路径(2026-09-16)。该遗留路径已随 v12 收口**整删**——帧定界/构建统一为 `IMessageCodec` + `MessagePump` 单栈(与 §5 一致) | ✅ 已整删(本行原记"冻结",述语已按现状更正) |
| `IMessage` | 保留消息契约(纯数据:`Payload`/`Body`/`Kind`/`CreateReply`);帧定界与构建由协议对象 `IMessageCodec` 承担。中间态"消息帧方法"(`IFrameMessage`/`ReadFrame`/`Build` 等)已整删——"`IMessage` 继承 `IFrameMessage`"的表述不再成立。消息不再池化口径已统一(内置消息直接 new,工厂池化定位为自定义消息的可选能力) | ✅ 2026-09-18(述语已按现状更正) |
| `Message` 响应语义 | 收敛为 `MessageKinds Kind` 单枚举(请求/单向/响应/响应+错误,与协议状态位一一对应);`Reply`/`OneWay` 为便捷视图(get 由 Kind 推导,set 回写 Kind),不再三布尔独立存储 | ✅ 2026-09-25 |
| `DefaultMessage` 构建契约 | `Build` 非破坏性:构建不改动消息负载,`message.Payload` 之后依然可用;已预留的负载向前借位共享(零拷贝),其余以新头节点挂接负载链(拥有句柄先切为独占共享链)。返回 `IOwnerPacket` 供调用方 `using` 释放 | ✅ 2026-09-25 |
| 具体包类型的反序列化识别 | 反序列化(读路径)只识别 `typeof(IPacket)`——物化必须由调用方指定明确的接口类型;序列化(写路径)继续用 `type.As<IPacket>()`(任何实现都能写出)。删除 `Packet` 后不再对具体包类型做特例,生态统一改用 `IPacket` | ✅ 2026-09-25 |
| `HttpHelper.ParseHeader` | 依赖 `Packet` 的可变 API(`Set/Data/Count`),且全仓与生态零调用 → 随类删除;不新建推测性替代 API | ✅ 2026-09-25 |
| `IPacket.FreeHeader` 借位前提 | 仅对拥有句柄借位;写入的是本视图之前的字节,仅当这些字节空闲(预留区、或解析所得帧头已消费)时才安全。调用方自造的非帧边界拥有句柄不满足该前提——只做文档约束,不加运行期防护(否则会切断零拷贝收益) | ✅ 2026-09-25 |
| `WsMessage`(原 `WebSocketMessage`) | 继承 `Message`(纯数据负载 + `Kind`);WebSocket 帧字段(`Fin`/`Type`/`MaskKey`/`CloseStatus`)由消息类读写、`WebSocketCodec` 委托调用;v12 删除旧名桥(`Read/ToPacket/ToHeaderPacket`),引用方需重新编译适配。此前"独立化、直接实现 `IFrameMessage`"的中间态已随帧契约整删取消(`IFrameMessage` 全库不存在) | ✅ 2026-09-18(述语已按现状更正) |
| `IPipeline` / `Handler` 全家族 | v12 已整删(协议对象 `IMessageCodec` + `MessagePump` 取代编解码链);`INetHandler` 旁路保留,职责边界见《消息协议栈》 | ✅ 已删除 |
| `UdpSession` | 收敛评估已完成(见附录 A):暂不实施,优先抽取共享发送/日志/错误助手而非继承 | ✅ 2026-09-16 |
| WebSocket 双实现(`Http/WebSocket` 与 `Net/Handlers/WebSocketCodec`) | 已互相定位注释(HTTP 升级路径 vs Net 侧通用编解码),合并留后续 | ✅ 2026-09-16 |
| `NetClient` | 保留(应用级起步封装) | ✅ |
| `Bin/` 陈旧文档副本 | 清理(与 `Doc/` 同名且落后) | ✅ 2026-09-16 |
## 7. 兼容纪律(改动本库前必读)
- 公开 API 的删除、改名、返回类型收窄都会断开旧二进制(CLR 成员引用绑定包含返回类型);优先新增成员。
- 兼容壳的处置不同:`OwnerPacket.Free` 已随 v12 破坏性清单删除;三参 `Slice` 壳(`[Obsolete]`)**保留**(v11 兼容成员补回,二进制兼容面),语义已统一为引用计数共享(`transferOwner` 被忽略)——旧二进制可继续加载,但"只释放新句柄"的旧用法会让原句柄引用滞留(延迟到 GC,非错误);源码升级请改用两参 `Slice`。
- 变更公开面后的默认门禁:全 18 TFM 编译(含 net45)+ 全量测试 + 生态扫描(`C:\X` 下游仓库与 NuGet 缓存 `newlife.*` 产物)。
## 8. 文档索引
- 数据与帧:《数据包IPacket》《数据管道Pipe》《数据包编码器PacketCodec》《网络缓冲所有权架构》《高级二进制序列化》
- 同步/异步:《网络库同步异步分工》(双轨决策、场景选型、发送架构:直发 + 一把写锁)、《会话关闭与在途帧交付》(关闭前交付在途帧、取消不丢已到达数据)
- 标准封包:`NewLife.Core/Net/Readme.md`(SRMP)
- 用法样例:`Samples/Zero.EchoServer`、`Samples/Zero.Server`;基准:`Benchmark/NetBenchmarks`、`Benchmark/PacketBenchmarks`
- 三件套(架构设计 / 功能清单 / 需求文档)为 v11 时期文档,v12 定稿时统一同步。
## 9. 未决架构项(2026-09-25 评估,待决策)
以下项属"结构性收敛",改动会触及协议契约或生态二进制,**未随缺陷修复批次实施**。按此顺序推进,每项独立可验证:
| 项 | 现状 | 前置决策 |
|---|---|---|
| codec 能力显式化(B2) | `IMessageCodec` 三成员实为"部分协议支持";装饰器能力已用 `IMessageCodecDecorator` 逐层解包(已完成该部分) | 是否允许给 `IMessageCodec` 加成员/加继承(`IMessage` 不动)。生态实现者需随大版本重编 |
| 帧定界与交付编排(B5) | 配对路径已统一(2026-10-08:UDP 接入共享 `ResponseMatcher`,与 TCP 同走 `IMatchQueue`);剩余差异只在交付编排本身(泵循环 vs 数据报循环)与收尾形式(UDP 体是内存切片,无需物化/`DiscardAsync`) | 已决:不抽 `MessageDispatcher`,共享匹配语义、编排各自实现(见 §9.1) |
| WebSocket 双帧循环(B12) | 已决(2026-10-08):**不合并**,保持服务端 `Http/WebSocket.Process` 与客户端 `WebSocketClient.OnReceivedMessage` 两套帧循环(客户端已统一为整帧交付);如后续要补强口径,做双端对称性契约测试而非抽类型 | — |
| `SessionBase` 瘦身(B9) | 已实施(2026-10-08):流式专有成员(`MaxCache`/`MaxFrameSize`/`RequireFullFrame`/消息泵/流式发送)下沉 `TcpSession`;`Protocol`/`MatchQueue`/`MatchTimeout`/`MaxConcurrency` 留基类(协议与匹配 TCP/UDP 共用) | 已决并落地(见 §9.1) |
| 事件体系收敛(B7) | `EventHub` 兼协议解码 + 路由 + 5 个 `[Obsolete]` 成员;`IEventBus` 自称"仅类型标记"却含 `PublishAsync`;`IAsyncEventBus` 为空别名 | 删除公开类型/成员前需生态扫描(`git grep` 逐仓,全仓递归遍历在本机常态超时) |
> 说明:`HTTP` 服务端处理器契约已异步化(`IHttpHandler.ProcessRequestAsync`,2026-10-08;随 v12 的破坏性变更,实现者改名即可),中间件本就是异步链;但会话级仍是"整请求缓冲 + 同步阻塞派发"(`ExecutePipeline(...).GetAwaiter().GetResult()`),且 `DefaultHttpContext.Current` 在 net45 为线程本地——会话级异步派发需一并解决响应保序、请求体跨轮所有权与低版本上下文传递,仍单列,不并入上表。
### 9.1 逐项结论(2026-09-25 决策)
| 项 | 结论 | 依据 |
|---|---|---|
| B2 codec 能力显式化 | **不做接口变更** | `IMessageCodec` 是生态实现最多的接口,加成员/加继承会断全部实现者。能力表达已有三条既有通道:`IMessageMatcher`(配对)、`IMessageCodecDecorator`(装饰器逐层解包)、明确的 `NotSupportedException` 文案;本会话已补"能力矩阵"文档段 |
| B3 `ParseResult` 语义 | **非缺陷,已文档化** | 逐条推演三个消费者(`MessagePump`/`CompressedCodec`/`UdpSession`)与分隔符协议"帧尾留给下一帧作前导跳过"的设计自洽;已补"尺寸不变量"文档段 |
| B4 掩码解码归属 | **不下沉 codec** | 流式体在 `TryParse` 阶段无法解码(掩码需按整帧连续 XOR),下沉会产生半解码状态;改为在 `WebSocketCodec` 明确"不要直接用作 `SessionBase.Protocol`" |
| B5 抽 MessageDispatcher | **不做;配对已统一(2026-10-08 修订)** | 原判据维持——两套交付编排服务两种传输形态,抽取会把泵循环与数据报循环硬编在一起,收益仅为代码组织。但"UDP 无配对"确为能力缺口(请求-响应等待在数据报上不可用):已抽出共享响应匹配组件 `ResponseMatcher`(`Net/ResponseMatcher.cs`,等待登记/匹配交付/关闭清理),TCP 在消息泵交付时、UDP 在 `ProcessDatagram` 交付时调用同一实现与同一 `IMatchQueue`。热路径改动仅限匹配接入点,编排未动 |
| B7 事件体系 | **已做** | 删除空别名 `IAsyncEventBus`、修正 `IEventBus` 自相矛盾的注释;`EventHub` 的 5 个 `[Obsolete]` 兼容成员已随 v12 大版本删除(58 个生态仓库核实零真实调用);仅“解码职责拆分”不做 |
| B9 `SessionBase` 瘦身 | **已做(2026-10-08 修订,原判"不做")** | 原判据是"要连带改 `ISocketSession`/`ITransport`"——核查后确认两个接口从未声明这些成员(只有 `Server`/`Session`/`Timeout`),下沉不必动接口。落地:流式专有成员与消息泵下沉 `TcpSession`,`MatchQueue`/`MatchTimeout`/`MaxConcurrency`/`Protocol` 留基类;生态扫描(Remoting/Stardust/XCode/Redis/Cube/XProxy 共 2285 个源文件)零引用被移成员 |
| B10 基类引用具体子类 | **不改** | `IStreamSession` 只建模管道,不含流式发送能力;改用接口需给它加发送成员(断实现者)。现有 `is not TcpSession` 守卫行为正确(子类可用、其它类型给出明确异常) |
| B12 双 WS 帧循环 | **不合并(2026-10-08 确认)** | 客户端已统一为整帧交付(`RequireFullFrame`);合并为 `WsFrameLoop` 属组织性重构、无行为收益,不做。如后续需要加强口径一致,改为补双端对称性契约测试(掩码/分片/控制帧/关闭码双端同口径断言) |
| B13/B14 HTTP 异步派发 | **契约已异步化(2026-10-08)** | 处理器契约直接改为异步(`Task ProcessRequestAsync(IHttpContext)`,实现者随 v12 改名;不保留并行接口,避免双契约分叉)、`DelegateHandler` 原生 `await`(`HttpProcessAsyncDelegate`,`Task<T>` 结果在完成后读取)、`ControllerHandler` 的异步操作方法同样 `await` 后取结果。会话级异步派发仍不做:`HttpSession` 同步桥需连带解决响应保序、请求体跨轮所有权与 net45 上下文传递,且 `INetHandler.Process(IData)` 同步契约需并行升级 |
| IMP-02 会话生命周期权威分散(审计立项) | **契约收口,不做结构收敛** | 六处状态定死为“`Disposed`=终态权威、`Sessions`=存在性权威、`Active`/`_running`/各计数=派生量”,权威表与不变量写入 §10,边界写入 XML 注释,由 `SessionAuthorityContractTests` 锁定。不做结构收敛的理由:`ISocketClient.Active` 是公开可写属性,收窄只读或拆语义都会断开旧二进制;`ISocketServer.Sessions` 的形态被 `HttpServer`、生态与示例直接消费;且与已决策不做的 B5/B9 重叠 |
> 结论口径:本次以"正确性与兼容性优先、不为代码组织动主流程"为原则(约 90% 覆盖原则)。上表"不做"项均已在本文档留痕,后续如需推进,按 B2 → B5 → B12 → B9 → B7 顺序单项实施、每项独立验证。
## 10. 会话生命周期状态权威(IMP-02 契约收口)
> 现状:同一个会话“还在不在/活没活”同时记在六处——`TcpServer._Sessions`、`UdpServer._Sessions`、`NetServer._Sessions`、`SessionBase._active`、`NetSession._running`、`SessionCollection`。任一处判断失败都会留下“集合里有、接收没起、Disposed 未置”的半死态(FIX-07、FIX-38 的土壤)。本节把这六处定死为“一个终态 + 一个存在性 + 若干派生量”,以文档 + XML 注释 + 契约用例固化;**不合并结构**,理由见 §10.3。
### 10.1 权威与派生
| 状态 | 位置 | 定位 |
|---|---|---|
| `Disposed` | `DisposeBase.Dispose`(`Common/DisposeBase.cs`) | **终态唯一权威**。一次性置位,`OnDisposed` 是所有集合摘除的统一回落点;判断“结束了没有”只认它 |
| `ISocketServer.Sessions`(`SessionCollection._dic`) | `Net/SessionCollection.cs` | **Socket 层存在性权威**。只收未释放会话(`Add` 入集合前二次复查 `Disposed`);出集合只有三条路:`OnDisposed` 回调、`RemoveNotAlive` 超时清理、`CloseAll`/`Clear` |
| `NetServer.Sessions` | `NetServer.cs` | **应用层第二份登记**(`INetSession` 包装器),受 `UseSession` 开关控制,与上一行不是一一对应;`UseSession=false` 时该集合恒空 |
| `SessionBase.Active` | `Net/SessionBase.cs` | “传输可用”,**不代表**“接收环已启动”,也**不代表**“在集合中”。客户端会话打开成功置 true、关闭完成置 false;服务端会话由宿主在构造时即置 true(表示“已接受连接”)、关闭起始处提前置 false(阻断掉线重连),因此服务端会话的 `Open()` 会幂等短路,`OnOpenAsync` 根本不会执行 |
| `NetSession._running` | `NetSession.cs` | 应用层一次性闸门:“`Start` 已生效且 `Close` 未发生”,用于保证 `OnConnected`/`OnDisconnected` 各一次;与 `Active`/`Disposed` 无联动 |
| 派生量(不作权威判定) | `SessionCollection._endPoints`、`UdpServer._broadcasts`、`NetServer._SessionCount`/`SessionCount`/`MaxSessionCount`、`NetServer.Active`、`NetClient.Active`、`TcpServer.Active` | 缓存与观测。`_endPoints`/`_broadcasts` 是与集合同源同销(同一 `OnDisposed`)的索引;`NetServer.Active`/`NetClient.Active` 由内部状态算出 |
### 10.2 不变量(由契约用例锁定)
1. 会话集合只收未释放会话;**释放 ⇒ 必出集合**。
2. 启动失败或业务回调抛异常时,会话必须收敛为 `Disposed` 并出集合(已实施于 FIX-07/FIX-38,用例见 `TcpServerAcceptRollbackTests`、`UdpServerCreateSessionRollbackTests`)。
3. `Active=false && !Disposed && 在集合` 是**允许的瞬态**,但**必须收敛**:服务端会话关闭后必须 `Disposed` 并出集合;客户端会话关闭后保持 `!Disposed`,由调用方保管终态——会话对象一次性,关闭后不可重新打开,重连请新建对象(见 `TcpSessionSingleUseTests`)。
4. 服务端会话的 `Open()` 幂等成功(连接已由宿主接管),`Close()` 走正常关闭流程。
5. UDP 差异:`UdpSession` 没有 `Active`,存活表达就是 `!Disposed`;`Stop()` 后 `Server=null` 表示“与服务器脱钩”而非“已死”,集合查找只认 `Disposed`。
6. 计数口径:`SessionCount` 统计服务端存活会话(与 `UseSession` 无关),`Sessions.Count` 是已登记的包装器数;两者在 `UseSession=true` 且稳态下相等。
### 10.3 不做结构收敛的理由
把六处并成一处更“干净”,但收益仅是代码组织,代价是动公开面:
- `ISocketClient.Active` 是**公开可写**属性:收窄为只读,或把 `Active` 拆成“已打开/未关闭”两个语义,都会断开旧二进制(CLR 成员引用绑定包含签名)。
- `ISocketServer.Sessions` 的形态(`IDictionary<String, ISocketSession>`)被 `HttpServer`、生态仓库与示例直接消费。
- `UdpSession` 继承 `SessionBase`(附录 A)、抽 `MessageDispatcher`(§9.1 B5)都已决策不做。B9 的"流式专有成员下沉"已于 2026-10-08 落地(不动接口、不动接收环模型),与本节的六处状态收敛无关。
因此 IMP-02 与 IMP-01 用同一种落地方式:**契约收口**——权威口径写进本节、边界写进 XML 注释、不变量由契约用例钉住;结构收敛留待大版本并与生态同步发版。
### 10.4 已修(2026-10-01)
- `FIX-48`:`NetServer.GetStat()` 曾反写 `SessionCount`,同一属性两种口径——`UseSession=false`(会话不进集合)时一打印统计就把会话计数清零。改为只展示 `_SessionCount` 计数器(`db9242ea7`);用例 `NetServer_GetStat_DoesNotOverwriteSessionCount`(退回旧实现变红)。
- `FIX-49`:`TcpSession.OnCloseAsync` 的异常分支曾不释放服务端会话,会话会以 `Active=false` + `Disposed=false` 留在集合里等超时清理。改为服务端会话在异常分支同样 `Dispose`(`4d63056e9`)。**实测该分支不可达**——底层套接字已释放、对端 RST 两种场景下关闭动作都不抛异常(接收环错误路径先一步收敛),故属防御性兜底;用例 `ServerSession_CloseFails_StillDisposed`、`ServerSession_PeerReset_SessionReleased` 锁定的是“无论如何都会收敛到已释放”的最终契约,无负向验证。
## 11. 审计收口记录(2026-10-05)
> 2026-10-04 ~ 10-05 对本库网络栈(`Net`/`Http`/`Messaging`/`Web`/`Remoting`)开展多轮审计——4 轮全量审计 + 补充扫描/运行期动态验证/覆盖补齐 + 1 轮 `Net` 专项,累计修复 24 个缺陷(FIX-105~128)与 9 项改进(IMP-13~21);自补充扫描起未再出现必改项,末轮 4 项全为建议/文档级。
> 本节集中记录**已知但决定不处理、后续审计不再重复登记**的项——扫描本库前先读本节,下列项请直接跳过。
### 11.1 非架构类已知挂账
| 项 | 位置 | 处置与理由 |
|----|------|-----------|
| `Upgrade._Client` 无释放路径 | `Net/Upgrade.cs` | 不补 `IDisposable`:进程级长生命周期对象,实际影响低;公开类型补释放语义还需调用方配合 |
| `SendAllAsync` 逐会话 `Task.Run` | `Net/NetServer.cs` | 显式取舍:慢会话不拖住整体;会话量极大时存在调度开销,如需可评估分批并发 |
| UDP 协议模式不支持请求-响应等待 | `Net/UdpSession.cs` | **已作废(2026-10-08)**:UDP 已接入与 TCP 同一匹配路径(共享 `ResponseMatcher` + `IMatchQueue`),协议模式支持带 `MatchTimeout` 的请求-响应等待;原地协议与无配对协议仍按 `NotSupportedException` 明确拒绝。原"设计边界"处置仅存史 |
| WebSocket 发送路径不支持分片(FIN 恒 1) | `Net/WebSocketClient.cs`、`Http/WebSocket.cs` | 设计边界:大帧经 `WebSocketCodec` 整帧构建 |
| `NetHelper.ParseEndPoint` 域名解析失败抛异常 | `Net/NetHelper.cs` | 沿用 `NetUri.ParseAddress` 口径,属有意选择(与静默返回 null 相反) |
| `JwtBuilder.LooksAsymmetricKey` 启发式兜底覆盖不全 | `Web/JwtBuilder.cs` | 主防线为算法钉死(`Algorithm` 显式设置即校验令牌声明一致);启发式仅兜"忘记显式设置算法"场景,裸 Base64 公钥形态漏检出,属可接受残余风险 |
### 11.2 架构类已知容忍
`ISocketSession`/`ITransport` 带流式属性(B9)、TCP/UDP 交付编排两套(B5,配对已统一、编排仍各自实现)、WebSocket 双帧循环(B12)、HTTP 同步派发(B13/B14)、六处会话状态(IMP-02)等的"不做"决策与理由见 §9.1、§10.3——同样不再重复登记。
### 11.3 审计停手判据与准入线
- **准入线**(登记什么):只登记"能说清触发路径(谁、什么场景、怎么发生)、后果为算错/丢数据/挂死/泄漏/安全/兼容断裂"的问题;纯风格、无后果理论项、无实测微优化不登记(与 [功能清单](/NewLife/X/Blob/v12/Doc/功能清单.md)「待修复项」保持一致)。
- **停手判据**(何时不再全量扫):① 一轮扫描无必改;② 剩余项必须换角度(协议合规、取消语义等)才能发现;③ 只剩需动公开面/生态发版节奏的架构项——此时改为按角度专项排期,不做全量扫描。
### 11.4 复核补充(2026-10-07)
> 对 9 月 1 日以来全部改动做独立复核——全 18 TFM 编译、6 套测试矩阵(net8/9/10、net8/10-windows、net6、compat)全绿且全量复跑 3 轮无偶发,另做取消语义与协议合规专项精读。修复 1 项:WebSocket 客户端接收侧补齐协议校验(`bbb600e00`,负向验证变红)。以下为复核新识别、决定不处理的容忍项,后续审计不再重复登记。
| 项 | 位置 | 处置与理由 |
|----|------|-----------|
| 关闭帧 1 字节负载双端容忍(RFC 6455 §5.5.1 视为协议错误) | `Net/WebSocketClient.cs`、`Http/WebSocket.cs` | 无后果:状态码不可解析时按"无状态码关闭帧"处理,不丢数据、不挂连接;严格失败只会把本可优雅关闭的连接变成异常关闭 |
| 《AOT兼容性分析》文档漂移(IMP-22) | `Doc/AOT兼容性分析.md` | 已翻案(2026-10-08):按 v12 基线完成 NativeAOT 实测评估并全文重写(文档"现状与策略"版);改进项转入《功能清单》待修复项 AOT 专项(IMP-23~28)。原"暂缓"结论作废,此行为翻案留痕 |
**v12 生态兼容专项扫描(2026-10-08)**:对 `C:\X` 下 55 个仓库的 8 千余个源文件做删除/改名 API 面定向扫描——核心五仓(NewLife.Remoting / XCode / Redis / Stardust,均依赖 v12 beta)**零触碰删除面**,Remoting 已按 v12 新 API(`WsMessage`、`SrmpCodec.Build`)适配;命中仅见于休眠/未升级外围仓(NewLife.TDengine 依赖 v8、NewLife.Zero / XProxy 依赖 v11、NewLife.MQ / NewLife.Net / NewLife.UWB / NewLife.XCoder / rem-head 快照),按其自身升级节奏适配,不属本库缺陷。编码器解析面另做 12 万次随机输入模糊测试(种子固定,已固化为 `Messaging/CodecFuzzTests`),零意外异常。
## 附录 A:UdpSession 收敛评估(2026-09-16,仅评估)
**现状**:`UdpSession` 不继承 `SessionBase`,独立实现发送(×4)/接收/日志/错误处理;与 `UdpServer` 共享同一 Socket,按远端地址懒建,空包结束会话;`ITransport` 的 `Open/Close` 为桩实现。
**收敛候选**:① 继承 `SessionBase`——基类接收环与每轮拥有句柄模型围绕独占 Socket 设计,UDP 是共享 Socket 按远端分片,直接继承需给基类增加传输抽象层,波及 UdpServer 热路径;② 抽取共享的发送/日志/错误处理助手——范围小,但只消除重复样板,不解决双模型差异。
**结论**:暂不实施。收益(减少重复代码)小于风险(触及 UDP 接收热路径与 2500+ 用例回归面);未来如实施,优先候选 ② 渐进收敛。
|