数据传输中的组帧和组包

  • 一、数据帧,数据包的概念
    • 数据帧
    • 组包
  • 二、 程序实现:
    • 2.1、frame(帧)类的实现:
    • 2.2、Pack(包)类的实现:
  • 三、测试

一、数据帧,数据包的概念

数据帧

数据传输往往都有一定的协议,通过CRC校验来验证数据的可靠性。数据帧包含三部分,帧头数据部分帧尾。其中帧头和帧尾包含一些必要的控制信息,比如同步信息,地址信息、差错控制信息等等。

组包

多个数据帧可以捆在一起,添加包头信息,就可以组包。组包可以使得多帧的数据同时发送,提高通信的效率。

数据的帧包可以提高数据传输的可靠性。

下面来介绍一种数据帧和包的封装:

组帧格式:


为了保证数据的可靠性,我们在帧结构中的长度,指令类型,数据,校验和数据包含5A556A69时需要转义,接收时也需要转义,以防止帧解析出现异常。

一帧数据只有一个指令。指令用于控制设备的状态等

组包格式:

这里我们将包头内容包含 版本信息和帧数据的长度信息。

按照该协议,我们可以串口传输,SOCKET TCP传输中来实现数据的发送和接收。

二、 程序实现:

这里我们讨论上位机SOCKET端的组帧和组包,以及解析帧和解包。我们下Qt中编写测试代码。

2.1、frame(帧)类的实现:

1. 新建一个frame类,命名为frame。 在frame.h中我们如下设计

第一步:
设置数据区格式:

#define INT8U unsigned char
#define INT32U unsigned int
#define INT16U unsigned short  # define MAX_MSG_LEN 128     typedef struct _Msg_
{INT8U   length;INT8U       crc;INT8U       data[MAX_MSG_LEN];
}Msg,*pMsg;

第二步:
设计组帧和解析帧

 bool PackFrame(Msg src, INT8U * dst, INT8U *len); //组包
INT8U UnpackFrame(INT8U ch, Msg *pmsg);   //解包

第三步:
因为我们还要实现对帧中的帧长度,数据区,校验中实现转义,于是我们定义两个函数:

 INT8U protocol_convert(INT8U ch);  //转义INT8U protocol_deconvert(INT8U ch);  //反转义

最后,我们添加校验函数

INT8U CRC8( INT8U*buffer, INT8U len);

因为在数据转义中,需要对帧的格式进行判断,我们这里设计一个枚举结构

enum FRAME_STATE
{
F_ERROR = -1,
F_HEADER_H,
F_HEADER_L,
F_LENGTH,
F_DATA,
F_CRC,
F_END_H,
F_END_L,
F_OVER,
};

frame.h 预览如下:

#ifndef FRAME_H
#define FRAME_H#include "encrypt/type.h"
#include "encrypt/encrypt.h"# define MAX_MSG_LEN 128#pragma pack(1)
typedef struct _Msg_
{INT8U   length;INT8U       crc;INT8U       data[MAX_MSG_LEN];
}Msg,*pMsg;
#pragma pack()class Frame
{public:Frame();bool PackFrame(Msg src, INT8U * dst, INT8U *len); //组包INT8U UnpackFrame(INT8U ch, Msg *pmsg);   //解包private:enum FRAME_STATE{F_ERROR = -1,F_HEADER_H,F_HEADER_L,F_LENGTH,F_DATA,F_CRC,F_END_H,F_END_L,F_OVER,};Encrypt *_encrypt;    //加密对象int converter = 0;int data_point = 0;FRAME_STATE frame_state;INT8U protocol_convert(INT8U ch);  //转义INT8U protocol_deconvert(INT8U ch);  //反转义INT8U CRC8( INT8U*buffer, INT8U len);};#endif // FRAME_H

2、frame.cpp 设计如下:
校验:
这里我们通过加密类中的CRC来返回一个CRC校验值,当然我们也一个自定义一个CRC计算的算法来实现

INT8U Frame::CRC8( INT8U*buffer, INT8U len){return _encrypt->CRC8(buffer, len);}

转义:

 INT8U Frame::protocol_convert(INT8U ch){if ((converter == 1) && (ch == 0xA5)){converter = 0;ch = 0x5A;}else if ((converter == 1) && (ch == 0x66)){converter = 0;ch = 0x99;}else if ((converter == 1) && (ch == 0x95)){converter = 0;ch = 0x6A;}else if (converter == 1){frame_state = F_ERROR;}return ch;}

反转义:

INT8U Frame::protocol_deconvert(INT8U ch){INT8U rtn = 0;switch(ch){case 0x5A:rtn = 0xA5;break;case 0x99:rtn = 0x66;break;case 0x6A:rtn = 0x95;break;default:rtn = ch;break;}return rtn;}

组帧和解析帧:

bool Frame::PackFrame(Msg src, INT8U * dst, INT8U *len)
{// 增加CRC校验src.crc = CRC8(src.data, src.length);dst[0] = 0x5A;dst[1] = 0x55;int8_t j = 2;// lenthif (src.length == protocol_deconvert(src.length)){dst[j++] = src.length;}else{dst[j++] = 0x99;dst[j++] = protocol_deconvert(src.length);}//datafor (int i = 0; i < src.length; i++){if (src.data[i] == protocol_deconvert(src.data[i])){dst[j++] = src.data[i];}else{dst[j++] = 0x99;dst[j++] = protocol_deconvert(src.data[i]);}}//crcif (src.crc == protocol_deconvert(src.crc)){dst[j++] = src.crc;}else{dst[j++] = 0x99;dst[j++] = protocol_deconvert(src.crc);}dst[j++] = 0x6A; //packet tail1dst[j++] = 0x69; //packet tail2(*len) = j;return true;}INT8U Frame::UnpackFrame(INT8U ch, Msg *pmsg)
{if ((ch == 0x5a) && (frame_state != F_HEADER_H) && (frame_state != F_CRC)){frame_state = F_HEADER_H;}if ((ch == 0x6a) && (frame_state != F_END_H) && (frame_state != F_CRC)){frame_state = F_ERROR;}if (frame_state == F_HEADER_H){if (ch == 0x5A){data_point = 0;frame_state = F_HEADER_L;}else{frame_state = F_ERROR;}}else if (frame_state == F_HEADER_L){if (ch == 0x55){frame_state = F_LENGTH;}else{frame_state = F_ERROR;}}else if (frame_state == F_LENGTH){if (ch == 0x99){converter = 1;return 0;}pmsg->length = protocol_convert(ch);if (pmsg->length > MAX_MSG_LEN){frame_state = F_ERROR;}else{frame_state = F_DATA;}}else if (frame_state == F_DATA){if (pmsg->length == 0)//没有数据区{frame_state = F_CRC;return 0;}if (ch == 0x99)    //转义{converter = 1;return 0;}pmsg->data[data_point] = protocol_convert(ch);data_point++;if (data_point == pmsg->length){data_point = 0;frame_state = F_CRC;}}else if (frame_state == F_CRC){if (ch == 0x99)    //转义{converter = 1;return 0;}pmsg->crc = protocol_convert(ch);frame_state = F_END_H;}else if (frame_state == F_END_H){if (ch != 0x6A){frame_state = F_ERROR;}else{frame_state = F_END_L;}}else if (frame_state == F_END_L){if (ch != 0x69){frame_state = F_ERROR;}else{// frame_state = FRAME_STATE.F_HEADER_H;//CRC successif (pmsg->crc == CRC8(pmsg->data, pmsg->length)){frame_state = F_HEADER_H;return 1;}else{frame_state = F_ERROR;}}}if (frame_state == F_ERROR){frame_state = F_HEADER_H;return 2;}return 0;
}

在解析帧的过程中,我们用frame_state 作为协议状态机的转换状态,用于确定当前字节处于一帧数据中的那个部位,在数据包接收完的同时也进行了校验的比较。
接收过程中,只要哪一步收到的数据不是预期值,则直接将状态机复位,用于下一帧数据的判断,因此系统出现状态死锁的情况非常少,系统比较稳定。

2.2、Pack(包)类的实现:

packer.h

#ifndef PACKER_H
#define PACKER_H
#include<QList>
#include "protocal/frame.h"const int packVersion  = 1;class Packer
{public:Packer();Frame *ptc;  //帧对象指针QList<Msg*> *lstMsg;// 解包后的通讯数据QByteArray  Pack(QList<Msg> lstMsg);      //组包QList<Msg*> *UnPack(INT8U * data, INT16U packLen);   //解包
};#endif // PACKER_H

packer.cpp

#include "packer.h"
#include<QDebug>
#include<QString>Packer::Packer()
{ptc = new Frame();lstMsg = new QList<Msg*>();
}QByteArray Packer:: Pack(QList<Msg> lstMsg){QByteArray pack;pack.resize(4);pack[0]= (uint8_t)((packVersion & 0xff00)>>8);pack[1] = (uint8_t)(packVersion &0xff);pack[2] = 0;pack[2] = 0;int pos = 4;Msg msg;int i = 0;foreach( msg , lstMsg){INT8U dst[256];INT8U len = 0;ptc->PackFrame(msg,  dst,  &len);INT8U pre_len = pack.size() ;INT8U cur_len = pack.size() + len;pack.resize( cur_len);for(int j = pre_len; j<cur_len;j++ ){pack[j] = dst[j-pre_len];}//      char * p_buf= new char[128]();
//      std::memcpy(p_buf,dst,len);
//      pack.append(p_buf);pos += len;}pos = pos - 4;pack[2] = (uint8_t)((pos & 0xff00) >> 8);pack[3] = (uint8_t)(pos & 0xff);return pack;}QList<Msg*> *Packer::UnPack(INT8U * data, INT16U packLen)   //packLen: 数据区的长度{if (data == NULL){qDebug()<< "数据为空!";return NULL;}int version = data[0] << 8 | data[1];// 版本异常if (version != packVersion){qDebug()<< "协议版本不正确!";return NULL;}int len = data[2] << 8 | data[3];//数据长度异常if (len + 4 > packLen){qDebug()<<  "数据截断异常!" ;return NULL;}if(len + 4 < packLen){qDebug()<<  "数据过长异常!" ;}Msg *pmsg = new Msg();packLen = (INT16U)(len + 4);for (int i = 4; i < packLen; i++){INT8U ch = data[i];INT8U result = ptc->UnpackFrame(ch, pmsg);if (result == 1){lstMsg->append(pmsg);pmsg = new Msg();}}return lstMsg;}

三、测试

我们在main() 函数中添加如下代码 进行测试:

    //解析帧测试unsigned char destdata[] = {0x00,0x01,0x00,0x1b,0x5A,0x55,0x15,0x81,0x31,0xFF,0xD8,0x05,0x4E,0x56,0x33,0x36,0x25,0x39,0x22,0x43,0x72,0xF7,0xFD,0x30,0x23,0x51,0x09,0xEF,0x0A,0x6A,0x69};QList<Msg*> *testlist;Packer *testpacker = new Packer();testlist = testpacker->UnPack(destdata,31);QList<Msg*>::iterator i;for (i = testlist->begin(); i != testlist->end(); ++i){for(int j = 0;j<(*i)->length;j++){qDebug()<<QString::number((*i)->data[j],16) ;}}//组包测试Msg testmsg;testmsg.length = 21;testmsg.data[0] = 0x81;testmsg.data[1] = 0x31;testmsg.data[2] = 0xFF;testmsg.data[3] = 0xD8;testmsg.data[4] = 0x05;testmsg.data[5] = 0x4E;testmsg.data[6] = 0x56;testmsg.data[7] = 0x33;testmsg.data[8] = 0x36;testmsg.data[9] = 0x25;testmsg.data[10] = 0x39;testmsg.data[11] = 0x22;testmsg.data[12] = 0x43;testmsg.data[13] = 0x72;testmsg.data[14] = 0xF7;testmsg.data[15] = 0xFD;testmsg.data[16] = 0x30;testmsg.data[17] = 0x23;testmsg.data[18] = 0x51;testmsg.data[19] = 0x09;testmsg.data[20] = 0xEF;QList<Msg> lstMsg ;lstMsg.append(testmsg);QByteArray ba;ba = testpacker->Pack(lstMsg);qDebug()<<ba.toHex();

输出:
jjjj
“81”
“31”
“ff”
“d8”
“5”
“4e”
“56”
“33”
“36”
“25”
“39”
“22”
“43”
“72”
“f7”
“fd”
“30”
“23”
“51”
“9”
“ef”
“0001001b5a55158131ffd8054e5633362539224372f7fd30235109ef0a6a69”

Qt 实现数据协议控制--组帧、组包、解析帧、解析包相关推荐

  1. 微信小程序控制硬件第17篇 : 腾讯连连小程序通过LLSync蓝牙协议控制安信可PB-02模组,无需网络实现蓝牙本地通讯。(附带源码)

    文章目录 前言 一.注册腾讯物联开发平台设备 新建项目 新建产品 创建数据模板 选择设备开发方式 交互开发配置 新建设备 二.设备端操作 硬件简介与连接 准备软件环境 工程目录 烧录步骤 三.腾讯连连 ...

  2. 串口调试助手源代码 qt编写 带协议解析 帧判断 通信数据保存等功能

    串口调试助手源代码 qt编写 带协议解析 帧判断 通信数据保存等功能 使用说明介绍 1.功能介绍: 采用Qt编写的串口调试助手工具,功能齐全,除了具备十六进制收发及文件保存等基本功能外,还具有以下功能 ...

  3. wireshark抓组播数据_HCIE学习笔记--组播路由协议PIM-DM工作机制解析

    拓扑 二---PIM--DM扩散 组播源发出的第一份ping包此组播域当中的哪些链路和路由器会接收到?解释对应机制与工作原理 组播路由器接口开启了PIM-DM的都会接收到组播报文. 在PIM-DM模式 ...

  4. ISME:全基因组关联研究揭示了控制根际微生物组遗传力的植物基因位点

    编译:微科盟阿Z,编辑:微科盟木木夕.江舜尧. 微科盟原创微文,欢迎转发转载,转载须注明来源<微生态>公众号. 导读 最近已证明宿主遗传学是植物微生物组组成的驱动因素之一.然而,确定控制微 ...

  5. UDP协议、广播、组播和多路复用(网络编程二)

    一.udp通信 1. 基本流程 udp发送端 udp接收端 socket() socket() bind(); bind(); sendto/recvfrom sendto/recvfrom clos ...

  6. 国家基因组科学数据中心(NGDC)---组学原始数据如何上传GSA

    文章目录 前言 一.什么是NGDC? 二.NGDC的发展历程 三.什么是GSA? 四.为什么选择上传数据到GSA? 五.如何上传测序原始数据至GSA?(重点!!附详细步骤!!) 1. 准备要上传的数据 ...

  7. mSystems:苏晓泉、徐健-基于大数据引擎的全球微生物组转化网络

    一个无标度.完全关联的全球转化网络是已知微生物组多样性的基础 A Scale-Free, Fully Connected Global Transition Network Underlies Kno ...

  8. 组播源不一定属于组播组,它向组播组发送数据,自己不一定是接收者。可以同时有多个...

    组播方式传输信息 综上所述,单播方式适合用户稀少的网络,而广播方式适合用户稠密的网络,当网络中需要某信息的用户量不确定时,单播和广播方式效率很低. IP组播技术的出现及时解决了这个问题.当网络中的某些 ...

  9. [数据分析与可视化] 数据绘图要点6-数据组过多

    数据绘图要点6-数据组过多 比较几个数值变量的分布是数据展示中的一项常见任务.变量的分布可以使用直方图或密度图来表示,在同一轴上表示适量数据的组是非常有吸引力的.但是数据组过多将严重影响图表信息表现. ...

最新文章

  1. li:hover背景色
  2. Uva1593 代码对齐
  3. MP4大文件虚拟HLS分片技术,避免服务器大量文件碎片
  4. 在Python中变量名这样写,就是给自己挖坑
  5. 每日程序C语言27-矩阵对角线求和
  6. android飞翔的小鸟游戏素材包_开心消消乐×愤怒的小鸟:为开心而战
  7. 数据结构思维 第十二章 `TreeMap`
  8. ILSpy 6.0 Preview 1 发布,.NET 反编译工具
  9. Java中的ArrayList类和LinkedList
  10. 阶段1 语言基础+高级_1-3-Java语言高级_05-异常与多线程_第4节 等待唤醒机制_1_线程状态概述...
  11. DOMContentLoaded、readystatechange、load、ready详谈
  12. 2018年第九届蓝桥杯决赛JAVA B 题解(全)
  13. Mysql如何按照指定间隔时间查询数据
  14. Customer类的设计
  15. 物理模拟重力 斜抛运动计算 抛物线计算
  16. (学信网联合万方)免费论文查重
  17. error: Failed dependencies: perl(Data::Dumper) is needed by MySQL-server-5.6.46-1.el7.x8
  18. PHP slideup,三级下拉菜单(slideDown/slideUp实现)
  19. 坑爹公司大盘点 --- 转自拉钩
  20. android是硬件还是软件,浅谈Android软硬件巧妙整合的开发技巧

热门文章

  1. 【Matlab】山地建模?立体热度?怎么绘制三维曲面图?
  2. [云炬创业管理笔记]第一章测试3
  3. [云炬创业基础笔记] 第三章测试10~12
  4. 科大星云诗社动态20210418
  5. 《秋暮登北楼》王武陵
  6. ps意外崩溃_充满意外的数学中考
  7. python迭代器生成器使用技巧(1):遍历、代理、生成器创建迭代、反向迭代
  8. SVM熟练到精通3:核函数与非线性分类
  9. 快速转换vs2008到vs2010
  10. 在一个JS文件中包含中文字符串,通过innerHTML输出后中文乱码?