C++實現(xiàn)TCP客戶端及服務器Recv數(shù)據(jù)篩選處理詳解
正文
對于一個簡單的tcp通訊這里我就不再講述了,今天主要為大家講解下,如何從::recv中篩選出一個完整包邏輯。
就簡單的以客戶端為例(服務器接收方也是同樣的邏輯),假設服務器一直在頻繁發(fā)送數(shù)據(jù),在recv函數(shù)中并不能保證每次接收的都是一個完整的包,當設置recv的緩沖區(qū)過大時,就會出現(xiàn)多個包同時接收的問題。
對于這種情況,初出茅廬的我們有時會想不到居然還有多個包共同出現(xiàn)的問題,甚至有些還沒學會如何高效的分離出一個有效的數(shù)據(jù)包。
一般在進行tcp通訊協(xié)議時,為了各個指令的區(qū)分,通常都會用以下方式進行發(fā)送,如下圖:

具體的通信協(xié)議規(guī)格可以按照各個業(yè)務需求來定義,這里只是列舉了一個簡單的例子。
在這篇文章中根據(jù)上圖中的協(xié)議格式,進行舉例講解。
| 協(xié)議標識 | 測試具體 |
|---|---|
| 系統(tǒng)標識 | 0x5A |
| 源設備標識 | 0xFCFB |
| 預留字節(jié) | 假設占10個字節(jié) |
| 源設備編碼 | 根據(jù)連接的服務端設備編碼,假設這是是0x01(編號1) |
| 命令字 | 具體的業(yè)務指令,兩個字節(jié) |
| 數(shù)據(jù)長度 | 需要發(fā)送的數(shù)據(jù)長度 |
| 數(shù)據(jù)內容 | 具體業(yè)務具體內容 |
| 校驗碼 | 0xEF |
在進行tcp通訊時,我們每條指令都是按照這種方式進行發(fā)送的,接下來,到了文章的重點部分了,該如何對接收的數(shù)據(jù)篩選?

根據(jù)流程圖可以發(fā)現(xiàn),當數(shù)據(jù)不匹配時,需要重新進行篩選,程序采用了遞歸的方式進行數(shù)據(jù)剔除。這里只是展示了內部的流程處理,外部調用該如何呢?
int nTotalSize = 0;
while(this->JudgeValidData(vetRecvData, nTotalSize) == ture)
{
//接收到了完整包,對包進行處理
}
代碼解析
nTotalSize:代表了一個完整數(shù)據(jù)包的總長度。
vetRecvData:recv函數(shù)接收的所有緩沖區(qū)內容。
其實上面流程圖的內容就是JudgeValidData()的函數(shù)處理邏輯。
對于該函數(shù)的詳細解析:
1:當Tcp緩沖區(qū)的數(shù)據(jù)小于3個字節(jié)時,不進行判斷
此時字節(jié)數(shù)據(jù)過小,無法判斷是不是當前程序中需要的
2:判斷包頭是不是一致?
上述表格中,舉例說明了包頭的定義,分別是:0x5A、0xFC、0xFB
那么,我們在判斷數(shù)據(jù)時首先要判斷包頭是否正確?包頭不正確后續(xù)也就不需要判斷了!
char ch0 = vetRecvData[0]; //系統(tǒng)標識
char ch1 = vetRecvData[1]; //標識1
char ch2 = vetRecvData[2]; //標識2
if((ch0 == 0x5A) && (ch1 == 0xFC) && (ch2 == 0xFB))
{
//包頭正確,進行后續(xù)處理
}
else
{
//包頭不正確,剔除數(shù)據(jù),默認剔除緩沖區(qū)的第一個字節(jié)數(shù)據(jù)
std::vector<char>::iterator itbegin = vetRecvData.begin();
vetRecvData.erase(itbegin);
//重新調用當前函數(shù),遞歸判斷
return JudgeValidData(vetRecvData, nTotalSize);
}
3:包頭匹配后,判斷是否達到了數(shù)據(jù)包的固定協(xié)議長度?
在這里說的固定協(xié)議長度,就是數(shù)據(jù)內容之前的字節(jié),在這篇文章中,數(shù)據(jù)內容之前有19個字節(jié),所以,當vetRecvData的數(shù)據(jù)總長度 < 19個字節(jié)時,不需要處理。
當達到固定協(xié)議長度后,說明該緩沖區(qū)中存在了有效數(shù)據(jù),那么就需要判斷實際的數(shù)據(jù)內容是否達到了整包的標準?
4:判斷有效數(shù)據(jù)是否接收完整?
首先,需要獲取當前包需要接收的數(shù)據(jù)長度。在這里是占用三個字節(jié),分別是:16、17、18這三位
int ndatalen = (unsigned char)pThreadParam->vetRecvData[16] * 256 *256+ (unsigned char)pThreadParam->vetRecvData[17]*256 + (unsigned char)pThreadParam->vetRecvData[18]; nTotalSize = 19 + ndatalen + 1;
如果當前vetRecvData緩沖區(qū)的數(shù)據(jù) < nTotalSize,說明數(shù)據(jù)不完整,不判斷;反之,緩沖區(qū)中存在完整的數(shù)據(jù)。
5:數(shù)據(jù)校驗位判斷
最后,判斷下緩沖區(qū)完整包的最后一位是否是校驗位?
這樣,才算是一個完整的一條數(shù)據(jù)包!
到這里,就算判斷完成了,由此可以從一個大的緩沖區(qū)中抽取出有用的一組數(shù)據(jù)。
內部的判斷邏輯講解完之后,接下來就是外部數(shù)據(jù)使用了。繼續(xù)上面的這段代碼繼續(xù)講解~
int nTotalSize = 0;
while(this->JudgeValidData(vetRecvData, nTotalSize) == ture)
{
//接收到了完整包,對包進行處理
}
接收到一個完整包之后,數(shù)據(jù)處理過程
1:根據(jù)nTotalSize大小從緩沖區(qū)中獲取有效內容
2:截取出剩余的緩沖區(qū)內容,重新賦值
3:對一個完整包數(shù)據(jù)進行業(yè)務處理
將流程轉換成代碼,如下:
int nTotalSize = 0;
while (this->JudgeValidData(vetRecvData, nTotalSize) == true)
{
//這一條數(shù)據(jù)被處理之后,使用一個臨時容器存儲 處理的 通訊數(shù)據(jù)
std::vector<char> vetValidData;
char* chUseData = new char[nTotalSize];
memset(chUseData, 0, nTotalSize);
for (int i = 0; i < vetRecvData.size(); i++)
{
if (i < nTotalSize)
{
chUseData[i] = vetRecvData[i];
}
else
vetValidData.push_back(vetRecvData[i]);
}
//刪除 存儲所有接收字節(jié)的容器 中的數(shù)據(jù)
vetRecvData.clear();
vetRecvData = vetValidData; //重新更新數(shù)據(jù)信息
//接收的是整包數(shù)據(jù)時,進行實際的數(shù)據(jù)處理
ProcessingPancelValidData(chUseData, nTotalSize);
delete[] chUseData;
chUseData = nullptr;
}以上就是C++實現(xiàn)TCP客戶端及服務器Recv數(shù)據(jù)篩選處理詳解的詳細內容,更多關于C++ 客戶端 服務器數(shù)據(jù)篩選的資料請關注腳本之家其它相關文章!
相關文章
詳解如何在code block創(chuàng)建一個C語言的項目
這篇文章主要介紹了詳解如何在code block創(chuàng)建一個C語言的項目,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-12-12
C語言數(shù)據(jù)結構之判斷循環(huán)鏈表空與滿
這篇文章主要介紹了C語言數(shù)據(jù)結構之判斷循環(huán)鏈表空與滿的相關資料,希望通過本文能幫助到大家,讓大家掌握這部分內容,需要的朋友可以參考下2017-10-10

