网络传输粘包解包处理

有时候发送的数据过长,接收的时候只接收了一部分,会出现错误。这里以客户端接收服务端消息为例,讲解一种解包的方法,作为备忘(总是忘没办法)

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;
}

猜你喜欢

转载自blog.csdn.net/Lasuerte/article/details/82657676
今日推荐