我目前就职于一家工业制造行业公司。
在工作中经常经常碰到通讯的问题,往往双方会有一个简单的约定:在要传输的数据前后分别加上“起始符”和“终止符”,中间为有效信息。形如:“起始符”+“数据”+“结束符”,对于发送一方来说,将数据打包在“起始符”和“结束符”中间是一件相当容易的事情。但接收方为了正确提取“起始符”和“终止符”中间的有效数据却并不容易。因为要判断“结束符”是否发送过来了,本次处理完数据后,还需要处理本次的残留数据(即下一有效数据的前半部分),已然在某项目中较好处理了这一解析过程,但往往通用性并不好。
发送方源源不断的发送这样的数据块: “起始符”+“数据”+“结束符”,不管由于什么原因,数据可能不会刚好一组一组的到达,但所有数据拼接起来后,就是有顺序的一组接一组,即数据中穿插着“起始符”和结束符。并且先到的组要先得到处理,这跟先进先出队列很相似。 为了增强代码的可重用性,我设计了一个类,专门用来解析这样的数据流:“起始符”+“数据”+“结束符” + “起始符”+“数据”+“结束符”......
内部使用环形数组存放收到的数据流,两个整型变量分别记录数组中读写位置。查找“结束符”(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。