介绍及使用
定义
一、说明
数据处理适配器,是 TouchSocket 的核心组件,其作用就是解析数据。
解析可以分为两种:
- 第一种是解决数据的粘包、半包问题;
- 第二种是将收到的数据解析为数据对象(
IRequestInfo)。
二、为什么需要适配器
在 TouchSocket 中,数据传输可分为流式单线程和非流式多线程两种模式。其中流式单线程以 Tcp、NamedPipe、SerialPort 等为代表;非流式多线程则以 Udp 为代表。
2.1 流式单线程
流式单线程的数据只有一个线程访问,像水流一样连续传输。其特点是:
- 数据包是有序的。
- 数据包是偶发性粘连的,即数据包之间没有分界,且粘连因素不确定。
- 读取时,可能读到多个数据包或多个半包。
解决粘包、半包问题的关键在于标识和顺位。通讯协议通常分为以下四种:
| 协议类型 | 思路说明 |
|---|---|
| 固定包头 | 包的前几个字节固定表示后续数据的长度,每次读取时先读包头、再按长度读包体 |
| 不固定包头 | 通过约定特征字节来识别包头位置,找到包头后再解析包体长度 |
| 固定长度 | 收发双方预先约定每个数据包长度固定,按固定长度切分即可 |
| 字符区间 | 使用特定的起始符和/或终止符来标识数据包的边界 |
绝大多数通讯协议都是以上四种的一种或混合体。适配器会大幅简化这类解析工作。
2.2 非流式多线程
Udp 等非流式场景的数据包是相互独立的,每个数据包自成一体,不存在粘包问题。因此适配器在此场景下的作用主要是将原始数据封装成 IRequestInfo 对象投递给上层。
三、设计架构
3.1 整体工作流程
以下流程图展示了适配器在 TouchSocket 中的完整工作过程:
3.2 数据流转逻辑
适配器接收到原始字节流后,通过 PreviewReceivedAsync 方法进行解析,完成后以以下任意组合调用 GoReceivedAsync 投递数据:
- 仅投递
ReadOnlyMemory<byte>:IRequestInfo为null,适用于FixedHeaderPackageAdapter、FixedSizePackageAdapter、TerminatorPackageAdapter等内置包适配器。 - 仅投递
IRequestInfo:Memory为Empty,适用于继承CustomDataHandlingAdapter自定义解析的场景。 - 两者均投递:适用于 HTTP 等高性能场景,包头以
IRequestInfo投递,Body 以Memory传递(直接来自内存池,避免额外复制)。
3.3 适配器类型体系
3.4 设计解释
TouchSocket 设计为同时投递 Memory 和 IRequestInfo 两个参数,而非像某些框架那样在会话上泛型约束适配器类型,主要有以下原因:
- 支持运行时切换适配器:以 WebSocket 为例,握手阶段需要用 HTTP 适配器解析请求,握手完成后需要切换为 WebSocket 帧适配器。泛型约束会破坏这种灵活性。
- 提升高性能场景下的效率:例如 HTTP 适配器,包头通过
IRequestInfo投递,而 Body 通过Memory(直接来自内存池)投递,避免了额外的内存分配和复制。
四、Tcp / NamedPipe / Serial 使用
Tcp、NamedPipe、SerialPort 均使用 SingleStreamDataHandlingAdapter,配置方式完全一致。
4.1 在 Config 中配置
在 Config 中配置适配器相当于初始化赋值,连接建立前生效。配置时必须传入工厂委托(每次新建实例),不能传入单例。
TouchSocket 内置了以下常用适配器,可按需选择:
| 适配器类 | 说明 |
|---|---|
FixedHeaderPackageAdapter | 固定包头,支持 Byte / Ushort / Int 三种包头类型 |
FixedSizePackageAdapter | 固定长度分包 |
TerminatorPackageAdapter | 终止符分包,如 \r\n |
PeriodPackageAdapter | 周期合包,在超时时间内将多次收到的数据合并为一个包 |
JsonPackageAdapter | 按 JSON 对象边界分包 |
同一个适配器实例只能被一个终端使用一次。如果将同一实例赋值给多个客户端,会在 OnLoaded 时抛出异常。请始终通过工厂委托(() => new XxxAdapter())的方式创建适配器。
五、Udp 使用
Udp 使用 UdpDataHandlingAdapter,只能通过 Config 配置,不支持运行时切换。
六、限制适配器(封装场景)
SetAdapter 是 protected 方法,外部代码无法直接调用。在自定义封装场景下,若需要强制锁定适配器类型,可继承 TcpSessionClient(服务端 SessionClient 场景),在 OnTcpConnecting 内部调用 SetAdapter,外部则永远无法通过任何公开 API 更换适配器。
七、通过 AdapterOption 统一配置参数
AdapterOption 可以在 Config 中统一设置适配器的通用参数,设置后会在适配器加载时自动应用:
| 属性 | 说明 |
|---|---|
MaxPackageSize | 最大允许的数据包大小(字节),超出则抛异常,默认 10MB |
CacheTimeoutEnable | 是否启用缓存超时(仅流式适配器有效),默认 false |
CacheTimeout | 缓存超时时长,默认 1 秒 |
启用缓存超时后,若一个数据包在超时时间内未完成接收,适配器会丢弃已缓存的部分数据并重新开始解析。这主要用于应对发送方发送异常数据(或运营商插入无效包)导致缓冲区数据永久无法完成解析的情况。