1. 程式人生 > >網路傳輸粘包解包處理

網路傳輸粘包解包處理

有時候傳送的資料過長,接收的時候只接收了一部分,會出現錯誤。這裡以客戶端接收服務端訊息為例,講解一種解包的方法,作為備忘(總是忘沒辦法)

1.客戶端有一段緩衝區char m_szAnalysisBuf[51200] 成員變數,用於存放接收資料,在回撥函式收到資料後,判斷緩衝區是否滿了(一般不會滿),未滿的話將新接收的資料加入到緩衝區中,並更新緩衝區長度。如果緩衝區滿了,則捨棄掉原緩衝區的資料,將新的資料加入緩衝區。

2.迴圈解包,進入AnalysisPacket函式,這裡查詢包頭位置,如果沒有找到,保留最後的(IMAH_XML_START_LEN-1)個字元,並移動到開始位置,其他的資料清空,如果找到了,但是不在緩衝區的起始位置,就把緩衝區包頭前邊的垃圾資料清空,保證起始位置就是包頭。然後查詢資料包結束位置,如果找到了,返回完整資料包起始位置,如果未找到,繼續接收資料,並再次查詢資料包結束位置,直到找到完整資料包,或緩衝區滿。

這樣可以保證,緩衝區開始位置就是資料包開始位置,如果緩衝區內找到資料包結束位置,就返回一個完整資料包,剩下的緩衝區資料繼續查詢,刪除掉垃圾資料,繼續保證緩衝開始位置為資料包起始位置,如此迴圈;如果沒有找到結束位置,就繼續接收,直到找到完整資料包。

這樣就可以每次解析一個完整的資料包了,程式碼如下。

///< 處理客戶端接收的資料
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, "ProvDataTcpClientRecv 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, "ProvDataTcpClientRecv RnssGb_Decode_MsgHeader error. len:%d xml:%s",
                nPocketEndPos, m_szAnalysisBuf);
        }
        else
        {
            // 判斷uuid是否一致
            if (strncmp(m_strUuid.c_str(), stMsgHeader.szMsgUUID, sizeof(m_strUuid.c_str())) != 0)
            {
                g_logHostCfg.Log(JN_WARN, "TcpClientRecvData recv uuid mismatching. id:%d Uuid:%s retUudi:%s", m_nTcpClientID, m_strUuid, stMsgHeader.szMsgUUID);
                return -2;
            }
            // 比對正在等待的資訊命令型別,一致,則發出通知
            if (memcmp(stMsgHeader.szCmdType, m_szRecvCmdType, sizeof(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;
}