--- 
id: adapterbuilder
title: 适配器消息构建器
---

### 定义

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

## 一、说明

适配器消息构建器，用于构建适配器消息。其目的就是简化上层接口的编写，让上层调用更简单。

## 二、使用消息构建器

消息构建，实际上就是对发送的数据进行再次封装，让他能够正确地被接收端解析。

在实际应用中，大家可能对[包模式适配器](./packageadapter.mdx)比较疑惑。为什么本来不具备粘分包的组件，在使用包模式适配器后，就具备了粘分包的能力？

原因就是：包模式适配器，在发送数据之前，会重写发送，并对数据进行再次封装。然后对方在接收时，只要按照约定解包。这样就实现了粘分包的功能。

### 2.1 直接构建数组消息

以**固定包头包适配器**为例： 

下列摘抄部分[源码](https://gitee.com/RRQM_Home/TouchSocket/blob/master/src/TouchSocket.Core/DataAdapter/_Package/FixedHeaderPackageAdapter.cs#L22)：

`FixedHeaderPackageAdapter`适配器重写了`PreviewSend`与`PreviewSendAsync`的多个重载函数。其目的就是拦截发送数据，并对其进行再次封装。

最后，将封装的数据通过GoSend（异步是GoSendAsync）函数发送出去。

:::caution 注意

重写`PreviewSend`函数时，应该也重写`PreviewSendAsync`函数。不然异步调用会失败。

同时，应该注意，在`PreviewSend`函数中，应该调用`GoSend`函数，在`PreviewSendAsync`函数中，应该调用`GoSendAsync`函数。

:::  



```csharp showLineNumbers
/// <summary>
/// 固定包头数据包处理适配器，支持Byte、UShort、Int三种类型作为包头。使用<see cref="TouchSocketBitConverter.DefaultEndianType"/>大小端设置。
/// </summary>
public class FixedHeaderPackageAdapter : SingleStreamDataHandlingAdapter
{
    /// <summary>
    /// 当发送数据前处理数据
    /// </summary>
    /// <param name="buffer"></param>
    /// <param name="offset"></param>
    /// <param name="length"></param>
    protected override void PreviewSend(byte[] buffer, int offset, int length)
    {
        ...
    }

    /// <summary>
    /// <inheritdoc/>
    /// </summary>
    /// <param name="transferBytes"></param>
    protected override void PreviewSend(IList<ArraySegment<byte>> transferBytes)
    {
        ...
    }

    /// <summary>
    /// <inheritdoc/>
    /// </summary>
    /// <param name="requestInfo"></param>
    protected override void PreviewSend(IRequestInfo requestInfo)
    {
        throw new NotImplementedException();
    }

    /// <inheritdoc/>
    protected override Task PreviewSendAsync(IRequestInfo requestInfo)
    {
        throw new NotImplementedException();
    }

    /// <inheritdoc/>
    protected override async Task PreviewSendAsync(byte[] buffer, int offset, int length)
    {
        ...
    }

    /// <inheritdoc/>
    protected override async Task PreviewSendAsync(IList<ArraySegment<byte>> transferBytes)
    {
        ...
    }
}
```

### 2.2 从IRequestInfo构建消息

好奇的小伙伴应该发现了，`PreviewSend`和`PreviewSendAsync`方法都有一个参数为`IRequestInfo`的重载，那么这个`IRequestInfo`是什么呢？

实际上这就是自定义适配器定义的一个消息实体，我们可以通过该实体，直接发送数据。

例如，我们定义一个TLV数据格式。

|Tag|Length|Value|

其中，Tag为1字节，Length为1字节，Value为可变长度的数据。我们可以声明以下代码：


```csharp showLineNumbers
class MyAdapter : CustomFixedHeaderDataHandlingAdapter<MyRequestInfo>
{
    public override int HeaderLength => 2;

    protected override MyRequestInfo GetInstance()
    {
        throw new NotImplementedException();
    }
}

//未实现接口的伪代码
class MyRequestInfo:IFixedHeaderRequestInfo
{
    private byte[] m_value;

    public byte Tag { get; set; }
    public byte Length => (byte)(this.Value==null ? 0 :this.Value.Length);
    public byte[] Value 
    { 
        get => m_value;
        set 
        {
            if (value == null)
            {
                throw new ArgumentNullException(nameof(value));
            }
            if (value.Length>byte.MaxValue)
            {
                throw new Exception($"数组长度不能大于{byte.MaxValue}");
            }
            m_value = value;
        } 
    }

}
```

假如我们想要发送一个`TLV`数据包，那么一般我们会自己拼接数据。

但是，自己拼接数据很明显不是很好用，我们在编程时还是比较喜欢直接属性操作。

例如：我们还是希望能直接发送`MyRequestInfo`实例，将会变得非常简单。

```csharp showLineNumbers
var myRequestInfo = new MyRequestInfo();
myRequestInfo.Tag = 10;
myRequestInfo.Value = new byte[] { 1, 2, 3 };
```
那么，我们只需要将`MyRequestInfo`实现`IRequestInfoBuilder`接口，并实现其成员即可。

其中：

- `MaxLength`成员，标识了该消息实体最大的长度，其作用是指示内存池申请大小，避免内存池扩张带来的性能消耗。
- `Build`成员，则指示了如何将`MyRequestInfo`实例转换为字节数组。

```csharp showLineNumbers
 class MyRequestInfo:IRequestInfoBuilder
 {
     ...

     int IRequestInfoBuilder.MaxLength => 1024;

     void IRequestInfoBuilder.Build(ByteBlock byteBlock)
     {
         byteBlock.Write((byte)Tag);
         byteBlock.Write((byte)Length);
         byteBlock.Write((byte[])Value);
     }
 }
```

经过上述代码，我们则可以直接向适配器发送`MyRequestInfo`实例。

不过，在发送之前，我们还需要设置适配器支持，即重写`CanSendRequestInfo`为`True`。

```csharp {5} showLineNumbers
//下列代码为伪代码
class MyAdapter : CustomFixedHeaderDataHandlingAdapter<TRequestInfo>
{
    ... 

    public override bool CanSendRequestInfo => true;
}
```

**此时，只要发送的对象实现了`IRequestInfoBuilder`接口，我们就可以直接发送。**

当然，自己也可以直接重写`PreviewSend`函数，实现自定义的发送逻辑。

:::tip 提示

在此示例中，我们只是发送了`MyRequestInfo`实例，但是，我们也可以发送其他类型的实例，只要它实现了`IRequestInfoBuilder`。

当然如果你并不想发送其他类型的实例，你也可以重写`PreviewSend(IRequestInfo)`函数，实现自定义的筛选发送逻辑。

:::  
