網路傳輸粘包解包處理
阿新 • • 發佈:2018-12-09
有時候傳送的資料過長,接收的時候只接收了一部分,會出現錯誤。這裡以客戶端接收服務端訊息為例,講解一種解包的方法,作為備忘(總是忘沒辦法)
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; }