有时候发送的数据过长,接收的时候只接收了一部分,会出现错误。这里以客户端接收服务端消息为例,讲解一种解包的方法,作为备忘(总是忘没办法)
1.客户端有一段缓冲区char m_szAnalysisBuf[51200] 成员变量,用于存放接收数据,在回调函数收到数据后,判断缓冲区是否满了(一般不会满),未满的话将新接收的数据加入到缓冲区中,并更新缓冲区长度。如果缓冲区满了,则舍弃掉原缓冲区的数据,将新的数据加入缓冲区。
2.循环解包,首先我们要保证,起始的位置就是包头位置,前面的数据均为垃圾数据,因为每次截取一个完整包之后,理论上接下来的数据应该就是包头。进入AnalysisPacket函数,这里查找包头位置,如果没有找到,保留最后的(IMAH_XML_START_LEN-1)个字符,并移动到开始位置,其他的数据清空,如果找到了,但是不在缓冲区的起始位置,就把缓冲区包头前边的垃圾数据清空,保证起始位置就是包头。然后查找数据包结束位置,如果找到了,返回完整数据包起始位置,如果未找到,继续接收数据,并再次查找数据包结束位置,直到找到完整数据包,或缓冲区满。
这样可以保证,缓冲区开始位置就是数据包开始位置,如果缓冲区内找到数据包结束位置,就返回一个完整数据包,剩下的缓冲区数据继续查找,删除掉垃圾数据,继续保证缓冲开始位置为数据包起始位置,如此循环;如果没有找到结束位置,就继续接收,直到找到完整数据包。
这样就可以每次解析一个完整的数据包了,代码如下。
还要注意,如果解析出的完整包是错误信息,跳过PraseData后记得移除这段数据。
///< 处理客户端接收的数据
int CImahConfigTcpClient::TcpClientRecvData(int nTcpClientID,
const char *pServerIP, int nServerPort, const char *pData, unsigned int uiDataLen)
{
int nRet = 0;
bool bRet = false;
int nPocketEndPos = 0; // 数据结尾位置
IMAH_MSG_HEADER stMsgHeader; // 消息头
// 如果解析缓冲区未满,将数据复制到解析缓冲区
if ((m_nAnalysisDataLen + uiDataLen) <= sizeof(m_szAnalysisBuf))
{
memcpy(&m_szAnalysisBuf[m_nAnalysisDataLen], pData, uiDataLen);
m_nAnalysisDataLen += uiDataLen;
}
else // 如果解析缓冲区满了,舍弃解析缓冲区原来的数据,将新数据复制到解析缓冲区
{
g_logHostCfg.Log(JN_WARN, "TcpClientRecvData analysis buf is full. lost data:%d", m_nAnalysisDataLen);
memset(m_szAnalysisBuf, 0, sizeof(m_szAnalysisBuf));
memcpy(&m_szAnalysisBuf[0], pData, uiDataLen);
m_nAnalysisDataLen = uiDataLen;
}
//g_logHostCfg.Log(JN_DEBUG, "len:%d buf:%s", uiDataLen, pData);
// 循环解析数据包,
// 有可能接收到的数据可以解析出来多个数据包,所以要循环,每次解析出来一个数据包
while (true)
{
nPocketEndPos = 0;
// 没有数据,跳出循环
if (m_nAnalysisDataLen <= 0)
{
break;
}
// 解析数据包
nRet = Imah_AnalysisPacket(m_szAnalysisBuf, sizeof(m_szAnalysisBuf), m_nAnalysisDataLen, nPocketEndPos);
// 解析数据失败,跳出循环
if (nRet != 0)
{
break;
}
// 未找到完整的数据包,跳出循环
if ((nPocketEndPos <= 0) || (nPocketEndPos > sizeof(m_szAnalysisBuf)))
{
break;
}
memset(&stMsgHeader, 0, sizeof(stMsgHeader));
// 找到一个完整的数据包,解析消息头
nRet = Message_Decode_MsgHeader(m_szAnalysisBuf, nPocketEndPos, &stMsgHeader);
if (nRet != 0)
{
g_logHostCfg.Log(JN_ERROR, "TcpClientRecvData RnssGb_Decode_MsgHeader error. len:%d xml:%s",
nPocketEndPos, m_szAnalysisBuf);
}
else
{
// 判断UUID、正在等待的信息命令类型,是否一致
if ((m_strNowUuid.compare(stMsgHeader.szMsgUUID) == 0) &&
(m_strRecvCmdType.compare(stMsgHeader.szCmdType) == 0))
{
// 解析xml数据
ParseXmlData(stMsgHeader, nPocketEndPos);
}
}
// 数据前移,去掉了已经复制出来的部分数据
m_nAnalysisDataLen -= nPocketEndPos;
if (m_nAnalysisDataLen <= 0)
{
m_nAnalysisDataLen = 0;
}
else
{
memmove(m_szAnalysisBuf, &m_szAnalysisBuf[nPocketEndPos], m_nAnalysisDataLen);
// 移动后,后面无效的数据清空
memset(&m_szAnalysisBuf[m_nAnalysisDataLen], 0, nPocketEndPos);
}
}
return nRet;
}
AnalysisData:
//----------------------------------------------------------
/**
* RNSS消息完整数据包解析函数
* 根据一个数据包的开始、结束标识,解析出一个完整数据包。
* 完整数据包的开始位置就是pAnalysisBuf的开始位置,
* 完整数据包的结束位置是pAnalysisBuf的nPacketEndPos。
* 当nPacketEndPos为0时,表明没有解析出完整的数据包
* @param[in/out] pAnalysisBuf 解析数据缓冲区
* @param[in] uiAnalysisBufLen 解析数据缓冲区长度
* @param[in/out] nAnalysisDataLen 解析数据缓冲区中数据的长度
* @param[out] nPacketEndPos 解析后的数据包的结束位置
* @return 成功返回 0
* @return 失败返回 -1
*/
int Imah_AnalysisPacket(char *pAnalysisBuf, unsigned int uiAnalysisBufLen, int &nAnalysisDataLen, int &nPacketEndPos)
{
int nRet = 0;
try
{
// 检查参数
if ((NULL == pAnalysisBuf) || (uiAnalysisBufLen == 0))
{
// g_logProvData.Log(JN_WARN, "RnssGb_AnalysisPacket param error. buf:%s buflen:%d datalen:%d",
// pAnalysisBuf, uiAnalysisBufLen, nAnalysisDataLen);
nPacketEndPos = 0;
return 0;
}
// 没有数据
if (nAnalysisDataLen <= 0)
{
nPacketEndPos = 0;
return 0;
}
int nStartPos = -1; // 数据包的开始位置
// 查找数据包的开始位置
for (int i = 0; i < (nAnalysisDataLen - IMAH_XML_START_LEN); i++)
{
if (memcmp(pAnalysisBuf + i, IMAH_XML_START, IMAH_XML_START_LEN) == 0)
{
nStartPos = i;
break;
}
}
// 没找到数据包的开始位置
if (nStartPos < 0)
{
// 保留最后的(IMAH_XML_START_LEN-1)个字符,并移动到开始位置,其他的数据清空
if (nAnalysisDataLen > IMAH_XML_START_LEN)
{
nStartPos = nAnalysisDataLen - (IMAH_XML_START_LEN - 1);
nAnalysisDataLen -= nStartPos;
memmove(pAnalysisBuf, pAnalysisBuf + nStartPos, nAnalysisDataLen);
// 移动后,后面无效的数据清空
memset(pAnalysisBuf + nAnalysisDataLen, 0, nStartPos);
}
nPacketEndPos = 0;
return 0;
}
// 数据包的开始位置不在缓冲区的开始位置
if (nStartPos > 0)
{
// 丢掉垃圾数据,缓冲区的数据向前移动
nAnalysisDataLen -= nStartPos;
memmove(pAnalysisBuf, pAnalysisBuf + nStartPos, nAnalysisDataLen);
// 移动后,后面无效的数据清空
memset(pAnalysisBuf + nAnalysisDataLen, 0, nStartPos);
}
int nEndPos = 0; // 数据包的结束位置
// 查找数据包的结束位置
for (int i = IMAH_XML_START_LEN; i <= (nAnalysisDataLen - IMAH_XML_END_LEN); i++)
{
if (memcmp(pAnalysisBuf + i, IMAH_XML_END, IMAH_XML_END_LEN) == 0)
{
nEndPos = i;
break;
}
}
// 没找到数据包的结束位置
if (nEndPos < IMAH_XML_START_LEN)
{
nPacketEndPos = 0;
return 0;
}
// 找到了数据包的结束位置,设置完整数据包的结束位置
nPacketEndPos = nEndPos + IMAH_XML_END_LEN;
}
catch (std::exception &ex)
{
//g_logProvData.Log(JN_ERROR, "RnssGb_AnalysisPacket exception:%s", ex.what());
nRet = -1;
}
return nRet;
}