1. 程式人生 > >環形陣列 自動提取起始符和結束符之間的資料

環形陣列 自動提取起始符和結束符之間的資料

    我目前就職於一家工業製造行業公司。

    在工作中經常經常碰到通訊的問題,往往雙方會有一個簡單的約定:在要傳輸的資料前後分別加上“起始符”和“終止符”,中間為有效資訊。形如:“起始符”+“資料”+“結束符”,對於傳送一方來說,將資料打包在“起始符”和“結束符”中間是一件相當容易的事情。但接收方為了正確提取“起始符”和“終止符”中間的有效資料卻並不容易。因為要判斷“結束符”是否傳送過來了,本次處理完資料後,還需要處理本次的殘留資料(即下一有效資料的前半部分),已然在某專案中較好處理了這一解析過程,但往往通用性並不好。

    傳送方源源不斷的傳送這樣的資料塊: “起始符”+“資料”+“結束符”,不管由於什麼原因,資料可能不會剛好一組一組的到達,但所有資料拼接起來後,就是有順序的一組接一組,即資料中穿插著“起始符”和結束符。並且先到的組要先得到處理,這跟先進先出佇列很相似。    為了增強程式碼的可重用性,我設計了一個類,專門用來解析這樣的資料流:“起始符”+“資料”+“結束符” + “起始符”+“資料”+“結束符”......

    內部使用環形陣列存放收到的資料流,兩個整型變數分別記錄陣列中讀寫位置。查詢“結束符”(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]