跳到主要内容
版本:2.1

适配器纠错

定义

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

一、说明

适配器纠错功能,用于对接收的数据进行纠错。

二、纠错流式单线程数据

我们在适配器介绍中讲过,流式单线程数据的解析,标识顺位就是解决问题的关键,这也就意味着一般来说,流式单线程数据是不允许有其他数据的。不然数据解析将会发生灾难性故障。

但是有时候,往往环境不是我们所控制的。在数据实际传输时,有极小的情况确实会发生其他数据包混入的情况。

此时,我们就可以使用适配器纠错功能,来解决这种问题。

2.1 纠错原理

对于纠错,我们的目的就是要重新定位到正确数据的起始位置,让数据能够重新解析。那么对于重新定位,我们有以下策略:

  1. 按照算法找到正确的起始位置。这要求数据格式本身就具有验证性。
  2. 即时重置缓存。
  3. 断开连接。

下列,我们将以Tcp数据为例,讲解如何使用适配器纠错功能。

2.2 纠错示例

我们假设一个常见的TLV数据,其格式为:

|Tag|Length|Value|

其中,Tag为4字节,Length为4字节,Value为可变长度的数据。

要解析该数据,我们非常容易的会想到使用模版解析固定包头适配器来解决问题。

所以代码大概如下:

public class MyFixedHeaderCustomDataHandlingAdapter : CustomFixedHeaderDataHandlingAdapter<MyFixedHeaderRequestInfo>
{
/// <summary>
/// 接口实现,指示固定包头长度
/// </summary>
public override int HeaderLength => 8;

/// <summary>
/// 获取新实例
/// </summary>
/// <returns></returns>
protected override MyFixedHeaderRequestInfo GetInstance()
{
return new MyFixedHeaderRequestInfo();
}
}
public class MyFixedHeaderRequestInfo : IFixedHeaderRequestInfo
{
public int Tag { get; set; }
public int Length => this.m_bodyLength;
public byte[] Value { get;private set; }
private int m_bodyLength;

int IFixedHeaderRequestInfo.BodyLength => this.m_bodyLength;

bool IFixedHeaderRequestInfo.OnParsingBody(byte[] body)
{
if (body.Length!=this.m_bodyLength)
{
return false;
}

this.Value = body;
return true;
}

bool IFixedHeaderRequestInfo.OnParsingHeader(byte[] header)
{
if (header.Length!=8)
{
return false;
}

this.Tag = TouchSocketBitConverter.BigEndian.ToInt32(header,0);
this.m_bodyLength = TouchSocketBitConverter.BigEndian.ToInt32(header,4);
return true;
}
}

此时,如果我们的数据是完全符合我们定义的协议的,那么我们的算法就是100%可靠的。

但是,如果我们的数据不符合我们的协议呢?例如:在Tag、Length、Value之间多出n个字节,那么我们的算法就无法解析了。

此时,我们需要对数据进行处理,进行纠错。

2.3 自我纠错

首先,我们来看能不能自己纠错。自己纠错,必须是数据格式自己能识别错误,并且能自己纠错。在此案例中,明显是不适用的,因为简单的TLV数据是不具备自我纠错能力的。

那么什么样的数据可以自我纠错呢?

例如:|Begin|Tag|TagCrc|Length|LengthCrc|Value|ValueCrc|End|

其中假设:Begin、End是固定的“**和##”,Tag、Length均为4字节,TagCrc、LengthCrc、ValueCrc是对应数据的CRC校验。

那么对于此类数据,可能具有一定纠错能力。

原因是,当在解析数据时,如果我们发现该数据并不是以“**”开头,则会认为该数据是错误的。可以在OnParsingHeader中返回false,表示数据错误。然后适配器会向后递推1个字节,再次尝试解析。Tag、Length、Value也可以使用crc校验来决定返回truefalse

聪明的小伙伴应该发现了,这种机制也只能解决部分问题。例如:当冗余数据出现在Tag、Length、Value数据间隔时,可能是有用的。一旦冗余数据在Tag、Length、Value之中时,我们仍然无法解析。所以,这种机制可能能解决部分问题。

但是,这仍然是有意义的,因为纠错的目的不仅仅是纠错还原本次数据,更主要的是能解析下次正确的数据。

2.4 即时重置缓存

即时重置缓存,是指当数据不完整时,立即重置缓存。这也就意味着,当数据不完整时,我们会立即放弃本次数据的解析。直接进行下一次数据的解析。

该操作对于用户自定义解析是容易的。您只需要调用this.Reset()即可。

但是对于模版解析,可能需要传递一些委托来完成重置工作。

例如上述代码,我们可以这样写:

MyFixedHeaderRequestInfo的构造函数中,将MyFixedHeaderCustomDataHandlingAdapterReset函数通过委托传递给MyFixedHeaderRequestInfo

public class MyFixedHeaderCustomDataHandlingAdapter : CustomFixedHeaderDataHandlingAdapter<MyFixedHeaderRequestInfo>
{
/// <summary>
/// 接口实现,指示固定包头长度
/// </summary>
public override int HeaderLength => 8;

/// <summary>
/// 获取新实例
/// </summary>
/// <returns></returns>
protected override MyFixedHeaderRequestInfo GetInstance()
{
return new MyFixedHeaderRequestInfo(this.Reset);
}
}
public class MyFixedHeaderRequestInfo : IFixedHeaderRequestInfo
{
public MyFixedHeaderRequestInfo(Action action)
{
this.m_actionForReset = action;
}
public int Tag { get; set; }
public int Length => this.m_bodyLength;
public byte[] Value { get;private set; }
private int m_bodyLength;
private readonly Action m_actionForReset;

int IFixedHeaderRequestInfo.BodyLength => this.m_bodyLength;

bool IFixedHeaderRequestInfo.OnParsingBody(byte[] body)
{
if (body.Length!=this.m_bodyLength)
{
this.m_actionForReset.Invoke();
return false;
}

this.Value = body;
return true;
}

bool IFixedHeaderRequestInfo.OnParsingHeader(byte[] header)
{
if (header.Length!=8)
{
this.m_actionForReset.Invoke();
return false;
}

this.Tag = TouchSocketBitConverter.BigEndian.ToInt32(header,0);
this.m_bodyLength = TouchSocketBitConverter.BigEndian.ToInt32(header,4);
return true;
}
}

亦或者,使用适配器自带的缓存超时来处理。

例如下列设置:

启用了缓存超时,和超时更新。

这也就意味着,当我们收到一个错误数据时,只要等待1秒以后再重新发送,那么上个错误的缓存将不再有效,也就是此次数据会被正确解析。

public class MyFixedHeaderCustomDataHandlingAdapter : CustomFixedHeaderDataHandlingAdapter<MyFixedHeaderRequestInfo>
{
public MyFixedHeaderCustomDataHandlingAdapter()
{
this.CacheTimeoutEnable = true;
this.CacheTimeout=TimeSpan.FromSeconds(1);
this.UpdateCacheTimeWhenRev = true;
}
/// <summary>
/// 接口实现,指示固定包头长度
/// </summary>
public override int HeaderLength => 8;

/// <summary>
/// 获取新实例
/// </summary>
/// <returns></returns>
protected override MyFixedHeaderRequestInfo GetInstance()
{
return new MyFixedHeaderRequestInfo(this.Reset);
}
}
注意

使用此配置,要注意发送数据的频次,如果是数秒甚至更大时间发送一次数据,那么这将是有用的。

2.5 断开连接

当数据有异常时,直接断开连接,也不失为一种处理方式。同样,该操作对于用户自定义解析是容易的。您只需要调用Owner的相关操作即可。例如:

可以声明一个方法。用于关闭连接。

private void Close()
{
if (this.Owner is ICloseObject client)
{
client.Close("适配器关闭");
}
}

同理,如果是模版解析,依然需要使用委托的方法,将Close()函数传递到IRequestInfo中。此处就不再赘述。