C# 设计一个可变长度的数据通信协议编码和解码代码。

avatar
作者
筋斗云
阅读量:1

设计一个可变长度的数据通信协议编码和解码代码。
要有本机ID字段,远端设备ID字段,指令类型字段,数据体字段,校验字段。其中一个要求是,每次固定收发八个字节,单个数据帧超过八个字节需要分包收发。对接收的数据帧要先存入环形缓存区,解码函数需要对环形缓存区中的协议数据持续解码,直到没有数据。解析出的数据最后逐个列出来,验证对错。对于存在的丢包问题,要求有重发机制。

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;  namespace edcode_s {     /*      * Packet 类表示一个数据包,      * 包含本地ID、远程ID、命令类型、数据长度和数据内容等字段。      */     class Packet     {         public byte LocalID;         public byte RemoteID;         public byte CommandType;         public byte DataLength;         public byte[] Data = new byte[255];         public ushort CRC;     }     /*      * RingBuffer 类实现了一个环形缓冲区,      * 用于存储发送和接收的数据。      * 它有一个固定大小的缓冲区,      * 通过添加和读取操作来管理数据。      */     class RingBuffer     {         private const int BufferSize = 1024;         private byte[] buffer = new byte[BufferSize];         private int start = 0;         private int end = 0;         /*          * Add方法          * 将给定的字节数组data中的数据按顺序添加到循环缓冲区中,          * 直到填满整个缓冲区或达到指定的length长度。          */         public void Add(byte[] data, int length)         {             for (int i = 0; i < length; i++)             {                 buffer[end] = data[i];                 end = (end + 1) % BufferSize;             }         }         /*          * Read方法          * 从循环缓冲区中读取指定长度的数据,          * 并将其存储到给定的字节数组中,          * 从指定的偏移量开始存储。          * 如果没有读取到任何数据(即缓冲区为空),          * 则返回0;否则返回1表示成功读取了数据。          */          public int Read(byte[] data, int offset, int length)         {             if (IsEmpty())                 return 0;              for (int i = 0; i < length; i++)             {                 data[offset + i] = buffer[start];                 start = (start + 1) % BufferSize;             }              return 1;         }          public bool IsEmpty()         {             return start == end;         }     }     /*      * Protocol 类是通信协议的核心部分,      * 负责发送和接收数据包。      * 它使用 RingBuffer 来存储发送和接收的数据。      */     class Protocol     {         private const int PacketSize = 8;         private RingBuffer ringBuffer = new RingBuffer();         private Random rand = new Random();         /*          * SendPacket 方法将一个 Packet 对象转换为字节数组,并计算CRC校验码,          * 然后将数据分块发送到缓冲区中。          * 如果模拟了数据包丢失的情况,则会重新发送数据包。          *           */          public void SendPacket(Packet packet)         {             int totalLength = 4 + packet.DataLength + 2;                 byte[] buffer = new byte[totalLength];             buffer[0] = packet.LocalID;             buffer[1] = packet.RemoteID;             buffer[2] = packet.CommandType;             buffer[3] = packet.DataLength;             Array.Copy(packet.Data, 0, buffer, 4, packet.DataLength);             ushort crc = ComputeCRC(buffer, totalLength - 2);             //变量crc转换为字节数组,并将该字节数组复制到buffer字节数组中。             //totalLength - 2 表示从 buffer 数组的索引位置 totalLength - 2 开始粘贴数据             BitConverter.GetBytes(crc).CopyTo(buffer, totalLength - 2);              for (int i = 0; i < totalLength; i += PacketSize)             {                 int chunkSize = Math.Min(PacketSize, totalLength - i);                 byte[] chunk = new byte[chunkSize];                 Array.Copy(buffer, i, chunk, 0, chunkSize);                  if (!SimulatePacketLoss(0.1f)) // 10% packet loss rate                 {                     ringBuffer.Add(chunk, chunkSize);                 }                 else                 {                     Console.WriteLine("Packet loss occurred, resending...");                     ResendPacket(chunk, chunkSize);                 }             }         }         /*          * DecodePacket 方法从缓冲区中读取数据,          * 解析出数据包的各个字段,          * 并进行CRC校验。如果校验成功,则返回 true,否则返回 false。          */         public bool DecodePacket(out Packet packet)         {             packet = new Packet();             if (ringBuffer.IsEmpty())                 return false;              byte[] header = new byte[4];             ringBuffer.Read(header, 0, 4);             packet.LocalID = header[0];             packet.RemoteID = header[1];             packet.CommandType = header[2];             packet.DataLength = header[3];              packet.Data = new byte[packet.DataLength];             ringBuffer.Read(packet.Data, 0, packet.DataLength);              ushort receivedCRC;             byte[] crcBytes = new byte[2];             ringBuffer.Read(crcBytes, 0, 2);             receivedCRC = BitConverter.ToUInt16(crcBytes, 0);             byte[] fullPacket = new byte[4 + packet.DataLength];             Array.Copy(header, 0, fullPacket, 0, 4);             //将一个数组packet.Data的从0位置开始的长度为packet.DataLength的内容复制到另一个数组fullPacket的4位置。             Array.Copy(packet.Data, 0, fullPacket, 4, packet.DataLength);             ushort computedCRC = ComputeCRC(fullPacket, fullPacket.Length);             if (receivedCRC != computedCRC)             {                 Console.WriteLine("CRC error");                 return false;             }              return true;         }         /*          * ResendPacket 方法将数据包重新添加到缓冲区中,以便重新发送。          */         private void ResendPacket(byte[] data, int length)         {             ringBuffer.Add(data, length);         }         /*          * SimulatePacketLoss 方法根据给定的丢包率随机决定是否模拟数据包丢失。          */         private bool SimulatePacketLoss(float lossRate)         {             return rand.NextDouble() < lossRate;//生成一个0到1之间的随机小数,并将其与lossRate进行比较。         }         /*          * ComputeCRC 方法计算给定数据的CRC校验码(CRC-16)。          */         private ushort ComputeCRC(byte[] data, int length)         {             /*              * 变量crc初始化为0xFFFF。然后使用两个嵌套的循环来遍历数据并计算CRC值。              * 外层循环遍历整个数据数组,内层循环处理每个字节的每一位。              * 在内层循环中,首先检查crc的最低位是否为1。              * 如果是1,则将crc右移一位并与0xA001进行异或操作;              * 否则,直接将crc右移一位。              */               //CRC寄存器被初始化0xFFFF。             ushort crc = 0xFFFF;             for (int i = 0; i < length; i++)             {                 //将数据的第一个字节(8位)与CRC寄存器的当前值进行异或运算,并将结果存回CRC寄存器。                 crc ^= data[i];                 //根据最低有效位(LSB)是否为0来决定下一步操作。                 //如果LSB为0,则直接将CRC寄存器的内容右移一位;                 //如果LSB为1,则在右移一位后,                 //还需要与一个特定的16位多项式(如0xA001)进行异或运算。                 for (int j = 0; j < 8; j++)                 {                     if ((crc & 1) != 0)                         crc = (ushort)((crc >> 1) ^ 0xA001);                     else                         crc >>= 1;                 }             }             return crc;         }     }     /*      * Program 类是程序的入口点,创建一个 Protocol 对象,      * 构造一个 Packet 对象,然后发送该数据包,      * 并尝试解码接收到的数据包。      */     class Program     {         static void Main(string[] args)         {             Protocol protocol = new Protocol();             Packet packet = new Packet();             packet.LocalID = 1;             packet.RemoteID = 2;             packet.CommandType = 0x01;             packet.Data = new byte[] { 0x11, 0x22, 0x33, 0x44, 0x55,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0xaa,0xbb,0xff };             packet.DataLength = byte.Parse(packet.Data.Length.ToString());              protocol.SendPacket(packet);              if (protocol.DecodePacket(out Packet received))             {                 Console.WriteLine("Packet received successfully:");                 Console.WriteLine($"Local ID: {received.LocalID}");                 Console.WriteLine($"Remote ID: {received.RemoteID}");                 Console.WriteLine($"Command Type: {received.CommandType:X2}");                 Console.WriteLine($"Data Length: {received.DataLength}");                 Console.Write("Data: ");                 for (int i = 0; i < received.DataLength; i++)                 {                     Console.Write($"{received.Data[i]:X2} ");                 }                 Console.WriteLine();             }             else             {                 Console.WriteLine("Failed to decode packet or CRC error occurred.");             }             Console.ReadKey();         }     }  } 

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!