解决MySql布尔型新旧版本兼容问题,采用枚举来表示布尔型的数据表。由正向工程赋值
|
# 消息协议栈
> 适用范围:`NewLife.Messaging`(契约与帧泵)、`NewLife.Net`(会话集成)、`NewLife.Data`(数据管道)。
> 定位:协议层唯一形态。v12 已整删旧帧引擎(`PacketCodec`/`PacketFramer`)、旧处理器链(`Pipeline`/`Handler`)与旧 WebSocket 消息(`WebSocketMessage`);迁移状态见 §7。
> 配套专题:《数据管道Pipe》《数据包IPacket》《网络库架构》。
## 1. 设计目标与四角模型
消息 = 头部 + 流式负载。**头部到齐即交付消息帧**,负载按需消费(大帧不必整载入内存);实现以 Span/Memory/Sequence 为基调,尽量零拷贝。
```
字节流 ─[帧泵:定界,头到齐]→ 消息(头 + 流式/内存体) ─[分发:匹配/事件]→ 业务
←[协议:Build/BuildHeader]─ 消息 ←[SendMessage]─ 业务
```
| 角落 | 类型 | 职责 | 生命周期 |
|---|---|---|---|
| 消息 | `Message`(基类) | 纯数据:头部语义 + 体(流式/内存统一);`IDisposable` 唯一归还点 | 每帧一个 |
| 协议 | `IMessageCodec` | 字节 ⇄ 消息:定界与构造 / 整帧构建 / 头部构建 | 无状态,跨连接共享 |
| 帧泵 | `MessagePump` | 管道上定界与交付:头先行 + 小帧快路径 | 每会话一个(薄) |
| 分发 | 会话 / `IMessageMatcher` | 匹配队列 → 事件/处理器 → 业务 | 会话级 |
## 2. 契约
### 2.1 IMessageCodec
```csharp
ParseResult? TryParse(ReadOnlySequence<Byte> buffer);
IOwnerPacket? Build(IMessage message);
IOwnerPacket BuildHeader(IMessage message, Int64 bodyLength);
```
- `TryParse`:从帧首窗口定界。头部不足返回 `null`(不消费、不产生对象)。
- **结果语义**(`ParseResult`):`Message` 为解析出的消息(头部字段就位、体待绑定);`Message == null` 表示“无消息帧”(心跳、空行、分隔符),按 `HeaderSize` 跳过;正常帧 `HeaderSize` 为头部字节数、`BodyLength` 为体字节数(体内容紧随头部,帧泵绑定,`Int64` 表示不溢出)。
- **三态返回**:`null` = 数据不足(继续等待);`ParseResult { Invalid = true }` = 帧损坏(长度非法等),帧泵立即抛异常而不再静默丢弃;其余为已定界帧。
- **`NeedFullFrame`**:结果标记“已定界但体需整帧到齐”(如压缩装饰器内层未完整),帧泵据此按帧长跳过 `MaxCache` 残余防护,避免大帧被误判为坏帧。
- **成功即有进展**:成功返回必须消费至少 1 字节,否则帧泵可能空转;实现须对非法输入(负长度等)返回 `Invalid`(损坏)或 `null`(数据不足),而不是产出无进展的成功。
- `Build`:整帧构建(头 + 内存体),**不消费消息负载**(构建后 `Payload` 依然可读);已预留的负载向前借位共享、其余新头节点挂接负载链,均零拷贝。无内容返回 null。流式体不可整帧构建(抛异常),改用 `BuildHeader` + 流式发送。
- `BuildHeader`:仅头部,声明体长;供头先行与流式发送使用。两者均不改动消息数据,返回拥有句柄可直接 `using` 释放。
> 协议实现必须无状态(可跨连接共享);跨段解析用 `SequenceReader<Byte>`(全 TFM 兼容)。
### 2.2 IMessageMatcher
```csharp
Boolean Match(IMessage request, IMessage response);
```
请求-响应配对语义(如 SRMP 按序列号)。会话的 RPC 等待依赖它;未实现该接口的协议无法配对,`SendMessageAsync` 等待将超时。
### 2.3 IMessage 接口(协作面)
- `LimitedReader? Body`:体读取器。整帧快路径为内存视图(`IsStreaming=false`);慢路径为流式(数据随管道到达)。
- `IPacket? Payload`:体的内存句柄(整帧/发送模式零拷贝;流式为 null)。
- `SetBody(IPacket?)`:发送体/整帧体绑定;`null` 表示放弃持有(所有权已交付调用方或构建结果),不归还。
- `BindBody(LimitedReader)`:帧层专用(帧泵绑定流式体)。
- `Dispose`:体唯一归还点;未读余量由交付收尾丢弃(`MessagePump.DiscardAsync`)。
- 请求-响应语义:`MessageKinds Kind` 四态(请求/单向/响应/响应+错误,与协议状态位一一对应)+ `Reply`/`OneWay` 便捷视图 + `CreateReply()`(应答上返回 null)。
> 接口保持纯数据(v12 已删除全部帧解析/构建成员);协议字段(如 SRMP 的 `Flag/Sequence`)由具体消息类(`DefaultMessage`)承载。
## 3. 帧泵 MessagePump
```csharp
var pump = new MessagePump(codec);
while (true)
{
var msg = await pump.ReadAsync(pipe.Reader, ct);
if (msg == null) break; // 流结束
try { /* 处理:头部字段立即就位,体按需 await msg.Body.ReadAllAsync() */ }
finally { await MessagePump.DiscardAsync(msg, ct); msg.TryDispose(); }
}
```
- **快路径**:帧已完整在窗口 → `TakeFrame` 零拷贝切出整帧,体为内存视图。
- **慢路径**:帧未完整 → 头部已到齐,体为流式读取器(`PipeReader.Limit`),大帧不整载。
- `TryRead`:不等待版本;循环跳过无消息帧直到产出消息或数据不足。
- **防护**:`MaxCache`(默认 1M)——残余无法定界且达到上限说明对端数据与协议不匹配或已损坏,`ReadAsync` 抛出异常,避免连接僵死。
## 4. 内置协议
| 协议 | 帧格式 | 特性 |
|---|---|---|
| `SrmpCodec` | 1 Flag + 1 Sequence + 2 Length(0xFFFF 时 8 字节扩展头)+ 负载 | SRMP;状态位含 `Reply/Error/OneWay`;按序列号配对(RPC) |
| `SplitDataCodec` | 分隔符(默认 CRLF)定界,内容为体 | 行协议;空行/前导分隔符=无消息帧 |
| `LengthFieldCodec` | [头部(Offset)] [长度字段(Size)] [负载] | 长度字段值=负载长;1/2/4 小端、负值为大端、0 为 7 位压缩变长;适配 MQTT 风格 |
| `WebSocketCodec` | WebSocket 帧(掩码/扩展长度/控制帧) | 应用层协议(`NewLife.Http`);服务端整帧泵 + 客户端 `WebSocketClient` 协议模式;详见《WebSocket迁移设计》 |
| `CompressedCodec` | 装饰内层协议(如 `SrmpCodec`),消息体 Deflate 压缩/解压 | **组合式变换层**:连接级约定(两端同配);完整帧整载解压;空体不压缩;流式发送不支持;解压上限 `MaxDecompressedSize`(默认 16M)超限即失败,防解压炸弹 |
## 5. 会话集成
### 5.1 启用协议模式
```csharp
var server = new NetServer { Port = 0, Protocol = new LengthFieldCodec { Offset = 2 } };
server.Start();
```
- `Protocol` 须在打开/下发之前设置(`NetServer` 在 `Start` 前设置,随监听服务下发到会话,Unix 域套接字同样透传)。
- 仅流式会话(TCP/SSL/UDS)启动消息泵;`AutoReceive=false` 时不启动。
- 配置透传:`NetServer.Protocol/MaxCache` → `TcpServer` → 会话;`NetClient.Protocol/MatchTimeout` 同理。
- 消息泵与帧定界配置(`MaxCache`/`MaxFrameSize`/`RequireFullFrame`)属流式会话(`TcpSession`);`SessionBase` 只保留收发原语、协议与匹配队列,经 `StartReceiveFlow`/`StopReceiveFlow`/`RawReceiveTaken` 三个钩子接入消息泵——数据报会话不覆盖即无动作。
### 5.2 接收与交付契约
```csharp
server.Received += (s, e) =>
{
if (e.Message is not DefaultMessage req) return;
// 交付契约:处理器返回后消息即收尾;需要负载时在本方法内读满
var all = req.Body!.ReadAllAsync().AsTask().GetAwaiter().GetResult();
var data = all.AsReadOnlySequence().ToArray();
all.TryDispose();
// 服务端应答:CreateReply 复制协议字段(序列号),经协议构建整帧回发
var reply = (DefaultMessage)req.CreateReply();
reply.SetBody(new ArrayPacket(data));
((INetSession)s!).SendMessage(reply);
};
```
- **交付契约**:处理器返回后消息收尾(未读体丢弃对齐帧尾、消息释放);`e.Packet`(内存体视图)与消息同生命周期。
- 同连接消息**串行**处理;需要异步消费流式体时重写会话的 `OnMessageAsync`(消息与数据窗口在其返回前保持有效)。
- 同步处理器内可同步等待读满:内存体立即完成;流式体的等待属串行语义,正确性优先。
> **处理入口选择**:`OnReceive(ReceivedEventArgs)` 是事件出口(裸数据与协议消息共用,业务订阅 `Received` 事件);`OnMessage(IMessage)` 为同步处理入口(默认实现构造事件参数并进事件链,零状态机开销);`OnMessageAsync(IMessage)` 为异步入口(默认转调 OnMessage,需要 `await` 流式体或异步业务时重写)。**重写其一即可,不会双触发**。
> **会话处理器(可选)**:服务器可重载 `NetServer.CreateHandler` 为会话挂 `INetHandler`,数据**先经处理器再进入事件**;协议模式下处理器从事件参数转型取消息(`data is ReceivedEventArgs e → e.Message`),返回后消息同样收尾。适合"有状态协议预处理"(如 HTTP 会话解析);普通业务直接用 `Received` 事件即可。
### 5.2.1 并行处理(MaxConcurrency)
协议模式默认同连接**串行**处理(前一条收尾后才读下一帧)。客户端多路复用并发请求(SRMP 不同序列号)时,服务端可设置 `MaxConcurrency` 大于 1 启用并行派发:
- 派发前先**物化流式体**(一次拷贝),使消息脱离数据管道独立可用(管窗口顺序消费仍串行);
- 并发受信号量限制(兼作背压,池满时等待);处理顺序不定——SRMP 按序列号配对,天然无顺序依赖;
- **并行要求业务处理器线程安全**;配置经 `NetServer.MaxConcurrency` → `TcpServer` → 会话下发(UDP 随服务器下发);
- 默认 1(串行),兼容现有执行模型。
### 5.3 请求-响应(RPC)
```csharp
var request = new DefaultMessage { Sequence = 1 };
request.SetBody(pk);
var reply = await client.SendMessageAsync(request, ct);
```
- 请求入匹配队列 → 响应到达后先物化流式体(保证事件链与等待方均可读)→ 统一进入 `Received` 事件(可观测)→ 尝试匹配交付等待方;命中后跳过收尾,消息由等待方释放;未命中按普通消息收尾。
- 匹配不以消息泵为前提,而是**交付阶段**能力:流式会话(TCP/SSL/UDS)由消息泵在帧定界交付时配对,数据报会话(UDP,每包即完整帧)在报文交付时配对,两者共用同一实现(`ResponseMatcher`)与同一 `IMatchQueue`;UDP 的响应体本就是内存切片,无需物化。
- 等待超时 `MatchTimeout`(默认 30s);配对语义由协议的 `IMessageMatcher.Match` 决定。
- 交付的响应消息体为内存模式(流式负载在交付前物化),等待方可任意异步消费。
### 5.4 流式发送
```csharp
await client.SendMessageAsync(new DefaultMessage { Sequence = 2 }, stream, stream.Length);
```
- 头部先行(`BuildHeader` 声明体长)+ 流内容读一块写一块:两者共用同一把写锁,无交错;背压由内核发送缓冲承担,大消息不整载。
- `bodyLength` 为负时从可定位流(`CanSeek`)推导。
### 5.5 UDP
- 数据报即完整帧:定界后逐条分发(一跳多帧),体为数据报内零拷贝切片。
- 不使用消息泵(数据报边界天然保持),无粘包/半包处理。
- 请求-响应等待与流式会话同口径:请求入会话匹配队列,应答数据报交付时按协议 `IMessageMatcher` 配对(`client.SendMessageAsync(request)` 返回应答);服务端侧只接收处理与单向回发,不参与配对等待。
- 等待超时为 `MatchTimeout`(随服务器下发);UDP 不可靠,未收到应答即超时,重传与否由调用方决定。原地协议或无配对能力的协议仍抛 `NotSupportedException`。
### 5.6 异常与防护
- 残余无法定界达到 `MaxCache`(默认 1M)→ 帧泵抛异常 → `OnError` + 关闭会话。
- 泵内部异常(IO/协议错误)同样关闭会话,避免半开连接僵死。
## 6. 自定义协议
```csharp
public class MyCodec : IMessageCodec
{
public ParseResult? TryParse(ReadOnlySequence<Byte> buffer)
{
// 1) 头部不足:返回 null(不消费、不产生对象)
// 2) 用 SequenceReader<Byte> 跨段读取(TryRead / TryReadLittleEndian / TryReadBigEndian)
// 3) 无消息帧(心跳等):Message = null,HeaderSize = 跳过字节数(> 0)
// 4) 正常帧:构造消息(头部字段就位),HeaderSize 头部字节数、BodyLength 体字节数
return new ParseResult { Message = msg, HeaderSize = size, BodyLength = len };
}
// 整帧构建:头 + 内存体,不改动消息负载
public IOwnerPacket? Build(IMessage message) => throw new NotImplementedException();
// 仅头部:声明体长,配合流式发送
public IOwnerPacket BuildHeader(IMessage message, Int64 bodyLength) => throw new NotImplementedException();
}
```
要点:无状态(可跨连接共享);`TryParse` 不物化、不拼读;`Build` 不消费消息负载(`Payload` 构建后依然可读)。
**组合优于叠加**:同一 `IMessageCodec` 接口可嵌套装饰,把压缩/加密等变换层与帧协议自由组合,无需多层管道:
```csharp
server.Protocol = new CompressedCodec(new SrmpCodec());
// 多层变换即多层嵌套:new EncryptedCodec(new CompressedCodec(new SrmpCodec()))
```
- 装饰器在 `TryParse` 中解压/解密并把结果预绑定到消息体(帧泵检测到已绑定体时直接消费整帧,不再二次绑定);
- 要求完整帧到齐后才能整载变换(压缩体无法流式解压),流式发送不支持;
- 组合实例无状态性由内层协议保证(建议整体无状态,可跨连接共享)。
## 7. 迁移状态
| 旧栈 | 新栈 | 状态 |
|---|---|---|
| `PacketCodec` / `PacketFramer`(同步拆帧) | `MessagePump` | ✅ 已整删旧栈;泵已接入 TCP/UDP/UDS/WS |
| `StandardCodec` 等 6 个 Handler 编解码器 | `SrmpCodec` / `LengthFieldCodec` / `SplitDataCodec` | ✅ 旧栈已整删;生态(Remoting/MQTT/RocketMQ)随大版本适配 |
| `WebSocketMessage` / `Http.WebSocket` 旧回调 | `WebSocketCodec` / `WsMessage` + `MessagePump` | ✅ 已接入(服务端整帧泵 + 客户端 `WebSocketClient` 协议模式);掩码由 codec 构建生成、消费侧解码;分片维持现状(FIN=0 无法定界)。详见《WebSocket迁移设计》 |
| 消息帧方法(`IFrameMessage` / `Message.ReadFrame/Build/TryParseHeader`) | 纯数据 `IMessage` + 协议对象(codec) | ✅ 契约已瘦身;帧解析/构建全部移入协议 |
| 处理器链(`Pipeline`/`Handler`/`IHandlerContext`) | `Received` 事件 + `INetHandler`(可选旁路) | ✅ 旧栈已整删 |
> **入口变更**:`ISocketRemote.Add<T>()` 与 `SendFile/SendMessages` 已删除;协议经 `Protocol` 属性设置(`NetClient.Protocol`、`NetServer.Protocol`;裸 `ISocketRemote` 转 `SessionBase.Protocol`)。
## 8. 注意事项
- **协议实例无状态**:编解码器跨连接共享,禁止在实例上存放连接级状态。
- **扩展消息**:新协议建议定义 `Message` 子类承载协议字段(参考 `DefaultMessage`);不要给 BCL 接口(如 `IMessage`)加成员(旧二进制兼容灾区)。
- **交付收尾**:业务未读体由框架丢弃并对齐帧尾;跨帧留存的负载须在事件内切出(`IPacket.Slice` 共享句柄)或先物化。
- **性能**:头到齐即交付使大帧延迟与帧大小解耦;小帧走快路径零拷贝(`TakeFrame` 机制)。
- **v12 破坏性变更**:`IMessage` 帧方法、`IFrameMessage`、`PacketCodec/PacketFramer`、`Pipeline/Handler` 全家族、`Http.WebSocketMessage`、`ISocketRemote.Add/SendFile/SendMessages`、`OwnerPacket.Free` 均已删除;三参 `Slice` 兼容壳**保留**(语义改为引用计数共享,`transferOwner` 被忽略,源码请改用两参 `Slice`);业务消息发/收统一走 `IMessage` 与 `Protocol`。
- **v12 公开面删除(二进制破坏)**:`TcpServer.EnableHttp`(死开关)、`IAsyncEventBus<TEvent>`(与 `IEventBus<TEvent>` 等价的空别名)以及 `EventBus<TEvent>` 对后者的实现均已移除;基于旧版编译的调用方会抛 `MissingMethodException` / `TypeLoadException`。
- **v12 过时成员清理**:除上述两项外,还删除了 `InIConfigProvider`、`IExtend2`/`IExtend3`、`ApiHttpClient.Service`、`ApiHttpClient.RoundRobin`、`EventHub` 的 `DispatchAsync`/`DispatchActionAsync`/`Add(topic,·)`/`TryGetBus<T>`、`SpanSerializer` 的 `Serialize`/`Deserialize`/`WriteValue`/`ReadValue`、`StringHelper.F`、`TokenProvider.Decode(String,out DateTime)`、`Reflect.GetTypeEx(String,Boolean)`、`NetHelper`/`TcpConnectionInformation2` 的无参 `GetAllTcpConnections()`、`Setting : SocketSetting` 别名、`ProcessHelper.GetProcessName2`、`Upgrade.DeleteBuckup`、`ObjectPool.AllIdleTime`、`ConcurrentHashSet.Contain`、`SpanReader.FreeCapacity`、`TimerX.Cron`/`CanExecute`、`NetSession.OnDisconnected()`、`ObjectContainerHelper.Resolve<TService>`。保留 `Put` 系列、`TrimStart`/`TrimEnd`、`XmlConfig<T>`(下游使用广泛)。
- **v12 行为收紧**:`MessagePump`/`TcpSession`/`WebSocket` 新增 `MaxFrameSize`(整帧模式单帧上限,默认 16M;会话级配置在 `TcpSession`,2026-10-08 随 B9 从 `SessionBase` 下沉)。超过上限的帧会被拒绝并关闭连接;确实需要更大帧时显式调大,或置 0 表示不限制。
---
(完)
|