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

9 篇文章 0 订阅

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

数据帧

数据传输往往都有一定的协议,通过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”

  • 24
    点赞
  • 252
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值