数据包协议设计(通讯协议的设计)

2023-11-06

一、为什么要设计通讯协议

通常,多设备之间进行通讯多使用数据包的方式。

如何从一堆的数据中确定哪些是有效数据,以及这些数据要表达什么意思。

为解决这些问题,通常我们需要设计一个通讯协议,依照通讯协议对数据进行解析,就能够正确的找到并使用这些数据。

二、通讯协议的一般格式

帧头 帧长度 帧序号 帧命令 帧数据 校验字 帧尾

HEAD

DATA_LEN

FRAME_SEQ

CMD

R/W

 DATA

CRC

TAIL

2 Bit 4 Bit 2 Bit 1 Bit 1 Bit N Bit 2 Bit 2 Bit

 

 

 

 

其中,帧数据包括读写类型以及相关参数

例如:读取命令的格式为

帧头 帧长度 帧序号 帧命令 帧数据(读) 校验字 帧尾

HEAD

DATA_LEN

FRAME_SEQ

CMD

R

 DATA

CRC

TAIL

2 Bit 4 Bit 2 Bit 1 Bit 1 Bit 0 Bit 2 Bit 2 Bit

 

 

 

 

读取数据中可以不需要跟其他数据,所以之后的DATA为0 bit。

 

写入命令的格式为

帧头 帧长度 帧序号 帧命令 帧数据(写) 校验字 帧尾

HEAD

DATA_LEN

FRAME_SEQ

CMD

W

 DATA

CRC

TAIL

2 Bit 4 Bit 2 Bit 1 Bit 1 Bit N Bit 2 Bit 2 Bit

 

 

 

 

写数据之后需要跟上写入的数据内容,所以DATA为N Bit,对应需要写入的数据内容。

三、程序

依照通讯协议的格式,编写好对应的打包和解包程序即可

参考:CRC校验程序编写

3.1 打包程序编写

打包程序主要用于将待发送的数据包进行打包处理。

可以参考以下程序。

#include <stdint.h>
#include <stdio.h>

const uint16_t crc_ta_8[256] = { /* CRC 字节余式表 */
	0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
	0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
	0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
	0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
	0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
	0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
	0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
	0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
	0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
	0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
	0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
	0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
	0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
	0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
	0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
	0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
	0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
	0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
	0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
	0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
	0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
	0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
	0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
	0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
	0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
	0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
	0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
	0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
	0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
	0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
	0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
	0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
};


const int PACKET_HEAD1 = 0xF1; //假设帧头的第一个字节为 0xF1 (这个可以自己定义)
const int PACKET_HEAD2 = 0x1F; //假设帧头的第二个字节为 0x1F (这个可以自己定义)
const int PACKET_TAIL1 = 0xF2; //假设帧尾的第一个字节为 0xF2 (这个可以自己定义)
const int PACKET_TAIL2 = 0x2F; //假设帧尾的第二个字节为 0x2F (这个可以自己定义)
static uint16_t g_frame_seq = 0; //设置全局变量,帧序号

/*函数名称:crc_cal_by_byte;按字节计算CRC
函数参数:uint8_t *ptr:指向发送缓冲区的首字节
uint32_t len:要发送的总字节数
函数返回值:uint16_t
多项式采用CRC-CCITT 0x1021
*/
uint16_t crc_cal_by_byte(uint8_t *ptr, uint32_t len)
{
	uint16_t  crc = 0xffff;

	while (len-- != 0)
	{
		uint16_t high = (unsigned int)(crc / 256); //取CRC高8位
		crc <<= 8;
		crc ^= crc_ta_8[high^*ptr];
		ptr++;
	}
	return crc;
}

/*函数名称:create_cmd_pack; 命令包打包
函数参数:uint8_t *input_buf: 需要进行打包的数据包(输入
		  uint32_t input_buf_len: 需要进行打包处理的数据包长度(输入
		  uint8_t cmd: 需要进行封包的命令字节(输入
		  uint8_t *ret_buf: 返回打包完成的数据包(输出
函数返回值:void
*/
void create_cmd_pack(uint8_t *input_buf, uint32_t input_buf_len, uint8_t cmd, uint8_t *ret_buf)
{
	uint16_t crc_val = 0;
	uint32_t index = 0;
	uint32_t len = input_buf_len + 4 + 1 + 2 + 2 + 2; //4个命令长度, 2个帧序号, 1个命令字节,2个CRC校验,2个包尾(len表示出包头以外的数据包长度
	do
	{
		ret_buf[index++] = (uint8_t)PACKET_HEAD1;
		ret_buf[index++] = (uint8_t)PACKET_HEAD2;
		//len 由高到低存储
		ret_buf[index++] = (uint8_t)(len >> 24) & 0xff;
		ret_buf[index++] = (uint8_t)(len >> 16) & 0xff;
		ret_buf[index++] = (uint8_t)(len >> 8) & 0xff;
		ret_buf[index++] = (uint8_t)(len) & 0xff;
		//g_frame_seq 由高到低存储
		ret_buf[index++] = (uint8_t)(g_frame_seq >> 8) & 0xff;
		ret_buf[index++] = (uint8_t)(g_frame_seq) & 0xff;
		ret_buf[index++] = (uint8_t)cmd;
		for (int i = 0; i<input_buf_len; i++)
		{
			ret_buf[index++] = input_buf[i];
		}
		crc_val = crc_cal_by_byte(ret_buf + 2, index - 2); //CRC校验

		//CRC 由高到低存储
		ret_buf[index++] = (crc_val >> 8) & 0xff;
		ret_buf[index++] = (crc_val >> 0) & 0xff;

		ret_buf[index++] = (uint8_t)PACKET_TAIL1;
		ret_buf[index++] = (uint8_t)PACKET_TAIL2;
	} while (0);
}

测试

int main()
{
	//最大计算65535个数据包
	if (g_frame_seq == 65535)
	{
		g_frame_seq = 0;
	}
	g_frame_seq++; //帧序号+1
	uint8_t test_in_buf[3] = { 0x01, 0xa1,0xa2 }; // 现在假设 0x01为写的标志,则0xa1 0xa2则为写的数据
	uint8_t test_out_buf[3 + 2 + 4 + 2 + 1 + 2 + 2] = { 0 }; // 3:数据,2:帧头,4:数据长度,2:帧序列 1:命令 2:CRC校验 2:帧尾
	create_cmd_pack(test_in_buf, 3, 0x22, test_out_buf); //现在假设要执行0x22命令
	//输出打包好的数据包
	for (int i = 0; i < 16; i++)
	{
		printf("0x%02x ", test_out_buf[i]);
	}
}

输出结果:

0xf1 0x1f 0x00 0x00 0x00 0x0e 0x00 0x01 0x22 0x01 0xa1 0xa2 0x2f 0x11 0xf2 0x2f

3.2 解包程序编写

解包程序主要用于将收到的数据包进行解包处理。

可以参考以下程序。

/*函数名称:analysis_return_pack; 解析命令包
函数参数:uint8_t *input_buf: 带解析的数据包(输入
uint32_t input_buf_len: 需要进行打包处理的数据包长度(输入
uint8_t *ret_buf: 解析后的数据包(输出
函数返回值:uint32_t 解析后的数据包长度
*/
uint32_t analysis_return_pack(uint8_t *input_buf, uint8_t *ret_buf)
{
	uint32_t pack_len = 0, retpack_len = 0;
	do
	{
		if ((input_buf == NULL) || (ret_buf == NULL))
		{
			retpack_len = 0;
			break;
		}
		/*检查包头信息*/
		if ((input_buf[0] == PACKET_HEAD1) && (input_buf[1] == PACKET_HEAD2))
		{

			pack_len = (uint32_t)((input_buf[2]<< 24) | (input_buf[3] << 16) | (input_buf[4] << 8) | (input_buf[5] << 0));
			//读取CRC校验
			uint16_t crc_ret = (uint16_t)((input_buf[pack_len + 2 - 4]) << 8 | (input_buf[pack_len + 2 - 3]) << 0);
			//计算CRC校验
			uint16_t crc_cal = crc_cal_by_byte(input_buf + 2, pack_len - 2 - 2);  //input_buf + 2 : 首地址偏移两个字节 pack_len - 2 - 2:长度 - CRC - 帧尾 + 当前
			//比较两者的CRC校验
			if (crc_ret != crc_cal)
			{
				retpack_len = 0;
				break;
			}
			else
			{
				/*报文长度DataLen4 + 帧序列2 + crc 2 + 报尾2*/
				for (int i = 0; i < pack_len - 10; i++)
				{
					//从命令字开始读
					ret_buf[i] = input_buf[2+ 4 + 2 + i]; //去掉包头2,长度4,帧序列2
				}
				retpack_len = pack_len -10;

			}
		}
	} while (0);
	return retpack_len;
}

测试:

int main()
{
	//最大计算65535个数据包
	if (g_frame_seq == 65535)
	{
		g_frame_seq = 0;
	}
	g_frame_seq++; //帧序号+1
	uint8_t test_in_buf[3] = { 0x01, 0xa1,0xa2 }; // 现在假设 0x01为写的标志,则0xa1 0xa2则为写的数据
	uint8_t test_out_buf[3 + 2 + 4 + 2 + 1 + 2 + 2] = { 0 }; // 3:数据,2:帧头,4:数据长度,2:帧序列 1:命令 2:CRC校验 2:帧尾
	create_cmd_pack(test_in_buf, 3, 0x22, test_out_buf); //现在假设要执行0x22命令
	//现在假设 test_out_buf 为收到的包,对其进行解析
	uint8_t test_buf[4] = { 0 };
	uint32_t ret = analysis_return_pack(test_out_buf, test_buf);
	if (ret != 0)
	{
		printf("\n");
		printf("命令字节: 0x%02x \n", test_buf[0]);
		printf("读写命令: 0x%02x\n", test_buf[1]);
		printf("数据: 0x%02x\n", test_buf[2]);
		printf("数据: 0x%02x\n", test_buf[3]);
	}
}

结果:

命令字节: 0x22
读写命令: 0x01
数据: 0xa1
数据: 0xa2
 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

数据包协议设计(通讯协议的设计) 的相关文章

  • 可转债

    一家上市公司想发行可转债必须满足 连续三年盈利且平均ROE大于10 公司资产负债率小于70 公司累计待还债券余额小于公司净资产40 有这3条标准做保障 债券和股票不一样 前者收益是明确的 到期就能拿到100元票面值 约定利息 所以 你买入的

随机推荐

  • OC门与线与逻辑

    转 OC门与线与逻辑 OC门 又称集电极开路 漏极开路 与非门门电路 Open Collector Open Drain 为什么引入OC门 实际使用中 有时需要两个或两个以上与非门的输出端连接在同一条导线上 将这些与非门上的数据 状态电平
  • 1051 复数乘法

    复数可以写成 A Bi 的常规形式 其中 A 是实部 B 是虚部 i 是虚数单位 满足 i2 1 也可以写成极坐标下的指数形式 R e Pi 其中 R 是复数模 P 是辐角 i 是虚数单位 其等价于三角形式 R cos P isin P 现
  • 如何在虚拟机中安装ikuai软路由系统

    首先访问ikuai官网下载固件固件下载 爱快 iKuai 商业场景网络解决方案提供商 ikuai8 com 根据需求下载 然后创建一个虚拟机 点击下一步 选择更下载的ISO映像文件 点击下一步 点击下一步 设置一下名称和储存位置 点击下一步
  • 学习记录:C语言源文件在编译时产生multiple definition of xxx; xxx: first defined here 相关报错的解决方法

    问题描述 在ubunt18 04 gcc 7 5 0 上可以正常编译的程序在树莓派 gcc 10 2 0 上编译报错 报错如图 问题原因 在头文件内定义全局变量 在多个源文件中引用且未声明 解决方法一 在树莓派上安装7 5 0版本的gcc
  • CPU性能测试及Coremark简介

    衡量处理器的一个重要指标是功耗 另外一个重要指标便是性能 在处理器领域的 Benchmarks 非常众多 有某些个人开发的程序 也有某些标准组织 或者商业公司开发的Benchmarks 本文在此不加以一一枚举 在嵌入式处理器领域最为知名和常
  • 198. House Robber

    You are a professional robber planning to rob houses along a street Each house has a certain amount of money stashed the
  • Docker与DevOps的无敌组合,引爆你的创新潜能

    荣誉认证 51CTO博客专家博主 TOP红人 明日之星 阿里云开发者社区专家博主 技术博主 星级博主 微信公众号 iOS开发上架 本文由iOS开发上架原创 欢迎关注 点赞 收藏 留言 首发时间 2023年8月7日 坚持和努力一定能换来诗与远
  • 代码检查工具选型

    源码分析工具选型 1 目前各种主流源码分析工具简单介绍 1 1 checkstyle checkstyle产生于2001年 是以antlr作为java语法分析器的静态源码分析工具 通过checkstyle的xml配置文件可指定源码分析规则
  • 面试篇:虚拟机栈5连问,一听心里就乐了

    面试路上 滴 滴滴 师傅我们到哪了 我还要赶着面试呢 师傅 快了快了 下个路口就到了 真是服了这帮人了 不会开车净往里凑 听着司机师傅的抱怨声 不禁想起首打油诗 满目尾灯红 耳盈刺笛声 心忧迟到久 颓首似雷轰 一下车赶紧小跑就进了富丽堂皇的
  • Android 控件 RecyclerView 看这篇就够了

    Android 控件 RecyclerView 概述 RecyclerView是什么 从Android 5 0开始 谷歌公司推出了一个用于大量数据展示的新控件RecylerView 可以用来代替传统的ListView 更加强大和灵活 Rec
  • 记录生活(一)

    我为什么要写这篇文章呢 主要是想记录自己的生活 我今天刚学css HTML以前学过一点 2022年1月17日 当日下午做的这两个模板 素材文件夹是两个模板共用的的 布局分明 是用百分比 布局的 灰色部分是导航栏 白色部分是用户登录的头像 绿
  • 数据库课后习题

    数据库练习 1 在Course表中添加 教师 列 20个长度的变长字符串 2 为每门课程添加教师信息 3 将教师列修改为非空列 4 查询选修了刘老师的课程的学生 5 检索选修了课程号为C01或C02课程 且成绩高于或等于70分的学生的姓名
  • 如何区分物联网的三大系统

    物联网是基于Internet的各种物理产品信息服务的综合 它主要由三个系统组成 一个是运营支撑系统 即相关应用服务软件 门户 管道 终端等的管理 其二是传感器网络系统 即通过现有的Internet 广电网络 通信网络等实现数据传输和计算 三
  • 最短路经算法简介(Dijkstra算法,A*算法,D*算法)

    转自 http www embhelp com drew algorithm shortpath htm 作者 Drew 据 Drew 所知最短路经算法现在重要的应用有计算机网络路由算法 机器人探路 交通路线导航 人工智能 游戏设计等等 美
  • C# 结构体的使用

    先说一下结构体和类的区别 1 结构体定义的是变量 保存在栈当中 类的对象 实例 保存在堆当中 引用保存在栈当中 结构体是值类型 类是引用类型 2 不能在结构体中定义默认的构造方法 无参 类中可以定义 3 结构体中自定义构造方法后 编译器会提
  • C++基础:for循环

    美好的知识点从出题开始 输出1 100所有的奇数 看到这道题 你可能有点懵 回顾标题 你找到办法了 但你不知道怎么写 来看看for循环的代码框架吧 for 控制变量初始化表达式 条件表达式 增量表达式 语句1 刚看到这 你肯定不太懂 我实际
  • 面试:Tomcat如何优化

    一 增大tomcat运行内存 例如 从默认的 256M增大到2G SET CATALINA OPTS Xms2048m Xmx4096m XX MaxNewSize 512m XX MaxPermSize 256m set JAVA OPT
  • “此帐户并未得到从这个工作站登录的授权”问题

    此帐户并未得到从这个工作站登录的授权 问题 转自 http blog 163 com yumin wang 126 blog static 36293550201210303413140 问题 访问网络共享文件夹时 出错提示为 此帐户并未得
  • [创业-33]:股权、期权、期股的区别

    目录 1 基本概念 1 1 股权 1 2 期权 1 3 期股 二 比较 2 1 享有的权益 2 2 在退出机制 2 3 兑现机制 2 4 分配方式 2 5 获利方式 附录 雷军关于创业公司的股权解读 1 基本概念 1 1 股权 股权 是有限
  • 数据包协议设计(通讯协议的设计)

    一 为什么要设计通讯协议 通常 多设备之间进行通讯多使用数据包的方式 如何从一堆的数据中确定哪些是有效数据 以及这些数据要表达什么意思 为解决这些问题 通常我们需要设计一个通讯协议 依照通讯协议对数据进行解析 就能够正确的找到并使用这些数据