阅读量: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(); } } }