跳到主要内容
版本:1.3.9

原始自定义适配器

说明

自定义适配器可从两个方面入手。

  1. 则是直接从DataHandlingAdapter继承,此时可以接触到最原始的TCP数据,可以自定实现数据的继续投递方式。但一般实现算法比较困难,因为所考虑的情况比较多。
  2. 则是从CustomDataHandlingAdapter(用户快捷自定义适配器)继承,此时数据的投递必须通过IRequestInfo,ByteBlock将为null。所需考虑的情况比较单一,对于数据的处理也比较简单。

原始DataHandlingAdapter

自己实现适配器,然后使其工作。例如:假设如下数据格式,第一个字节表示整个数据长度(包括数据类型和指令类型),第二字节表示数据类型,第三字节表示指令类型,后续字节表示其他数据。

其次,希望在发送时,只传入数据类型,指令类型和其他数据,而数据长度则由适配器自行封装。最后,希望在接收端每次能接收到一个完整的数据。

实现

首先,创建类,继承自DataHandlingAdapter,然后实现对应属性,及方法。

class MyDataHandleAdapter : DataHandlingAdapter
{
public override bool CanSplicingSend => false;

public override bool CanSendRequestInfo => false;

protected override void PreviewReceived(ByteBlock byteBlock)
{

}

protected override void PreviewSend(byte[] buffer, int offset, int length)
{

}

protected override void PreviewSend(IRequestInfo requestInfo)
{

}

protected override void PreviewSend(IList<ArraySegment<byte>> transferBytes)
{

}

protected override void Reset()
{

}
}

【封装发送数据长度】 封装发送数据时,比较简单,示例如下:

protected override void PreviewSend(byte[] buffer, int offset, int length)
{
int dataLen = length - offset;//先获取需要发送的实际数据长度
if (dataLen > byte.MaxValue)//超长判断
{
throw new OverlengthException("发送数据太长。");
}

//从内存池申请内存块,因为此处数据绝不超过255,所以避免内存池碎片化,每次申请64K
//ByteBlock byteBlock = new ByteBlock(dataLen+1);//实际写法。
using (ByteBlock byteBlock = new ByteBlock(64 * 1024))
{
byteBlock.Write((byte)dataLen);//先写长度
byteBlock.Write(buffer, offset, length);//再写数据
this.GoSend(byteBlock.Buffer, 0, byteBlock.Len);
}
}

【解析接收数据】 从原生适配器解封数据,需要考虑的情况比较多。在本示例中,需要考虑以下情况:

  1. 一次刚好接收一个数据。
  2. 一次刚好接收了多个数据。
  3. 一次接收了多个数据,但最后一个数据不完整。
  4. 一次未接收完一个数据。

综上,情况比较复杂,所以就必须自己做接收数据的缓存容器。

/// <summary>
/// 临时包,此包仅当前实例储存
/// </summary>
private ByteBlock tempByteBlock;

/// <summary>
/// 包剩余长度
/// </summary>
private byte surPlusLength;
protected override void PreviewReceived(ByteBlock byteBlock)
{
byte[] buffer = byteBlock.Buffer;
int r = byteBlock.Len;
if (this.tempByteBlock == null)//如果没有临时包,则直接分包。
{
SplitPackage(buffer, 0, r);
}
else
{
if (surPlusLength == r)//接收长度正好等于剩余长度,组合完数据以后直接处理数据。
{
this.tempByteBlock.Write(buffer, 0, surPlusLength);
PreviewHandle(this.tempByteBlock);
this.tempByteBlock = null;
surPlusLength = 0;
}
else if (surPlusLength < r)//接收长度大于剩余长度,先组合包,然后处理包,然后将剩下的分包。
{
this.tempByteBlock.Write(buffer, 0, surPlusLength);
PreviewHandle(this.tempByteBlock);
this.tempByteBlock = null;
SplitPackage(buffer, surPlusLength, r);
}
else//接收长度小于剩余长度,无法处理包,所以必须先组合包,然后等下次接收。
{
this.tempByteBlock.Write(buffer, 0, r);
surPlusLength -= (byte)r;
}
}
}
/// <summary>
/// 分解包
/// </summary>
/// <param name="dataBuffer"></param>
/// <param name="index"></param>
/// <param name="r"></param>
private void SplitPackage(byte[] dataBuffer, int index, int r)
{
while (index < r)
{
byte length = dataBuffer[index];
int recedSurPlusLength = r - index - 1;
if (recedSurPlusLength >= length)
{
ByteBlock byteBlock = new ByteBlock(length);
byteBlock.Write(dataBuffer, index + 1, length);
PreviewHandle(byteBlock);
surPlusLength = 0;
}
else//半包
{
this.tempByteBlock = new ByteBlock(length);
surPlusLength = (byte)(length - recedSurPlusLength);
this.tempByteBlock.Write(dataBuffer, index + 1, recedSurPlusLength);
}
index += length + 1;
}
}
/// <summary>
/// 处理数据
/// </summary>
/// <param name="byteBlock"></param>
private void PreviewHandle(ByteBlock byteBlock)
{
try
{
this.GoReceived(byteBlock, null);
}
finally
{
byteBlock.Dispose();//在框架里面将内存块释放
}
}

使用自定义适配器

自定义适配器的使用和预设的适配器一样。不过在该案例中,发送数据时,应当传入三个有效值,分别为数据类型指令类型其他数据

封装函数及分片发送意义

在上述案例中,发送数据时应当传入数据类型指令类型其他数据三个有效值,而在RRQM中,发送函数仅有Send(和重载),这无疑需要我们自己封装其他方法假设以下情况需要实现:

数据类型有两种,分别为Up(1),Down(0)。指令类型有两种,分别为Go(1)、Hold(0)。数据类型和指令类型可以任意组合,且均可携带其他数据。

面对上述情况,我们可以封装以下函数使用:

public class MySocketClient : SimpleSocketClient
{
public void Up_Go_Send(byte[] data)
{
ByteBlock byteBlock = new ByteBlock(this.BufferLength);//内存池实现,可以直接new byte[].
byteBlock.Write((byte)1);
byteBlock.Write((byte)1);
byteBlock.Write(data);
try
{
this.Send(byteBlock);
}
finally
{
byteBlock.Dispose();
}
}
public void Down_Go_Send(byte[] data)
{
ByteBlock byteBlock = new ByteBlock(this.BufferLength);//内存池实现,可以直接new byte[].
byteBlock.Write((byte)0);
byteBlock.Write((byte)1);
byteBlock.Write(data);
try
{
this.Send(byteBlock);
}
finally
{
byteBlock.Dispose();
}
}
public void Up_Hold_Send(byte[] data)
{
ByteBlock byteBlock = new ByteBlock(this.BufferLength);//内存池实现,可以直接new byte[].
byteBlock.Write((byte)1);
byteBlock.Write((byte)0);
byteBlock.Write(data);
try
{
this.Send(byteBlock);
}
finally
{
byteBlock.Dispose();
}
}
public void Down_Hold_Send(byte[] data)
{
ByteBlock byteBlock = new ByteBlock(this.BufferLength);//内存池实现,可以直接new byte[].
byteBlock.Write((byte)0);
byteBlock.Write((byte)0);
byteBlock.Write(data);
try
{
this.Send(byteBlock);
}
finally
{
byteBlock.Dispose();
}
}
}

为什么要分片发送??

在示例代码中不难看出,封装的函数将发送数据进行了Write操作(相当于Copy),这无疑是消耗性能的。只是在该案例中,复制的数据最大为255,感觉优化效果甚微,倘若我们需要发送的数据是1Mb,那就相当于在封装数据时,因为前两个字节的存在而复制1Mb的数据(冤死了),然后在适配器中还需要因为数据包头,再复制一次。

优化。。。 所以我们在封装时,可以使用分片发送,但是同时也需要适配器支持。不然内部会出错。

protected override void PreviewSend(IList<ArraySegment<byte>> transferBytes)
{
int dataLen = 0;
foreach (var item in transferBytes)
{
dataLen += item.Count;
}
if (dataLen > byte.MaxValue)//超长判断
{
throw new OverlengthException("发送数据太长。");
}

//从内存池申请内存块,因为此处数据绝不超过255,所以避免内存池碎片化,每次申请64K
//ByteBlock byteBlock = new ByteBlock(dataLen+1);//实际写法。

using (ByteBlock byteBlock = new ByteBlock(64 * 1024))
{
byteBlock.Write((byte)dataLen);//先写长度
foreach (var item in transferBytes)
{
byteBlock.Write(item.Array, item.Offset, item.Count);//依次写入
}
this.GoSend(byteBlock.Buffer, 0, byteBlock.Len);
}
}

重新封装函数。。。

public void Up_Go_SplicingSend(byte[] data)
{
List<TransferByte> transferBytes = new List<TransferByte>();
transferBytes.Add(new TransferByte(new byte[] { 1}));
transferBytes.Add(new TransferByte(new byte[] { 1}));
transferBytes.Add(new TransferByte(data));
this.Send(transferBytes);
}
public void Down_Go_SplicingSend(byte[] data)
{
List<TransferByte> transferBytes = new List<TransferByte>();
transferBytes.Add(new TransferByte(new byte[] { 0 }));
transferBytes.Add(new TransferByte(new byte[] { 1 }));
transferBytes.Add(new TransferByte(data));
this.Send(transferBytes);
}
public void Up_Hold_SplicingSend(byte[] data)
{
List<TransferByte> transferBytes = new List<TransferByte>();
transferBytes.Add(new TransferByte(new byte[] { 1 }));
transferBytes.Add(new TransferByte(new byte[] { 0 }));
transferBytes.Add(new TransferByte(data));
this.Send(transferBytes);
}
public void Down_Hold_SplicingSend(byte[] data)
{
List<TransferByte> transferBytes = new List<TransferByte>();
transferBytes.Add(new TransferByte(new byte[] { 0 }));
transferBytes.Add(new TransferByte(new byte[] { 0 }));
transferBytes.Add(new TransferByte(data));
this.Send(transferBytes);
}