适配器消息构建器
定义
命名空间:TouchSocket.Core
程序集:TouchSocket.Core.dll
一、说明
适配器消息构建器,用于构建适配器消息。其目的就是简化上层接口的编写,让上层调用更简单。
二、使用消息构建器
消息构建,实际上就是对发送的数据进行再次封装,让他能够正确地被接收端解析。
在实际应用中,大家可能对包模式适配器比较疑惑。为什么本来不具备粘分包的组件,在使用包模式适配器后,就具备了粘分包的能力?
原因就是:包模式适配器,在发送数据之前,会重写发送,并对数据进行再次封装。然后对方在接收时,只要按照约定解包。这样就实现了粘分包的功能。
2.1 直接构建数组消息
以固定包头包 适配器为例:
下列摘抄部分源码:
FixedHeaderPackageAdapter
适配器重写了PreviewSend
与PreviewSendAsync
的多个重载函数。其目的就是拦截发送数据,并对其进行再次封装。
最后,将封装的数据通过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构建消息
好奇的小伙伴应该发现了,PreviewSend
和PreviewSendAsync
方法都有一个参数为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
实例。
不过,在发送之前,我们还需要设置适配器支持,即重写CanSendRequestInfo
为True
。
//下列代码为伪代码
class MyAdapter : CustomFixedHeaderDataHandlingAdapter<TRequestInfo>
{
...
public override bool CanSendRequestInfo => true;
}
此时,只要发送的对象实现了IRequestInfoBuilder
接口,我们就可以直接发送。
当然,自己也可以直接重写PreviewSend
函数,实现自定义的发送逻辑。
在此示例中,我们只是发送了MyRequestInfo
实例,但是,我们也可以发送其他类型的实例,只要它实现了IRequestInfoBuilder
。
当然如果你并不想发送其他类型的实例,你也可以重写PreviewSend(IRequestInfo)
函数,实现自定义的筛选发送逻辑。