C# 优雅处理通信中的粘包和断包(一) 处理固定长的的协议
前言
今天我们讲一下怎么用BytesIO里面的解包器优雅的解析带有协议的数据包。
什么是协议?
比如说我收到了一包数据,然后我去处理它。在我处理的同时呢,我又收到了两包数据。当我处理完手中的这一包数据再回过头来看我的缓存区,哎!我发现两包数据它们粘在一起了。那我应该怎么办呢?当然是要把它们拆开,而我拆开它们的依据就是——协议。
有的协议呢它是一个固定长度的,比如我们说有一种协议,它的数据包长度固定就是十个字节。也就是说每十个字节我都可以认为它是一个完整的数据包。
有的协议呢它也没有说固定是多长,它的长度是在数据包的第n位注明的。从数据包的头部开始读取,读到长度位,它告诉你后续还有20个字节。
还有的协议它是以一个固定的符号作为分割,比如说回车、换行符这种。我也不知道我前面有多长,但是一遇特定的结束符号就知道这一包数据结束了。
这就是协议的作用。
为什么要用解包器呢?
因为在发送数据和接收数据的过程中有可能会发生粘包和断包这两种情况。
什么是粘包呢?
就如上文所说的那样,我在处理一个数据包的时候,多个数据包堆叠在了缓存区中,这样就发生了粘包。
什么是断包呢?
断包是为什么呢?对于发送端的种种原因,他一句话没说完,他给你来个大喘气,只说了前半部分,但是你通过前半部分呢已经知道他的话还没有说完,你就在那耐心的等他,等他…这就是遇到了断包。
遇到断包你就需要把前半部分存起来,耐心的等后半部分消息传来把前后数据拼接完整才认为这是一个完整的数据包。然后再进行处理。
视频教程
【女朋友都能学会】C# 协议解包器(优雅处理粘包断包)
源代码
通信协议格式
教学视频中展示出的协议格式草图(群友小伙伴提供):
针对此协议格式完成的数据包代码如下:
/// <summary>
/// 数据协议
/// </summary>
public class SimpleData : Response, IRequest
{
/// <summary>
/// 协议起始标记
/// </summary>
public static readonly byte[] StartMark = new byte[] { 0xDD, 0xEE };
/// <summary>
/// 功能码
/// </summary>
public FunctionCode Code { get; }
/// <summary>
/// 方位角度
/// </summary>
public short Heading { get; }
/// <summary>
/// 俯仰角度
/// </summary>
public short Tilt { get; }
public SimpleData(IEnumerable<byte> bytes) : base(bytes)
{
var data = bytes.ToArray();
if (bytes.Count() != 8)
{
throw new ArgumentException("长度应为8位");
}
if (!bytes.StartWith(StartMark))
{
throw new ArgumentException("协议头错误");
}
if (CheckSum(bytes.Skip(2).Take(5)) != bytes.Last())
{
throw new ArgumentException("校验失败");
}
Code = (FunctionCode)data[2];
Heading = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, 3));
Tilt = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, 5));
}
public SimpleData(FunctionCode code, short heading, short tilt) : base(null)
{
Code = code;
Heading = heading;
Tilt = tilt;
}
public byte[] GetBytes()
{
List<byte> bytes = new List<byte>();
bytes.AddRange(StartMark);
bytes.Add((byte)Code);
bytes.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder(Heading)));
bytes.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder(Tilt)));
byte checkSum = CheckSum(bytes.Skip(2));
bytes.Add(checkSum);
return bytes.ToArray();
}
private byte CheckSum(IEnumerable<byte> bytes)
{
byte checksum = 0x00;
foreach (byte bt in bytes)
{
checksum ^= bt;
}
return checksum;
}
/// <summary>
/// 功能码
/// </summary>
public enum FunctionCode : byte
{
/// <summary>
/// 停止
/// </summary>
Stop = 0,
/// <summary>
/// 定点
/// </summary>
FixedPoint = 1,
/// <summary>
/// 扇扫
/// </summary>
Scan = 2,
}
}
解包器的实现
由于协议草稿中展示的协议格式是一种非常简单的固定长度协议,所以在实现计算数据包长度的方法时只需要返回固定的8位即可。
/// <summary>
/// 固定长度的协议解包器
/// 数据包长度固定,实现非常简单;
/// 重写CalculatePacketLength方法时返回固定长度值即可
/// </summary>
public class SimpleDataUnpacker : Unpacker
{
public SimpleDataUnpacker()
{
StartMark = SimpleData.StartMark;
}
protected override int CalculatePacketLength(IEnumerable<byte> bytes) => 8;
}
运行结果
模拟了断包和粘包数据的接收,在视频中可以看到运行结果。
相关文章
C# 优雅处理通信中的粘包和断包(一) 处理固定长的的协议
C# 优雅处理通信中的粘包和断包(二) 处理非固定长度的协议
C# 优雅处理通信中的粘包和断包(三) 处理使用结束符的协议