用户自定义适配器
定义
命名空间: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属性,至少已经递增一位。不然会发生无限循环的危险情况。
三、特点
- 更加自由度的操作数据。
- 能够简单的缓存不能解析的数据。
四、使用
还是以下列数据为例:
步骤
- 声明新建类,实现IRequestInfo接口,此对象即为存储数据的实体类,可在此类中声明一些属性,以备使用。
- 声明新建类,继承CustomDataHandlingAdapter,并且以步骤1声明的类作为泛型。并实现对应抽象方法。
- TouchSocketConfig配置中设置。
- 通过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
来抛出,所以如果有传递其他数据的需求,可以多声明一些属性来完成。
提示
上述创建的适配器客户端与服务器均适用。