原始自定义适配器
说明
自定义适配器可从两个方面入手。
- 则是直接从DataHandlingAdapter继承,此时可以接触到最原始的TCP数据,可以自定实现数据的继续投递方式。但一般实现算法比较困难,因为所考虑的情况比较多。
- 则是从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);
}
}
【解析接收数据】 从原生适配器解封数据,需要考虑的情况比较多。在本示例中,需要考虑以下情况:
- 一次刚好接收一个数据。
- 一次刚好接收了多个数据。
- 一次接收了多个数据,但最后一个数据不完整。
- 一次未接收完一个数据。
综上,情况比较复杂,所以就必须自己做接收数据的缓存容器。
/// <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);
}