跳到主要内容
版本:2.1

用户自定义适配器

定义

命名空间:TouchSocket.Core
程序集:TouchSocket.Core.dll

一、说明

用户自定义适配器,是为了解决当用户的数据是其他情况的问题。不过和原始适配器相比,用户自定义适配器(CustomDataHandlingAdapter)要简单很多。因为他提供了很多指令可以组合处理不同情况,而你只需要简单调用即可。

二、运行逻辑

返回指令类型:

  • FilterResult.Cache:将ByteBlock中的,从ByteBlock.Pos到结束的所有数据进行缓存,用于和下次接收数据做拼接。
  • FilterResult.Success:完成本次数据解析,向Received投递IRequestInfo对象。在返回之前,请一定确保已经修改ByteBlock.Pos属性。不然会发生无限循环的危险情况。
  • FilterResult.GoOn:将ByteBlock.Pos至结束的数据重新投递,所以在返回之前,请一定确保已经修改ByteBlock.Pos属性,至少已经递增一位。不然会发生无限循环的危险情况。
注意

返回Success或者GoOn指令时,请一定确保已经修改ByteBlock.Pos属性,至少已经递增一位。不然会发生无限循环的危险情况。

三、特点

  1. 更加自由度的操作数据。
  2. 能够简单的缓存不能解析的数据。

四、使用

还是以下列数据为例:

步骤

  1. 声明新建类,实现IRequestInfo接口,此对象即为存储数据的实体类,可在此类中声明一些属性,以备使用。
  2. 声明新建类,继承CustomDataHandlingAdapter,并且以步骤1声明的类作为泛型。并实现对应抽象方法。
  3. TouchSocketConfig配置中设置。
  4. 通过Received(事件、方法、插件)中的RequestInfo对象,强转为步骤1声明的类型,然后读取其属性值,以备使用。

【定义适配器】

internal class MyCustomDataHandlingAdapter : CustomDataHandlingAdapter<MyRequestInfo>
{
/// <summary>
/// 筛选解析数据。实例化的TRequest会一直保存,直至解析成功,或手动清除。
/// <para>当不满足解析条件时,请返回<see cref="FilterResult.Cache"/>,此时会保存<see cref="ByteBlock.CanReadLen"/>的数据</para>
/// <para>当数据部分异常时,请移动<see cref="ByteBlock.Pos"/>到指定位置,然后返回<see cref="FilterResult.GoOn"/></para>
/// <para>当完全满足解析条件时,请返回<see cref="FilterResult.Success"/>最后将<see cref="ByteBlock.Pos"/>移至指定位置。</para>
/// </summary>
/// <param name="byteBlock">字节块</param>
/// <param name="beCached">是否为上次遗留对象,当该参数为True时,request也将是上次实例化的对象。</param>
/// <param name="request">对象。</param>
/// <param name="tempCapacity">缓存容量指导,指示当需要缓存时,应该申请多大的内存。</param>
/// <returns></returns>
protected override FilterResult Filter<TByteBlock>(ref TByteBlock byteBlock, bool beCached, ref MyRequestInfo request, ref int tempCapacity)
{
//以下解析思路为一次性解析,不考虑缓存的临时对象。

if (byteBlock.CanReadLength < 3)
{
return FilterResult.Cache;//当头部都无法解析时,直接缓存
}

var pos = byteBlock.Position;//记录初始游标位置,防止本次无法解析时,回退游标。

var myRequestInfo = new MyRequestInfo();

//此操作实际上有两个作用,
//1.填充header
//2.将byteBlock.Pos递增3的长度。
var header = byteBlock.ReadToSpan(3);//填充header

//因为第一个字节表示所有长度,而DataType、OrderType已经包含在了header里面。
//所有只需呀再读取header[0]-2个长度即可。
var bodyLength = (byte)(header[0] - 2);

if (bodyLength > byteBlock.CanReadLength)
{
//body数据不足。
byteBlock.Position = pos;//回退游标
return FilterResult.Cache;
}
else
{
//此操作实际上有两个作用,
//1.填充body
//2.将byteBlock.Pos递增bodyLength的长度。
var body = byteBlock.ReadToSpan(bodyLength);

myRequestInfo.DataType = header[1];
myRequestInfo.OrderType = header[2];
myRequestInfo.Body = body.ToArray();
request = myRequestInfo;//赋值ref
return FilterResult.Success;//返回成功
}
}
}

internal class MyRequestInfo : IRequestInfo
{
/// <summary>
/// 自定义属性,Body
/// </summary>
public byte[] Body { get; internal set; }

/// <summary>
/// 自定义属性,DataType
/// </summary>
public byte DataType { get; internal set; }

/// <summary>
/// 自定义属性,OrderType
/// </summary>
public byte OrderType { get; internal set; }
}

使用

private static async Task<TcpClient> CreateClient()
{
var client = new TcpClient();
//载入配置
await client.SetupAsync(new TouchSocketConfig()
.SetRemoteIPHost("127.0.0.1:7789")
.SetTcpDataHandlingAdapter(() => new MyCustomDataHandlingAdapter())
.ConfigureContainer(a =>
{
a.AddConsoleLogger();//添加一个日志注入
}));

await client.ConnectAsync();//调用连接,当连接不成功时,会抛出异常。
client.Logger.Info("客户端成功连接");
return client;
}

private static async Task<TcpService> CreateService()
{
var service = new TcpService();
service.Received = (client, e) =>
{
//从客户端收到信息

if (e.RequestInfo is MyRequestInfo myRequest)
{
client.Logger.Info($"已从{client.Id}接收到:DataType={myRequest.DataType},OrderType={myRequest.OrderType},消息={Encoding.UTF8.GetString(myRequest.Body)}");
}
return Task.CompletedTask;
};

await service.SetupAsync(new TouchSocketConfig()//载入配置
.SetListenIPHosts("tcp://127.0.0.1:7789", 7790)//同时监听两个地址
.SetTcpDataHandlingAdapter(() => new MyCustomDataHandlingAdapter())
.ConfigureContainer(a =>
{
a.AddConsoleLogger();//添加一个控制台日志注入(注意:在maui中控制台日志不可用)
})
.ConfigurePlugins(a =>
{
//a.Add();//此处可以添加插件
}));
await service.StartAsync();//启动
service.Logger.Info("服务器已启动");
return service;
}
提示

使用自定义适配器的数据,只能通过IRequestInfo来抛出,所以如果有传递其他数据的需求,可以多声明一些属性来完成。

提示

上述创建的适配器客户端与服务器均适用。

本文示例Demo