解决MySql布尔型新旧版本兼容问题,采用枚举来表示布尔型的数据表。由正向工程赋值
|
# 网络库架构
> 适用范围:NewLife.Core 的 `NewLife.Net`、`NewLife.Messaging`、`NewLife.Data`(数据包部分)与 `NewLife.Http`(服务端)。
> 目标形态:简单架构覆盖 80% 日常场景,剩余 20% 通过明确的扩展点留给下游。
> 配套专题:《数据包IPacket》《数据管道Pipe》《网络缓冲所有权架构》《消息协议栈》《高级二进制序列化》。
## 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. 帧引擎
帧定界与构建由单一帧泵 `MessagePump` 承担:管道上定界交付(头到齐即交付、小帧零拷贝快路径、大帧流式体、`MaxCache` 残余防护),协议经无状态 `IMessageCodec` 实现。
## 6. 兼容纪律(改动本库前必读)
- 公开 API 的删除、改名、返回类型收窄都会断开旧二进制(CLR 成员引用绑定包含返回类型);优先新增成员。
- 兼容壳的处置不同:`OwnerPacket.Free` 壳已删除;三参 `Slice` 壳(`[Obsolete]`)**保留**(二进制兼容面),语义已统一为引用计数共享(`transferOwner` 被忽略)——旧二进制可继续加载,但"只释放新句柄"的旧用法会让原句柄引用滞留(延迟到 GC,非错误);源码升级请改用两参 `Slice`。
- 变更公开面后的默认门禁:全 18 TFM 编译(含 net45)+ 全量测试 + 生态扫描(`C:\X` 下游仓库与 NuGet 缓存 `newlife.*` 产物)。
- 零调用成员清理(2026-10):本库范围内部成员直接删除(如 `TcpSession.DirectSendAsync(ReadOnlyMemory)`、`XmlHelper` 标量换算簇);公开面删除项(`EventHub.EncodeEvent/EncodeAction`、`HttpHelper.MakeRequest`/`PostMultipartFormAsync`/`ConsumeAndPushAsync`/`WaitForClose` 等)见《消息协议栈》§8 v12 清理清单,均经 `C:\X` 全生态零引用复核;保留项与误报类别见《架构设计》§3。
## 7. 文档索引
- 数据与帧:《数据包IPacket》《数据管道Pipe》《数据包编码器PacketCodec》《网络缓冲所有权架构》《高级二进制序列化》
- 同步/异步:《网络库同步异步分工》(双轨决策、场景选型、发送架构:直发 + 一把写锁)、《会话关闭与在途帧交付》(关闭前交付在途帧、取消不丢已到达数据)
- 标准封包:`NewLife.Core/Net/Readme.md`(SRMP)
- 用法样例:`Samples/Zero.EchoServer`、`Samples/Zero.Server`;基准:`Benchmark/NetBenchmarks`、`Benchmark/PacketBenchmarks`
## 8. 会话生命周期状态权威
> 现状:同一个会话“还在不在/活没活”同时记在六处——`TcpServer._Sessions`、`UdpServer._Sessions`、`NetServer._Sessions`、`SessionBase._active`、`NetSession._running`、`SessionCollection`。任一处判断失败都会留下“集合里有、接收没起、Disposed 未置”的半死态。本节把这六处定死为“一个终态 + 一个存在性 + 若干派生量”,以文档 + XML 注释 + 契约用例固化;不合并结构的取舍见《架构设计》§3 关键设计决策。
### 8.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` 由内部状态算出 |
### 8.2 不变量(由契约用例锁定)
1. 会话集合只收未释放会话;**释放 ⇒ 必出集合**。
2. 启动失败或业务回调抛异常时,会话必须收敛为 `Disposed` 并出集合(用例见 `TcpServerAcceptRollbackTests`、`UdpServerCreateSessionRollbackTests`)。
3. `Active=false && !Disposed && 在集合` 是**允许的瞬态**,但**必须收敛**:服务端会话关闭后必须 `Disposed` 并出集合;客户端会话关闭后保持 `!Disposed`,由调用方保管终态——会话对象一次性,关闭后不可重新打开,重连请新建对象(见 `TcpSessionSingleUseTests`)。
4. 服务端会话的 `Open()` 幂等成功(连接已由宿主接管),`Close()` 走正常关闭流程。
5. UDP 差异:`UdpSession` 没有 `Active`,存活表达就是 `!Disposed`;`Stop()` 后 `Server=null` 表示“与服务器脱钩”而非“已死”,集合查找只认 `Disposed`;会话级拉取明确拒绝(`Receive/ReceiveAsync` 抛 `NotSupportedException`,拉取统一归 `UdpServer`,见《架构设计》§3 设计决策,用例见 `PullModeTests`)。
6. 计数口径:`SessionCount` 统计服务端存活会话(与 `UseSession` 无关),`Sessions.Count` 是已登记的包装器数;两者在 `UseSession=true` 且稳态下相等。
|