数据传输中的组帧和组包
一、数据帧,数据包的概念
数据帧
数据传输往往都有一定的协议,通过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;
// lenth
if (src.length == protocol_deconvert(src.length))
{
dst[j++] = src.length;
}
else
{
dst[j++] = 0x99;
dst[j++] = protocol_deconvert(src.length);
}
//data
for (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]);
}
}
//crc
if (src.crc == protocol_deconvert(src.crc))
{
dst[j++] = src.crc;
}
else
{
dst[j++] = 0x99;
dst[j++] = protocol_deconvert(src.crc);
}
dst[j++] = 0x6A; //packet tail1
dst[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 success
if (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”