Qt 实现数据协议控制--组帧、组包、解析帧、解析包
數據傳輸中的組幀和組包
- 一、數據幀,數據包的概念
- 數據幀
- 組包
- 二、 程序實現:
- 2.1、frame(幀)類的實現:
- 2.2、Pack(包)類的實現:
- 三、測試
一、數據幀,數據包的概念
數據幀
數據傳輸往往都有一定的協議,通過CRC校驗來驗證數據的可靠性。數據幀包含三部分,幀頭、數據部分、幀尾。其中幀頭和幀尾包含一些必要的控制信息,比如同步信息,地址信息、差錯控制信息等等。
組包
多個數據幀可以捆在一起,添加包頭信息,就可以組包。組包可以使得多幀的數據同時發送,提高通信的效率。
數據的幀包可以提高數據傳輸的可靠性。
下面來介紹一種數據幀和包的封裝:
組幀格式:
為了保證數據的可靠性,我們在幀結構中的長度,指令類型,數據,校驗和數據包含5A556A69時需要轉義,接收時也需要轉義,以防止幀解析出現異常。
一幀數據只有一個指令。指令用于控制設備的狀態等
組包格式:
這里我們將包頭內容包含 版本信息和幀數據的長度信息。
按照該協議,我們可以串口傳輸,SOCKET TCP傳輸中來實現數據的發送和接收。
二、 程序實現:
這里我們討論上位機SOCKET端的組幀和組包,以及解析幀和解包。我們下Qt中編寫測試代碼。
2.1、frame(幀)類的實現:
1. 新建一個frame類,命名為frame。 在frame.h中我們如下設計
第一步:
設置數據區格式:
第二步:
設計組幀和解析幀
第三步:
因為我們還要實現對幀中的幀長度,數據區,校驗中實現轉義,于是我們定義兩個函數:
最后,我們添加校驗函數
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_H2、frame.cpp 設計如下:
校驗:
這里我們通過加密類中的CRC來返回一個CRC校驗值,當然我們也一個自定義一個CRC計算的算法來實現
轉義:
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_Hpacker.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 实现数据协议控制--组帧、组包、解析帧、解析包的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于QMap的几点总结思考
- 下一篇: 加权最小二乘法的理解