环形数组 自动提取起始符和结束符之间的数据

    我目前就职于一家工业制造行业公司。

 

    在工作中经常经常碰到通讯的问题,往往双方会有一个简单的约定:在要传输的数据前后分别加上“起始符”和“终止符”,中间为有效信息。形如:“起始符”+“数据”+“结束符”,对于发送一方来说,将数据打包在“起始符”和“结束符”中间是一件相当容易的事情。但接收方为了正确提取“起始符”和“终止符”中间的有效数据却并不容易。因为要判断“结束符”是否发送过来了,本次处理完数据后,还需要处理本次的残留数据(即下一有效数据的前半部分),已然在某项目中较好处理了这一解析过程,但往往通用性并不好。

 

    发送方源源不断的发送这样的数据块: “起始符”+“数据”+“结束符”,不管由于什么原因,数据可能不会刚好一组一组的到达,但所有数据拼接起来后,就是有顺序的一组接一组,即数据中穿插着“起始符”和结束符。并且先到的组要先得到处理,这跟先进先出队列很相似。    为了增强代码的可重用性,我设计了一个类,专门用来解析这样的数据流:“起始符”+“数据”+“结束符” + “起始符”+“数据”+“结束符”......

 

    内部使用环形数组存放收到的数据流,两个整型变量分别记录数组中读写位置。查找“结束符”(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);
}

本人能力有限,若有问题烦请大侠指正!    我目前就职于一家工业制造行业,我的邮箱是wuxfei@gmail.com。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值