C++實(shí)現(xiàn)TCP客戶端及服務(wù)器Recv數(shù)據(jù)篩選處理詳解
正文
對(duì)于一個(gè)簡(jiǎn)單的tcp通訊這里我就不再講述了,今天主要為大家講解下,如何從::recv
中篩選出一個(gè)完整包邏輯。
就簡(jiǎn)單的以客戶端為例(服務(wù)器接收方也是同樣的邏輯),假設(shè)服務(wù)器一直在頻繁發(fā)送數(shù)據(jù),在recv函數(shù)中并不能保證每次接收的都是一個(gè)完整的包,當(dāng)設(shè)置recv的緩沖區(qū)過(guò)大時(shí),就會(huì)出現(xiàn)多個(gè)包同時(shí)接收的問(wèn)題。
對(duì)于這種情況,初出茅廬的我們有時(shí)會(huì)想不到居然還有多個(gè)包共同出現(xiàn)的問(wèn)題,甚至有些還沒(méi)學(xué)會(huì)如何高效的分離出一個(gè)有效的數(shù)據(jù)包。
一般在進(jìn)行tcp通訊協(xié)議時(shí),為了各個(gè)指令的區(qū)分,通常都會(huì)用以下方式進(jìn)行發(fā)送,如下圖:
具體的通信協(xié)議規(guī)格可以按照各個(gè)業(yè)務(wù)需求來(lái)定義,這里只是列舉了一個(gè)簡(jiǎn)單的例子。
在這篇文章中根據(jù)上圖中的協(xié)議格式,進(jìn)行舉例講解。
協(xié)議標(biāo)識(shí) | 測(cè)試具體 |
---|---|
系統(tǒng)標(biāo)識(shí) | 0x5A |
源設(shè)備標(biāo)識(shí) | 0xFCFB |
預(yù)留字節(jié) | 假設(shè)占10個(gè)字節(jié) |
源設(shè)備編碼 | 根據(jù)連接的服務(wù)端設(shè)備編碼,假設(shè)這是是0x01(編號(hào)1) |
命令字 | 具體的業(yè)務(wù)指令,兩個(gè)字節(jié) |
數(shù)據(jù)長(zhǎng)度 | 需要發(fā)送的數(shù)據(jù)長(zhǎng)度 |
數(shù)據(jù)內(nèi)容 | 具體業(yè)務(wù)具體內(nèi)容 |
校驗(yàn)碼 | 0xEF |
在進(jìn)行tcp通訊時(shí),我們每條指令都是按照這種方式進(jìn)行發(fā)送的,接下來(lái),到了文章的重點(diǎn)部分了,該如何對(duì)接收的數(shù)據(jù)篩選?
根據(jù)流程圖可以發(fā)現(xiàn),當(dāng)數(shù)據(jù)不匹配時(shí),需要重新進(jìn)行篩選,程序采用了遞歸的方式進(jìn)行數(shù)據(jù)剔除。這里只是展示了內(nèi)部的流程處理,外部調(diào)用該如何呢?
int nTotalSize = 0; while(this->JudgeValidData(vetRecvData, nTotalSize) == ture) { //接收到了完整包,對(duì)包進(jìn)行處理 }
代碼解析
nTotalSize:代表了一個(gè)完整數(shù)據(jù)包的總長(zhǎng)度。
vetRecvData:recv函數(shù)接收的所有緩沖區(qū)內(nèi)容。
其實(shí)上面流程圖的內(nèi)容就是JudgeValidData()
的函數(shù)處理邏輯。
對(duì)于該函數(shù)的詳細(xì)解析:
1:當(dāng)Tcp緩沖區(qū)的數(shù)據(jù)小于3個(gè)字節(jié)時(shí),不進(jìn)行判斷
此時(shí)字節(jié)數(shù)據(jù)過(guò)小,無(wú)法判斷是不是當(dāng)前程序中需要的
2:判斷包頭是不是一致?
上述表格中,舉例說(shuō)明了包頭的定義,分別是:0x5A、0xFC、0xFB
那么,我們?cè)谂袛鄶?shù)據(jù)時(shí)首先要判斷包頭是否正確?包頭不正確后續(xù)也就不需要判斷了!
char ch0 = vetRecvData[0]; //系統(tǒng)標(biāo)識(shí) char ch1 = vetRecvData[1]; //標(biāo)識(shí)1 char ch2 = vetRecvData[2]; //標(biāo)識(shí)2 if((ch0 == 0x5A) && (ch1 == 0xFC) && (ch2 == 0xFB)) { //包頭正確,進(jìn)行后續(xù)處理 } else { //包頭不正確,剔除數(shù)據(jù),默認(rèn)剔除緩沖區(qū)的第一個(gè)字節(jié)數(shù)據(jù) std::vector<char>::iterator itbegin = vetRecvData.begin(); vetRecvData.erase(itbegin); //重新調(diào)用當(dāng)前函數(shù),遞歸判斷 return JudgeValidData(vetRecvData, nTotalSize); }
3:包頭匹配后,判斷是否達(dá)到了數(shù)據(jù)包的固定協(xié)議長(zhǎng)度?
在這里說(shuō)的固定協(xié)議長(zhǎng)度,就是數(shù)據(jù)內(nèi)容之前的字節(jié),在這篇文章中,數(shù)據(jù)內(nèi)容之前有19個(gè)字節(jié),所以,當(dāng)vetRecvData的數(shù)據(jù)總長(zhǎng)度 < 19個(gè)字節(jié)時(shí),不需要處理。
當(dāng)達(dá)到固定協(xié)議長(zhǎng)度后,說(shuō)明該緩沖區(qū)中存在了有效數(shù)據(jù),那么就需要判斷實(shí)際的數(shù)據(jù)內(nèi)容是否達(dá)到了整包的標(biāo)準(zhǔn)?
4:判斷有效數(shù)據(jù)是否接收完整?
首先,需要獲取當(dāng)前包需要接收的數(shù)據(jù)長(zhǎng)度。在這里是占用三個(gè)字節(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;
如果當(dāng)前vetRecvData緩沖區(qū)的數(shù)據(jù) < nTotalSize,說(shuō)明數(shù)據(jù)不完整,不判斷;反之,緩沖區(qū)中存在完整的數(shù)據(jù)。
5:數(shù)據(jù)校驗(yàn)位判斷
最后,判斷下緩沖區(qū)完整包的最后一位是否是校驗(yàn)位?
這樣,才算是一個(gè)完整的一條數(shù)據(jù)包!
到這里,就算判斷完成了,由此可以從一個(gè)大的緩沖區(qū)中抽取出有用的一組數(shù)據(jù)。
內(nèi)部的判斷邏輯講解完之后,接下來(lái)就是外部數(shù)據(jù)使用了。繼續(xù)上面的這段代碼繼續(xù)講解~
int nTotalSize = 0; while(this->JudgeValidData(vetRecvData, nTotalSize) == ture) { //接收到了完整包,對(duì)包進(jìn)行處理 }
接收到一個(gè)完整包之后,數(shù)據(jù)處理過(guò)程
1:根據(jù)nTotalSize大小從緩沖區(qū)中獲取有效內(nèi)容
2:截取出剩余的緩沖區(qū)內(nèi)容,重新賦值
3:對(duì)一個(gè)完整包數(shù)據(jù)進(jìn)行業(yè)務(wù)處理
將流程轉(zhuǎn)換成代碼,如下:
int nTotalSize = 0; while (this->JudgeValidData(vetRecvData, nTotalSize) == true) { //這一條數(shù)據(jù)被處理之后,使用一個(gè)臨時(shí)容器存儲(chǔ) 處理的 通訊數(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]); } //刪除 存儲(chǔ)所有接收字節(jié)的容器 中的數(shù)據(jù) vetRecvData.clear(); vetRecvData = vetValidData; //重新更新數(shù)據(jù)信息 //接收的是整包數(shù)據(jù)時(shí),進(jìn)行實(shí)際的數(shù)據(jù)處理 ProcessingPancelValidData(chUseData, nTotalSize); delete[] chUseData; chUseData = nullptr; }
以上就是C++實(shí)現(xiàn)TCP客戶端及服務(wù)器Recv數(shù)據(jù)篩選處理詳解的詳細(xì)內(nèi)容,更多關(guān)于C++ 客戶端 服務(wù)器數(shù)據(jù)篩選的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
c語(yǔ)言根據(jù)用戶輸入的出生年份并計(jì)算出當(dāng)前年齡
這篇文章主要介紹了c語(yǔ)言根據(jù)用戶輸入的出生年份并計(jì)算出當(dāng)前年齡,需要的朋友可以參考下2023-03-03C++數(shù)據(jù)序列化方式(自定義結(jié)構(gòu)體的保存和讀取)
這篇文章主要介紹了C++數(shù)據(jù)序列化方式(自定義結(jié)構(gòu)體的保存和讀取),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08C語(yǔ)言中大小寫(xiě)字母相互轉(zhuǎn)化的方法示例
在C語(yǔ)言中,大小寫(xiě)字母的轉(zhuǎn)換可以通過(guò)標(biāo)準(zhǔn)庫(kù)中的ctype.h頭文件提供的函數(shù)來(lái)實(shí)現(xiàn),具體來(lái)說(shuō),toupper()函數(shù)可以將小寫(xiě)字母轉(zhuǎn)換為大寫(xiě)字母,而tolower()函數(shù)可以將大寫(xiě)字母轉(zhuǎn)換為小寫(xiě)字母,本文給大家介紹了C語(yǔ)言中大小寫(xiě)字母相互轉(zhuǎn)化的方法,需要的朋友可以參考下2024-08-08詳解C語(yǔ)言中的getgrgid()函數(shù)和getgrnam()函數(shù)
這篇文章主要介紹了詳解C語(yǔ)言中的getgrgid()函數(shù)和getgrnam()函數(shù),是C語(yǔ)言入門(mén)學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-08-08Qt自定義表頭實(shí)現(xiàn)過(guò)濾功能的方法
這篇文章主要個(gè)給大家介紹了關(guān)于Qt自定義表頭實(shí)現(xiàn)過(guò)濾功能的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Qt具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07