跳到主要内容
版本:2.1

适配器消息构建器

定义

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

一、说明

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

二、使用消息构建器

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

在实际应用中,大家可能对包模式适配器比较疑惑。为什么本来不具备粘分包的组件,在使用包模式适配器后,就具备了粘分包的能力?

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

2.1 直接构建数组消息

固定包头包适配器为例:

下列摘抄部分源码

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

最后,将封装的数据通过GoSend(异步是GoSendAsync)函数发送出去。

注意

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

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

/// <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构建消息

好奇的小伙伴应该发现了,PreviewSendPreviewSendAsync方法都有一个参数为IRequestInfo的重载,那么这个IRequestInfo是什么呢?

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

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

|Tag|Length|Value|

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

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实例,将会变得非常简单。

var myRequestInfo = new MyRequestInfo();
myRequestInfo.Tag = 10;
myRequestInfo.Value = new byte[] { 1, 2, 3 };

那么,我们只需要将MyRequestInfo实现IRequestInfoBuilder接口,并实现其成员即可。

其中:

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

不过,在发送之前,我们还需要设置适配器支持,即重写CanSendRequestInfoTrue

//下列代码为伪代码
class MyAdapter : CustomFixedHeaderDataHandlingAdapter<TRequestInfo>
{
...

public override bool CanSendRequestInfo => true;
}

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

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

提示

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

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