環形陣列 自動提取起始符和結束符之間的資料
阿新 • • 發佈:2019-01-05
我目前就職於一家工業製造行業公司。
在工作中經常經常碰到通訊的問題,往往雙方會有一個簡單的約定:在要傳輸的資料前後分別加上“起始符”和“終止符”,中間為有效資訊。形如:“起始符”+“資料”+“結束符”,對於傳送一方來說,將資料打包在“起始符”和“結束符”中間是一件相當容易的事情。但接收方為了正確提取“起始符”和“終止符”中間的有效資料卻並不容易。因為要判斷“結束符”是否傳送過來了,本次處理完資料後,還需要處理本次的殘留資料(即下一有效資料的前半部分),已然在某專案中較好處理了這一解析過程,但往往通用性並不好。
傳送方源源不斷的傳送這樣的資料塊: “起始符”+“資料”+“結束符”,不管由於什麼原因,資料可能不會剛好一組一組的到達,但所有資料拼接起來後,就是有順序的一組接一組,即資料中穿插著“起始符”和結束符。並且先到的組要先得到處理,這跟先進先出佇列很相似。 為了增強程式碼的可重用性,我設計了一個類,專門用來解析這樣的資料流:“起始符”+“資料”+“結束符” + “起始符”+“資料”+“結束符”......
內部使用環形陣列存放收到的資料流,兩個整型變數分別記錄陣列中讀寫位置。查詢“結束符”(ETX)時,類似在一個大字串中查詢子字串首次出現的位置。
特點:可以設定“起始符”和“結束符”(即STX和ETX),設定好起始符和結束符後,不用再關心如何解析資料。收到資料時呼叫PushData即可壓入資料,呼叫PopData即可得到資料流中的有效資料。
介面說明:
class UTILITY_EXT_CLASS C0x020x03Queue { public: C0x020x03Queue(); virtual ~C0x020x03Queue(); public: // 資料入隊,不檢查首尾標示STX和ETX; // 返回TRUE:表示所有資料成功加入佇列。 // 返回FALSE:剩餘空間已不夠用;若設定了自動清理,將會清掉緩衝區資料; // 剩餘空間不夠用時,將忽略當前的入隊資料,建議先取出資料或清空緩衝區後再次呼叫PushData。 BOOL PushData(BYTE *pData, long lLength); // 資料出隊,請在外部分配好記憶體,傳入首地址和可用空間大小; // 返回非負數 :出隊的位元組數;返回的資料不包含STX、ETX; // 返回-1:佇列頭部非0x02;若設定了自動清理,將會清空緩衝區; // 返回-2:外部的記憶體空間不夠用; long PopData(BYTE *pData, long lLength); // 清空緩衝區中所有資料,臨界區模式; void EmptyData(); // 設定緩衝區已不夠用、佇列頭部非0x02時,是否自動清空緩衝區;預設:不啟用自動清空。 void SetAutoEmpty(BOOL bAutoEmpty); //設定STX,預設STX是0x02,一個位元組 void SetSTX(byte stx[128], int nLen); //設定ETX,預設ETX是0x03,一個位元組 void SetETX(byte etx[128], int nLen); //除錯使用,列印緩衝區所有資料 void OutputDebugData(); private: // 清空緩衝區,非臨界區模式; // 返回值:是否清空了緩衝區資料; BOOL AutoEmptyData(CString strReason); private: long m_lReadPos; // 讀取位置,若有資料可讀,其始終指向可讀資料 long m_lStorePos; // 儲存位置,始終指向可寫區域 long m_lCrtCount; // 當前有效資料長度 BYTE m_uszData[DATA_BUF_MAX_SIZE];//內部資料緩衝區 BOOL m_bAutoEmpty;//緩衝區滿、資料出錯時是否自動清空緩衝區 CRITICAL_SECTION m_CSData; byte m_byteSTX[128];//STX儲存 byte m_byteETX[128];//ETX儲存 int m_nSTXLen; //STX位元組數 int m_nETXLen; //ETX位元組數 };
主要實現部分:
為了方便使用環形陣列中的資料,這裡定義了一個巨集:
#define SAFE_INDEX(Idex) (((Idex)+DATA_BUF_MAX_SIZE)%DATA_BUF_MAX_SIZE)
壓入資料:
long C0x020x03Queue::PopData(BYTE *pData, long lLength) { EnterCriticalSection(&m_CSData); long lRet = 0; long lTmpReadPos = 0; long nLoop =0, nLoop2=0, nLoop3=0;//檢查stx,etx bool bFlag; //查詢ETX輔助 long nValiadLen=0;//淨資料長度(去除了stx和etx) if (0 == m_lCrtCount) { lRet = 0;// 沒有任何資料 goto LClearup; } //STX接收不全 if (m_nSTXLen > m_lCrtCount) { for (nLoop=0; nLoop<m_lCrtCount; nLoop++) { //STX接收不全,已接收部分不匹配 if (m_byteSTX[nLoop] != m_uszData[SAFE_INDEX(nLoop+m_lReadPos)]) { AutoEmptyData(_T("佇列頭部非STX")); lRet = -1; goto LClearup; } } //STX接收不全,但已接收部分正常 lRet = 0; goto LClearup; } //開始查詢STX,正常情況下應該可以找到STX。 for (nLoop=0; nLoop<m_nSTXLen; nLoop++) { if (m_byteSTX[nLoop] != m_uszData[SAFE_INDEX(m_lReadPos+nLoop)]) { lRet = -1; AutoEmptyData(_T("佇列頭部非STX")); goto LClearup; } } if ((m_nSTXLen+m_nETXLen) > m_lCrtCount) { lRet = 0;//緩衝區中一定沒有ETX,因為stx+etx長度大於當前全部資料長度 goto LClearup; } //查詢ETX, lTmpReadPos = SAFE_INDEX(m_lReadPos+m_nSTXLen);//lTmpReadPos指向有效資料 nValiadLen = 0; for (nLoop=0; nLoop<(m_lCrtCount-m_nSTXLen-m_nETXLen+1); nLoop++) { nValiadLen++; bFlag = true; for (nLoop2=0; nLoop2<m_nETXLen; nLoop2++) { if (m_byteETX[nLoop2] != m_uszData[SAFE_INDEX(nLoop2+lTmpReadPos)]) { bFlag = false; break; } } if (bFlag) { nValiadLen-=1; //成功找到了ETX,此時lTmpReadPos指向ETX頭,nValiadLen為淨資料長度 break; } lTmpReadPos = SAFE_INDEX(lTmpReadPos+1); } if (!bFlag) { lRet = 0;//未找到尾部標示etx,可能是資料還不完全 goto LClearup; } else { //找到了ETX,但傳入空間不夠用 if (nValiadLen>lLength) { CString strInfo; strInfo.Format(_T("C0x020x03Queue::PopData 空間不夠用,所需空間:%d,傳入空間:%d.\n"), nValiadLen, lLength); OutputDebugString(strInfo); lRet = -2; goto LClearup; } else { //開始剪下淨資料 m_lReadPos = SAFE_INDEX(m_lReadPos+m_nSTXLen); for (nLoop3=0; nLoop3<nValiadLen; nLoop3++) { pData[nLoop3] = m_uszData[m_lReadPos]; m_lReadPos = SAFE_INDEX(m_lReadPos+1); } m_lReadPos = SAFE_INDEX(m_lReadPos+m_nETXLen); lRet = nValiadLen; m_lCrtCount = m_lCrtCount-m_nSTXLen-m_nETXLen-nValiadLen; } } LClearup: LeaveCriticalSection(&m_CSData); #ifdef _DEBUG OutputDebugData(); #endif return lRet; }
列印除錯
void C0x020x03Queue::OutputDebugData()
{
EnterCriticalSection(&m_CSData);
long ltemp=m_lReadPos;
CString strAll=_T("Queue data=["), str;
for (int i=0; i<m_lCrtCount; i++)
{
str.Format(_T("%02x "), m_uszData[ltemp]);
strAll += str;
ltemp = SAFE_INDEX(ltemp+1);
}
strAll.TrimRight(_T(' '));
strAll += _T("]");
OutputDebugString(strAll);
LeaveCriticalSection(&m_CSData);
}
本人能力有限,若有問題煩請大俠指正! 我目前就職於一家工業製造行業,我的郵箱是[email protected]。