--- 
id: customunfixedheaderdatahandlingadapter
title: 模板解析“非固定包头”数据适配器
---

### 定义

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

## 一、说明

有时候，我们需要解析的数据的包头是不定的，例如：HTTP数据格式，其数据头和数据体由“\r\n”隔开，而数据头又因为请求者的请求信息的不同，头部数据量不固定，而数据体的长度，也是由数据头的ContentLength的值显式指定的，所以，可以考虑使用**CustomUnfixedHeaderDataHandlingAdapter**解析。


## 二、特点

1. 可以自由适配**所有**的数据协议。
2. 可以随意定制数据协议。
3. 可以与**任意语言、框架**对接数据。


## 三、使用

客户端与服务器均适用。下列以服务器为例。

步骤

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

【MyUnfixedHeaderRequestInfo】
首先，新建MyFixedHeaderRequestInfo类，然后实现**IUnfixedHeaderRequestInfo**用户自定义固定包头接口。
然后在**OnParsingHeader**函数执行结束时，对实现的`BodyLength`属性作出赋值，以此来决定，后续还应该接收多少数据作为Body 。

```csharp showLineNumbers
public class MyUnfixedHeaderRequestInfo : IUnfixedHeaderRequestInfo
{
    /// <summary>
    /// 接口实现，标识数据长度
    /// </summary>
    public int BodyLength { get; private set; }

    /// <summary>
    /// 自定义属性，标识数据类型
    /// </summary>
    public byte DataType { get; set; }

    /// <summary>
    /// 自定义属性，标识指令类型
    /// </summary>
    public byte OrderType { get; set; }

    /// <summary>
    /// 自定义属性，标识实际数据
    /// </summary>
    public byte[] Body { get; set; }



    public int HeaderLength { get; private set; }

    public bool OnParsingBody(ReadOnlySpan<byte> body)
    {
        if (body.Length == this.BodyLength)
        {
            this.Body = body.ToArray();
            return true;
        }
        return false;
    }


    public bool OnParsingHeader<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlock
    {
        //在使用不固定包头解析时

        //【首先】需要先解析包头
        if (byteBlock.CanReadLength < 3)
        {
            //即直接缓存
            return false;
        }

        //先保存一下初始游标，如果解析时还需要缓存，可能需要回退游标
        var position = byteBlock.Position;

        //【然后】ReadToSpan会递增游标，所以不需要再递增游标
        var header = byteBlock.ReadToSpan(3);

        //如果使用Span自行裁剪的话，就需要手动递增游标
        //var header=byteBlock.Span.Slice(position,3);
        //byteBlock.Position += 3;

        //【然后】解析包头，和BodyLength
        //在该示例中，第一个字节表示后续的所有数据长度，但是header设置的是3，所以后续还应当接收length-2个长度。
        this.BodyLength = header[0] - 2;
        this.DataType = header[1];
        this.OrderType = header[2];

        //【最后】对HeaderLength做有效赋值
        this.HeaderLength = 3;

        return true;
    }
}
```

新建MyCustomUnfixedHeaderDataHandlingAdapter继承**CustomUnfixedHeaderDataHandlingAdapter**。

```csharp showLineNumbers
public class MyUnfixedHeaderCustomDataHandlingAdapter : CustomUnfixedHeaderDataHandlingAdapter<MyUnfixedHeaderRequestInfo>
{
    protected override MyUnfixedHeaderRequestInfo GetInstance()
    {
        return new MyUnfixedHeaderRequestInfo();
    }
}
```

【接收】

```csharp showLineNumbers
private static async Task Main(string[] args)
{
    var service = await CreateService();
    var client = await CreateClient();

    ConsoleLogger.Default.Info("按任意键发送10次");
    while (true)
    {
        Console.ReadKey();
        for (var i = 0; i < 10; i++)
        {
            var myRequestInfo = new MyUnfixedHeaderRequestInfo()
            {
                Body = Encoding.UTF8.GetBytes("hello"),
                DataType = (byte)i,
                OrderType = (byte)i
            };

            //构建发送数据
            using (var byteBlock = new ByteBlock(1024))
            {
                byteBlock.WriteByte((byte)(myRequestInfo.Body.Length + 2));//先写长度，因为该长度还包含数据类型和指令类型，所以+2
                byteBlock.WriteByte((byte)myRequestInfo.DataType);//然后数据类型
                byteBlock.WriteByte((byte)myRequestInfo.OrderType);//然后指令类型
                byteBlock.Write(myRequestInfo.Body);//再写数据

                await client.SendAsync(byteBlock.Memory);
            }
        }
    }
}

private static async Task<TcpClient> CreateClient()
{
    var client = new TcpClient();
    //载入配置
    await client.SetupAsync(new TouchSocketConfig()
         .SetRemoteIPHost("127.0.0.1:7789")
         .SetTcpDataHandlingAdapter(() => new MyUnfixedHeaderCustomDataHandlingAdapter())
         .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 MyUnfixedHeaderRequestInfo 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 MyUnfixedHeaderCustomDataHandlingAdapter())
         .ConfigureContainer(a =>
         {
             a.AddConsoleLogger();//添加一个控制台日志注入（注意：在maui中控制台日志不可用）
         })
         .ConfigurePlugins(a =>
         {
             //a.Add();//此处可以添加插件
         }));
    await service.StartAsync();//启动
    service.Logger.Info("服务器已启动");
    return service;
}
```

:::tip 提示

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

:::  


## 四、适配器纠错

该适配器当收到半包数据时，会自动缓存半包数据，然后等后续数据收到以后执行拼接操作。

但有的时候，可能由于其他原因导致后续数据错乱，这时候就需要纠错。详情可看[适配器纠错](./adaptererrorcorrection.mdx)