---
id: customdatahandlingadapter
title: 用户自定义适配器
---

### 定义

命名空间：TouchSocket.Core <br/>
程序集：[TouchSocket.Core.dll](https://www.nuget.org/packages/TouchSocket.Core)

## 一、说明

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

## 二、运行逻辑

<img src={require('@site/static/img/docs/customdatahandlingadapter-1.png').default} width="700" />

返回指令类型：

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

:::danger 注意

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

:::  

## 三、特点

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


## 四、使用

还是以下列数据为例：

<img src={require('@site/static/img/docs/datahandleadapter-1.png').default}/>

步骤

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

【定义适配器】

```csharp showLineNumbers
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(in ByteBlock byteBlock, bool beCached, ref MyRequestInfo request, ref int tempCapacity)
    {
        //以下解析思路为一次性解析，不考虑缓存的临时对象。

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

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

        MyRequestInfo myRequestInfo = new MyRequestInfo();

        //此操作实际上有两个作用，
        //1.填充header
        //2.将byteBlock.Pos递增3的长度。
        byteBlock.Read(out byte[] header, 3);//填充header

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

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

            myRequestInfo.DataType = header[1];
            myRequestInfo.OrderType = header[2];
            myRequestInfo.Body = body;
            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; }
}
```

使用

```csharp {7,30}
static TcpClient CreateClient()
{
    var client = new TcpClient();
    //载入配置
    client.Setup(new TouchSocketConfig()
        .SetRemoteIPHost("127.0.0.1:7789")
        .SetTcpDataHandlingAdapter(() => new MyCustomDataHandlingAdapter())
        .ConfigureContainer(a =>
        {
            a.AddConsoleLogger();//添加一个日志注入
        }));

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

static 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 EasyTask.CompletedTask;
    };

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

:::tip 提示

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

:::  

:::tip 提示

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

:::  

[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Adapter/CustomAdapterConsoleApp)