当前位置: 首页 > news >正文

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

设计一个可变长度的数据通信协议编码和解码代码。
要有本机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);elsecrc >>= 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();}}}

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 快速排序及归并排序的实现与排序的稳定性
  • 业务终端动态分配IP-DHCP技术、DHCP中继技术
  • Go语言中GC(垃圾回收回收机制)三色标记与混合写屏障
  • 智能手术新时代:Apple Vision Pro在医疗领域的突破性应用
  • 【计算机网络】学习指南及导论
  • 每日练习,不要放弃
  • Java程序打印日志
  • 怎么找抖音视频素材?下载抖音的素材视频网站分享给你
  • Python实现音频均衡和降噪
  • 服务端正常启动了,但是客户端请求不到
  • Go 1.19.4 函数-Day 08
  • 大数据基础:Doris重点架构原理
  • [ACM独立出版] 2024年虚拟现实、图像和信号处理国际学术会议(VRISP 2024,8月2日-4)
  • 【简历】惠州某二本学院:前端简历指导,秋招面试通过率为0
  • SEO:6个避免被搜索引擎惩罚的策略-华媒舍
  • 【刷算法】从上往下打印二叉树
  • Centos6.8 使用rpm安装mysql5.7
  • cookie和session
  • CSS进阶篇--用CSS开启硬件加速来提高网站性能
  • echarts花样作死的坑
  • ES6 ...操作符
  • java小心机(3)| 浅析finalize()
  • Laravel 菜鸟晋级之路
  • MySQL用户中的%到底包不包括localhost?
  • nodejs实现webservice问题总结
  • PHP CLI应用的调试原理
  • React Native移动开发实战-3-实现页面间的数据传递
  • swift基础之_对象 实例方法 对象方法。
  • 个人博客开发系列:评论功能之GitHub账号OAuth授权
  • 技术发展面试
  • 离散点最小(凸)包围边界查找
  • 爬虫模拟登陆 SegmentFault
  • 深度学习之轻量级神经网络在TWS蓝牙音频处理器上的部署
  • 测评:对于写作的人来说,Markdown是你最好的朋友 ...
  • 曾刷新两项世界纪录,腾讯优图人脸检测算法 DSFD 正式开源 ...
  • ​数据链路层——流量控制可靠传输机制 ​
  • #!/usr/bin/python与#!/usr/bin/env python的区别
  • #Datawhale AI夏令营第4期#多模态大模型复盘
  • #HarmonyOS:Web组件的使用
  • $L^p$ 调和函数恒为零
  • (NO.00004)iOS实现打砖块游戏(九):游戏中小球与反弹棒的碰撞
  • (附源码)spring boot火车票售卖系统 毕业设计 211004
  • (附源码)ssm高校运动会管理系统 毕业设计 020419
  • (十)c52学习之旅-定时器实验
  • (十三)Flask之特殊装饰器详解
  • (转)项目管理杂谈-我所期望的新人
  • .NET “底层”异步编程模式——异步编程模型(Asynchronous Programming Model,APM)...
  • .NET MVC第五章、模型绑定获取表单数据
  • .net MySql
  • .net 使用ajax控件后如何调用前端脚本
  • /var/spool/postfix/maildrop 下有大量文件
  • @RequestParam @RequestBody @PathVariable 等参数绑定注解详解
  • @RequestParam详解
  • @Resource和@Autowired的区别
  • [ web基础篇 ] Burp Suite 爆破 Basic 认证密码