C++實戰(zhàn)之二進制數(shù)據(jù)處理與封裝_第1頁
C++實戰(zhàn)之二進制數(shù)據(jù)處理與封裝_第2頁
C++實戰(zhàn)之二進制數(shù)據(jù)處理與封裝_第3頁
C++實戰(zhàn)之二進制數(shù)據(jù)處理與封裝_第4頁
C++實戰(zhàn)之二進制數(shù)據(jù)處理與封裝_第5頁
已閱讀5頁,還剩4頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)

文檔簡介

第C++實戰(zhàn)之二進制數(shù)據(jù)處理與封裝目錄前言什么是二進制數(shù)據(jù)處理二進制數(shù)據(jù)封裝二進制數(shù)據(jù)思路1:基于配置文件思路2:基于數(shù)據(jù)底層存儲方式

前言

最近在研究所做網(wǎng)絡(luò)終端測試的項目,包括一些嵌入式和底層數(shù)據(jù)幀的封裝調(diào)用。之前很少接觸對二進制原始數(shù)據(jù)的處理與封裝,所以在此進行整理。

以下例子主要以c++語言進行說明。

什么是二進制數(shù)據(jù)

在電腦上一切數(shù)據(jù)都是通過二進制(0或1)進行存儲的,通過多位二進制數(shù)據(jù)可以進而表示整形、浮點型、字符、字符串等各種基礎(chǔ)類型數(shù)據(jù)或者一些更復(fù)雜的數(shù)據(jù)格式。

針對日常中一般的需求進行編程,我們通常無需關(guān)注底層的二進制數(shù)據(jù)。但如果要處理二進制文件(音頻、視頻、圖片等)、設(shè)計空間上更高效的數(shù)據(jù)結(jié)構(gòu)(網(wǎng)絡(luò)數(shù)據(jù)幀、字節(jié)碼、protobuf)或者處理某些底層時,需要我們處理這些二進制數(shù)據(jù)。

計算機中,稱每一個二進制位為比特(bit,也稱:位),是計算機中的最小存儲單位。

每8比特組成一個字節(jié)(byte),一般是計算機實際存儲和處理的最小單位(可以是它的倍數(shù)),也就是說,計算機是以字節(jié)為最小單位分配空間或進行計算的,不能分配比字節(jié)更小的存儲空間(如,最小的數(shù)據(jù)類型是char,長度1字節(jié),不支持申請6比特存儲空間)或者直接處理小于字節(jié)單位的數(shù)據(jù)(如,兩個4比特的數(shù)據(jù)相加減)。

若干字節(jié)構(gòu)成一個計算機字(簡稱:字,word),表示計算機一次性處理事務(wù)的固定長度二進制數(shù)據(jù),字的位數(shù)為字長。計算機是以字為單位處理或運算的,兩個常見的概念是CPU位數(shù)和操作系統(tǒng)位數(shù)。

CPU的位數(shù)就是指CPU執(zhí)行一次指令能處理的最大位數(shù)(一個字長),和CPU中的寄存器的位數(shù)對應(yīng)。其中,地址寄存器MAR限制了計算機的尋址范圍,數(shù)據(jù)寄存器MDR限制了一次處理的數(shù)據(jù)長度。更多的位數(shù)帶來了更大的尋址空間和更強的運算能力。

說明:尋址范圍不等于內(nèi)存大小,尋址對象有內(nèi)存條、顯卡內(nèi)存、聲卡、網(wǎng)卡和其他設(shè)備。之所以常把尋址范圍當(dāng)作內(nèi)存上限,是因為內(nèi)存是CPU的主要尋址對象。

這里解釋一下常見的指令架構(gòu):x86是intel推出的一種指令集架構(gòu)(復(fù)雜指令集CISC架構(gòu)),一開始只有32位的,叫x86_32;后來AMD公司推出了兼容x86_32的64位指令集amd64,被業(yè)界接受,intel將其改名為x86_64,簡稱x64,而x86_32和x86_64可統(tǒng)稱為x86。與x86相對的是基于精簡指令集RISC架構(gòu)的ARM指令集架構(gòu),多用于移動設(shè)備。

操作系統(tǒng)基于CPU指令集實現(xiàn),所以操作系統(tǒng)位數(shù)也直接對應(yīng)CPU位數(shù)。由于CPU指令集的向下兼容性,所以32位操作系統(tǒng)也可以運行在64位的CPU上,但反過來不行。操作系統(tǒng)對軟件提供了向下兼容的能力,64位的操作系統(tǒng)支持64和32位的程序,但32位的操作系統(tǒng)只支持32位的程序。

處理二進制數(shù)據(jù)

在大多語言中,最小的數(shù)據(jù)類型是char,一個字節(jié),二進制數(shù)據(jù)多用unsignedchar表示,并寫作uint8。語言底層常把它當(dāng)作int進行運算。

二進制常數(shù)以0b開頭,如:0b001。二進制數(shù)據(jù)也常用8進制(以0開頭)和16進制(以0x開頭)表示,如:0257(175,八進制)、0x1f(31,16進制)。8進制1個數(shù)字表示3位二進制數(shù)據(jù),16進制1個數(shù)字表示4位二進制數(shù)據(jù),一個字節(jié)可以用2個16進制數(shù)表示。

若要處理小于一字節(jié)的數(shù)據(jù),就要使用位運算符(、|、^、~、、)。

位運算符描述運算規(guī)則用途與兩個位都為1時,結(jié)果才為1二進制位清零或得到指定位數(shù)據(jù)|或兩個位都為0時,結(jié)果才為0二進制位設(shè)置為1;與對應(yīng)位為0的數(shù)據(jù)相加^異或兩個位相同為0,相異為1反轉(zhuǎn)指定位~取反0變1,1變0二進制位全部取反左移各二進位全部左移若干位,高位丟棄,低位補0求x2nx2n;將數(shù)據(jù)移到高位右移各二進位全部右移若干位,對無符號數(shù),高位補0,有符號數(shù),各編譯器處理方法不一樣,有的補符號位(算術(shù)右移),有的補0(邏輯右移)求x/2nx/2n;將數(shù)據(jù)移到低位

舉個例子,判斷某個字節(jié)的第3位是否是1:

//先清0其他位,再判斷是否等于0b100

boolisOne=(byte0b100)==0b100;

再舉個例子,計算機網(wǎng)絡(luò)IP協(xié)議中的controlflag和fragmentoffset合起來存儲在IP頭部的第7、8字節(jié),flag占前三位,后13位為fragmentoffset,可以通過以下運算獲得flag和offset:

//獲得flag要截取byte7前3位數(shù)據(jù):先清空后5位,保留前3位數(shù)據(jù),再右移5位將前3位數(shù)據(jù)移到起始

uint8_tflag=(byte70b11100000)5;

//此處以大端存儲,獲得offset要截取byte7的低5位作為高位,byte8作為低位,求和:先清空byte7前3位,保留后5位數(shù)據(jù),把它移到高8位上,再通過全0的低8位與byte8按位求或來求二者之和

((byte70b00011111)8)|byte8;

補充說明,當(dāng)需要多個字節(jié)表示一個數(shù)據(jù)類型時,需要定義數(shù)據(jù)的高位字節(jié)是存儲在高位地址空間還是低位地址空間,這就是大小端的定義。大端指高位字節(jié)存在低位地址,這是人的手寫習(xí)慣;小端指低位字節(jié)存高位地址。在處理用多個字節(jié)表示的數(shù)據(jù)時,首先要搞清楚數(shù)據(jù)是大端還是小端。

所以,我們可以基于上述知識寫一個無符號整形與字節(jié)流相互轉(zhuǎn)換的通用方法:

//true為大端,低位地址存高位字節(jié)

boolENDIAN=true;

*將data轉(zhuǎn)換為無符號整形數(shù)字(無符號char,short,int,long,longlong等)

*@tparamT目標類型,默認為uint32_t

*@paramdata載荷數(shù)據(jù)byte數(shù)組

*@paramvalueSize數(shù)據(jù)長度,單位:byte,-1表示根據(jù)T類型自動計算

*@paramdefault_value默認值,默認為0

*@return根據(jù)data轉(zhuǎn)換的無符號整形數(shù)據(jù)

templatetypenameT=uint32_t

TpayloadToUnsignedInt(std::vectoruint8_tdata,intvalueSize=-1,Tdefault_value=uint32_t(0)){

if(valueSize==-1)valueSize=sizeof(T);

if(valueSizedata.size())returndefault_value;

Tvalue=0;

for(inti=0;ivalueSize;i++){

if(ENDIAN){

value|=(data[i]0xff)((valueSize-1-i)3);

}else{

value|=(data[i]0xff)(i3);

returnvalue;

*無符號整形轉(zhuǎn)換為載荷byte數(shù)組

*@paramvalue無符號整形數(shù)據(jù)

*@paramvalueSize數(shù)據(jù)長度,單位:byte,-1表示根據(jù)T類型自動計算

*@return載荷byte數(shù)組

templatetypenameT

std::vectoruint8_tuintToPayload(Tvalue,intvalueSize=-1){

if(valueSize==-1)valueSize=sizeof(T);

std::vectoruint8_tdata(valueSize,0);

for(inti=0;ivalueSize;i++){

if(ENDIAN){

data[i]=(value((valueSize-1-i)3))0xff;

}else{

data[i]=(value(i3))0xff;

returndata;

}

封裝二進制數(shù)據(jù)

掌握了二進制數(shù)據(jù)的處理方法,接下來就是對二進制數(shù)據(jù)的封裝,將其封裝為人可以理解的對象。

二進制數(shù)據(jù)通常以uint8_t數(shù)組表示,不同位有不同的含義,需要根據(jù)實際含義進行解析后得到有意義的目標信息。所以重點就是描述每一位的含義,并基于該描述解析二進制數(shù)據(jù),提供二進制數(shù)據(jù)與有含義的對象的相互轉(zhuǎn)換。

思路1:基于配置文件

此處以自定義的二進制指令封裝為例進行說明(項目地址),但該配置項目適用于任意二進制數(shù)據(jù)封裝場景。面對這個需求,首先想到的是通過配置文件描述二進制流每一位的含義,加載配置文件后根據(jù)一些過濾條件配置確定當(dāng)前二進制流段實際對應(yīng)的配置并解析為字典。

由于項目包括一些嵌入式的內(nèi)容,需要把所有文件編譯后燒入板子,不支持存儲普通文件格式的配置文件,所以采用變量形式的配置,全局聲明配置的類型信息和配置對象(cmd_manager),項目內(nèi)任意位置定義該配置對象即可。在其他場景也可選擇Json、xml等配置格式。

本文設(shè)計的配置對象定義方式如下:

/**

*載荷配置項

constCmdManagercmd_manager={2,{//指令個數(shù),下面是每一個指令的配置

{"TCRQ",3,{//配置項名,配置項對應(yīng)的字段數(shù)

{"TE_SEQ_NO",-1,FT_SHORT,0},//具體配置項內(nèi)字段配置(字段名,字段偏移,字段類型,配置項該字段過濾條件

{"CMD",-1,FT_CHARS_4,"TCRQ"},//配置項要求該字段等于"TCRQ",數(shù)據(jù)不滿足則不匹配該配置項

{"REPEAT_COUNT",-1,FT_SHORT,0}}}

}};

項目會自動加載該配置對象,之后針對原始二進制數(shù)據(jù)通過PayloadObjectMapFactory工廠匹配對應(yīng)配置并生成數(shù)據(jù)對象,可從數(shù)據(jù)對象獲得該對象類型(配置項名)并讀寫其中的字段值。或者指定配置項創(chuàng)建空的數(shù)據(jù)對象,進行數(shù)據(jù)設(shè)置后獲得其原始二進制數(shù)據(jù)載荷。

評價

該思路通過配置文件可以自由且動態(tài)的調(diào)整解析方式,易于復(fù)用、拓展或調(diào)整。其難點在于配置格式的設(shè)計,同時字典類型數(shù)據(jù)無法如直接聲明類型結(jié)構(gòu)那樣清晰易用。

思路2:基于數(shù)據(jù)底層存儲方式

此處以計算機網(wǎng)絡(luò)數(shù)據(jù)幀封裝為例進行說明。c++底層對對象/結(jié)構(gòu)體的成員字段采用類型對齊連續(xù)存儲方式,使用該特性可以基于實際含義自然聲明、使用字段,同時可以直接作為二進制數(shù)據(jù)流處理。實現(xiàn)示例如下:

/**

*數(shù)據(jù)抽象類,提供二進制流到對象的相互轉(zhuǎn)化能力

*內(nèi)部類,只復(fù)用代碼,不用于多態(tài)

*@tparamsize數(shù)據(jù)字節(jié)長度

templateintsize

classDataType{

public:

DataType(){resetData();}

//初始化所有數(shù)據(jù)

voidresetData()const{memset((void*)(this),0,size);}

//從二進制流加載數(shù)據(jù)

boolloadData(conststd::vectoruint8_tdata,intstartIndex=0){

auto*p=(uint8_t*)this;//將自身當(dāng)作二進制數(shù)組處理

for(inti=0;isize;i++){

*p=data[i+startIndex];

p++;

returntrue;

//基于自身生成新的二進制數(shù)據(jù)流

[[nodiscard]]std::vectoruint8_tcreateData()const{

std::vectoruint8_tresult;

autop=(uint8_tconst*)this;

for(inti=0;isize;i++){

result.push_back(*p);

p++;

returnresult;

[[nodiscard]]intgetSize()const{returnsize;}

//以順序聲明方式定義具體的二進制數(shù)據(jù)類型,支持嵌套聲明

classMACHeader:publicDataType14{

public:

//通過上述無符號整形與字節(jié)流相互轉(zhuǎn)化的方法將netType的讀寫進行封裝

[[nodiscard]]uint16_tgetNetType()const{

returnpayloadToUnsignedInt(std::vectoruint8_t(netType.begin(),netType.end()),2,uint16_t(0));

voidsetNetType(uint16_t_netType){

autodata=uintToPayload(_n

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論