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

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

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

cs 复制代码
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();
        }
    }

}
相关推荐
安科瑞刘鸿鹏31 分钟前
高低压配电系统中电弧光的危害有多大?
运维·网络·能源
robin59111 小时前
CentOS 7下CX5-RDMA网络测试
linux·网络·centos·云计算
VB.Net1 小时前
EmguCV学习笔记 VB.Net 11.6 图像分割
opencv·计算机视觉·c#·图像分割·dnn·vb.net·emgucv
yusur2 小时前
产品探秘|开物——面向AI原生和云原生网络研究的首选科研平台
网络·云原生·ai-native·dpu
xiaoxiongip6662 小时前
HTTP 协议介绍
网络·网络协议·http
MediaTea2 小时前
Pr:Adobe SRT
linux·运维·服务器·网络·adobe
serene943 小时前
DL/T645-2007 通信库(C#版本)
物联网·c#·modbus··电力·智能电表·dlt645
晨曦丿3 小时前
TCP和UDP的区别
网络·tcp/ip·udp
__water3 小时前
『功能项目』事件中心【43】
c#·unity引擎·事件中心
dark_20017 小时前
ESXI8.0 vsphere vcenter 多网卡多网段配置
运维·服务器·网络