跳到主要内容
版本:4.2

介绍及使用

定义

命名空间:
TouchSocket.Core
安装:
dotnet add package TouchSocket.Core

一、说明

数据处理适配器,是 TouchSocket 的核心组件,其作用就是解析数据

解析可以分为两种:

  • 第一种是解决数据的粘包、半包问题;
  • 第二种是将收到的数据解析为数据对象IRequestInfo)。

二、为什么需要适配器

在 TouchSocket 中,数据传输可分为流式单线程非流式多线程两种模式。其中流式单线程TcpNamedPipeSerialPort 等为代表;非流式多线程则以 Udp 为代表。

2.1 流式单线程

流式单线程的数据只有一个线程访问,像水流一样连续传输。其特点是:

  1. 数据包是有序的。
  2. 数据包是偶发性粘连的,即数据包之间没有分界,且粘连因素不确定。
  3. 读取时,可能读到多个数据包或多个半包。

解决粘包、半包问题的关键在于标识顺位。通讯协议通常分为以下四种:

协议类型思路说明
固定包头包的前几个字节固定表示后续数据的长度,每次读取时先读包头、再按长度读包体
不固定包头通过约定特征字节来识别包头位置,找到包头后再解析包体长度
固定长度收发双方预先约定每个数据包长度固定,按固定长度切分即可
字符区间使用特定的起始符和/或终止符来标识数据包的边界

绝大多数通讯协议都是以上四种的一种或混合体。适配器会大幅简化这类解析工作。

2.2 非流式多线程

Udp 等非流式场景的数据包是相互独立的,每个数据包自成一体,不存在粘包问题。因此适配器在此场景下的作用主要是将原始数据封装成 IRequestInfo 对象投递给上层。

三、设计架构

3.1 整体工作流程

以下流程图展示了适配器在 TouchSocket 中的完整工作过程:

3.2 数据流转逻辑

适配器接收到原始字节流后,通过 PreviewReceivedAsync 方法进行解析,完成后以以下任意组合调用 GoReceivedAsync 投递数据:

  • 仅投递 ReadOnlyMemory<byte>IRequestInfonull,适用于 FixedHeaderPackageAdapterFixedSizePackageAdapterTerminatorPackageAdapter 等内置包适配器。
  • 仅投递 IRequestInfoMemoryEmpty,适用于继承 CustomDataHandlingAdapter 自定义解析的场景。
  • 两者均投递:适用于 HTTP 等高性能场景,包头以 IRequestInfo 投递,Body 以 Memory 传递(直接来自内存池,避免额外复制)。

3.3 适配器类型体系

3.4 设计解释

TouchSocket 设计为同时投递 MemoryIRequestInfo 两个参数,而非像某些框架那样在会话上泛型约束适配器类型,主要有以下原因:

  1. 支持运行时切换适配器:以 WebSocket 为例,握手阶段需要用 HTTP 适配器解析请求,握手完成后需要切换为 WebSocket 帧适配器。泛型约束会破坏这种灵活性。
  2. 提升高性能场景下的效率:例如 HTTP 适配器,包头通过 IRequestInfo 投递,而 Body 通过 Memory(直接来自内存池)投递,避免了额外的内存分配和复制。

四、Tcp / NamedPipe / Serial 使用

TcpNamedPipeSerialPort 均使用 SingleStreamDataHandlingAdapter,配置方式完全一致。

4.1 在 Config 中配置

Config 中配置适配器相当于初始化赋值,连接建立前生效。配置时必须传入工厂委托(每次新建实例),不能传入单例

🔄 正在加载代码...
内置适配器

TouchSocket 内置了以下常用适配器,可按需选择:

适配器类说明
FixedHeaderPackageAdapter固定包头,支持 Byte / Ushort / Int 三种包头类型
FixedSizePackageAdapter固定长度分包
TerminatorPackageAdapter终止符分包,如 \r\n
PeriodPackageAdapter周期合包,在超时时间内将多次收到的数据合并为一个包
JsonPackageAdapter按 JSON 对象边界分包
注意

同一个适配器实例只能被一个终端使用一次。如果将同一实例赋值给多个客户端,会在 OnLoaded 时抛出异常。请始终通过工厂委托(() => new XxxAdapter())的方式创建适配器。

五、Udp 使用

Udp 使用 UdpDataHandlingAdapter只能通过 Config 配置,不支持运行时切换。

🔄 正在加载代码...

六、限制适配器(封装场景)

SetAdapterprotected 方法,外部代码无法直接调用。在自定义封装场景下,若需要强制锁定适配器类型,可继承 TcpSessionClient(服务端 SessionClient 场景),在 OnTcpConnecting 内部调用 SetAdapter,外部则永远无法通过任何公开 API 更换适配器。

🔄 正在加载代码...

七、通过 AdapterOption 统一配置参数

AdapterOption 可以在 Config 中统一设置适配器的通用参数,设置后会在适配器加载时自动应用:

属性说明
MaxPackageSize最大允许的数据包大小(字节),超出则抛异常,默认 10MB
CacheTimeoutEnable是否启用缓存超时(仅流式适配器有效),默认 false
CacheTimeout缓存超时时长,默认 1 秒
🔄 正在加载代码...
缓存超时说明

启用缓存超时后,若一个数据包在超时时间内未完成接收,适配器会丢弃已缓存的部分数据并重新开始解析。这主要用于应对发送方发送异常数据(或运营商插入无效包)导致缓冲区数据永久无法完成解析的情况。