C语言容器中数据的高效串行化和解串--下位机的C语言处理(1)
C语言容器中数据的高效串行化和解串–下位机的C语言处理(1)
文章目录
- C语言容器中数据的高效串行化和解串--下位机的C语言处理(1)
- 摘要
- 1、前言
- 2、 实战代码示例
- 2.1 定义一个类来封装数据容器
- 2.2 定义校验和的计算函数
- 2.3 演示部分
- 2.4 运行结果
- 2.5 结果验证
摘要
使用C#开发上位机的应用程序中,需要频繁的在上位机和下位机之间传递数据,这种传递数据使用值对象是比较合适的。值对象在上位机用C#语言处理,在下位机可容易地使用C语言处理;配合高效的串行化和解串可高效地在上下位机传送数据。采用值对象可有效简化数据传递,减少内存占用,提高处理效率,提高可维护性。
要在上位机和下位机之间传递数据,涉及的内容较广,我们打算分三部分来讲:数据容器,上位机的C#处理,下位机的C处理。
本文讲述下位机的C处理。采用C语言利用联合的可重叠性对容器(结构)中数据高效进行数据容器里的数据串行化和解串处理。
1、前言
图1 本文在系列专题中的位置
下位机的编程大多采用C/C++语言,C++是C的超集,以C语言来做本文的演示更具通用性。C语言中没有类的概念,采用结构(stuct)是合适的。
C语言中的联合(union)的内存是可重叠的,所有成员共享同一块内存,总大小等于最大成员的大小。我们想要的是联合体(union)嵌套结构体(struct)+字节数组,结构体(struct)和字节数组类型的变量共享同一块内存空间,实现内存重叠复用。**通过内存重叠复用,实现结构体和字节数组之间的相互转换。**也就实现了容器中的数据串行化和解串处理。由于这种方法直接读取内存数据,不做其他运算,因而将数据容器里的数据串行化和解串处理效率也是非常高的;我们采用结构体的字节对齐,不会浪费内存。
在上位机和下位机之间传递数据的数据传送中,大多是带宽受限的,像Modbus-RTU。自然是同样传输内容的情况下,数据包/数据帧越小越好;数据串行化和解串处理效率越高越好。在这样的要求下,联合体(union)嵌套结构体(struct)+字节数组就是最佳选择之一。
使用Code::Blocks编程IDE。
2、 实战代码示例
2.1 定义一个类来封装数据容器
#include<stdio.h>#include<stdlib.h>#include<stdint.h>#include<string.h>// 使用1字节对齐,消除结构体间的填充字节#pragmapack(1)//=============================================================================// 结构体定义:DeviceData// 用途:描述设备采集的传感器数据,包含命令、温度、压力、状态和校验码// 注意:由于#pragma pack(1),各成员紧密排列,共占用 2+2+4+1+2 = 11字节//=============================================================================structDeviceData{uint16_tCmd;// 命令码 (2字节):设备指令标识,如读取/写入/控制等操作int16_tTemp;// 温度值 (2字节):有符号整数,表示温度数据(单位:度)floatPress;// 压力值 (4字节):浮点数,表示压力传感器采集的数值unsignedcharState;// 状态标志 (1字节):位标志,可表示设备运行状态等信息uint16_tCRC;// CRC16校验码 (2字节):用于数据完整性校验};//=============================================================================// 联合体定义:Overlay// 用途:实现内存overlay(内存覆盖),结构体成员和字节数组共享同一块内存// 特性:结构体占12字节(含填充),字节数组占11字节,实际共享内存为11字节// 应用场景:便于将结构体数据直接序列化为字节流,或从字节流直接解析//=============================================================================unionOverlay{// 结构体成员:占用 2+2+4+1+2 = 11字节(无填充因为在union中)structDeviceDatadata;// 字节数组成员:显式指定11字节,与结构体共享同一块内存unsignedcharbuffer[11];};上述代码定义了联合体(union Overlay)嵌套结构体(struc DeviceDatat)+字节数组(buffer[11])。这是代码的核心部分。
主要必须使用:#pragma pack(1)保证嵌套结构体(struc DeviceDatat)采用1字节对齐。
2.2 定义校验和的计算函数
//=============================================================================// 函数名:CRC16_Modbus// 功能:计算Modbus标准的CRC16校验码// 参数:data - 输入数据缓冲区指针// len - 数据长度(字节数)// 返回值:uint16_t类型CRC校验码// 算法说明:采用Modbus标准的CRC-16多项式(0xA001),初始值为0xFFFF//=============================================================================uint16_tCRC16_Modbus(uint8_t*data,uint16_tlen){uint16_tcrc=0xFFFF;// 初始化为全1for(uint16_ti=0;i<len;i++){crc^=data[i];// 将数据字节与CRC寄存器进行异或for(uint8_tj=0;j<8;j++){// 处理每一位if(crc&0x0001){// 判断最低位是否为1crc>>=1;// 右移一位crc^=0xA001;// 与生成多项式异或}else{crc>>=1;// 仅右移一位}}}returncrc;}2.3 演示部分
演示部分应用联合体(union Overlay)嵌套结构体(struc DeviceDatat)+字节数组(buffer[11]),调用校验和的计算函数验证:
- 结构体(struc DeviceDatat)-----> 字节数组(buffer[11])
- 字节数组(buffer[11]))-----> 结构体(struc DeviceDatat)
//=============================================================================// 主函数:演示联合体的内存overlay特性// 功能:// 1. 通过结构体方式写入数据并计算CRC校验码// 2. 将数据以字节流方式输出// 3. 模拟从字节流接收数据,解析为结构体//=============================================================================intmain(){// 声明联合体变量,结构体成员和字节数组成员共享同一块内存unionOverlay overlay;//-------------------------------------------------------------------------// 第一部分:构造发送数据(使用结构体方式赋值)//-------------------------------------------------------------------------overlay.data.Cmd=0x0102;// 设置命令码:0x0102overlay.data.Temp=26;// 设置温度值:26度overlay.data.Press=1.25;// 设置压力值:1.25overlay.data.State=1;// 设置状态标志:1// 计算CRC16校验码(只对前9个字节进行校验,不包含CRC字段本身)uint16_tcrc=CRC16_Modbus(&overlay.buffer[0],9);overlay.data.CRC=crc;// 将计算出的CRC写入结构体// 输出结构体形式的各字段值printf("构造的结构体: Cmd: 0x%X, Temp: %d, Press: %f, State: %d, CRC: 0x%X \n",overlay.data.Cmd,overlay.data.Temp,overlay.data.Press,overlay.data.State,overlay.data.CRC);// 输出字节流形式的各字节值(用十六进制显示,字节间用"-"分隔)printf("转换后的字节数组:");for(inti=0;i<11;i++){if(i<10)printf("%02X-",overlay.buffer[i]);// 前10个字节后带"-"elseprintf("%02X \n",overlay.buffer[i]);// 最后一个字节后换行}//-------------------------------------------------------------------------// 第二部分:模拟接收数据(从字节流解析)//-------------------------------------------------------------------------// 先将结构体各字段清零,验证后续字节覆盖的效果overlay.data.Cmd=0;overlay.data.Temp=0;overlay.data.Press=0;overlay.data.State=0;overlay.data.CRC=0;// 模拟接收到的字节流数据(共11字节)// 对应结构体各字段的预期值:// Cmd=0x0102, Temp=26, Press=1.25, State=1, CRC=B9 8FunsignedcharrecBuf[]={0x02,0x01,0x1A,0x00,0x00,0x00,0xA0,0x3F,0x01,0xB9,0x8F};// 将接收到的字节流直接复制到联合体的字节数组中// 由于共享内存的特性,结构体成员同时被更新memcpy(overlay.buffer,recBuf,11);// 输出从字节流解析出的结构体各字段值printf("接收字节构成的结构体: Cmd: 0x%X, Temp: %d, Press: %f, State: %d, CRC: 0x%X \n",overlay.data.Cmd,overlay.data.Temp,overlay.data.Press,overlay.data.State,overlay.data.CRC);return0;// 程序正常结束}2.4 运行结果
定义的结构体: Cmd: 0x102, Temp: 26, Press: 1.250000, State: 1, CRC: 0x8FB9 串行化后的字节数组:02-01-1A-00-00-00-A0-3F-01-B9-8F 解串后形成的结构体:: Cmd: 0x102, Temp: 26, Press: 1.250000, State: 1, CRC: 0x8FB92.5 结果验证
校验和完成后的结构体 data
由于我们使用的Code::Blocks编程IDE 无法自动将数据转为16进制。读者可自行转换。
按Cmd , Temp , Press , State 顺序结构体 data与串行化后的数据02-01-1A-00-00-00-A0-3F-01-B9-8F 相符。
字节数组: 02-01-1A-00-00-00-A0-3F-01-B9-8F 进行解串 ,原结构体 data与解串后的结构体deviceData一致。
