TCP網路傳輸“粘包”問題,經典解決(附程式碼)
一、前言
關於TCP網路傳輸粘包,網上很多人寫了原理。總結起來就一句話(這裡拿Server和Client長連線,Server和Client之間通過信令傳輸做說明)
Server傳送的時候,雖然按照一條條信令傳送,經過網路傳輸,到達客戶端作業系統網路層,首先進入緩衝池,然後TCP協議從池子中獲取資料,傳輸給Client App。但是,我們知道TCP的傳輸有多個方案,比如,滑動視窗、1位元方案。所以應用層 Client App收到的資料包,基本不可能是一個完整的Server傳送的信令包了。
個人理解TCP粘包的概念,即它描述了一個場景:“信令是一個個緊挨著的,好像是被粘在一起了,不知道從哪兒將信令拆分”。在把信令拆開之前我們要做一個必須的任務:規整資料。規整了socket資料,那麼原始信令就一條條規整出來了。
二、分析
剛才已經講過,Client從Socket收到的資料是不確定的,可能是1Byte,可能1000Byte。而即便相同的信令,內容不一樣長度也是不同的。將信令一個個摘出來,需要一個關鍵的屬性:信令長度。通常信令的前兩個位元組是長度(如下圖中橙色塊),其表明了該條信令的長度。Client需要一個Buffer,用於規整信令。
通常Client接收Socket資料,存在以下幾種場景:
(圖1)
上圖1展示了:信令前兩個位元組(橙色標註)是200B,然而Client的Buffer只是接收到了100B,那麼客戶端什麼也不做,等待後續的socket資料
(圖2)
上圖2展示了:信令前兩個位元組(橙色標註)是50B,然而Client的Buffer已經超過50B了,那麼Client可以擷取50B,當成完整的信令,給後續邏輯處理了。
(圖3)
上圖3跟,圖2相似。
總之,邏輯上只有這三種情況:預期 > 實際 ; 預期 < 實際;預期 = 實際
三、邏輯實現
需要注意的是:在獲取前兩個位元組的時候,需要判斷系統是大端,還是小端
/** * @brief 收到訊息 * * @param data 資料指標 * @param length 資料長度 */ void onReceiveData(NSData * data) { if (data == NULL) { return; } [m_data appendData:data]; const Byte *packageData = (Byte *)[m_data bytes]; NSUInteger packageSize = [m_data length]; // parse packet unsigned short messageSize = 0; NSUInteger pos = 0; while (pos < packageSize) { if (pos + 2 < packageSize) { // can read message packet-size // read message packet-size messageSize = *((unsigned short *)(packageData + pos)); // 現為小頭 //Byte tmp = (messageSize & 0xFF00) >> 8; //messageSize <<= 8; //messageSize |= tmp; if(messageSize <= packageSize - pos) { // there is a complate message if (m_callback) { m_callback->onDeliverMsg((packageData + pos), messageSize); } pos += messageSize; continue; } } break; } // deal with last bytes. if (pos < packageSize) { [m_data replaceBytesInRange:NSMakeRange(0, packageSize - pos) withBytes:(packageData + pos)]; [m_data setLength:packageSize - pos]; } else { [m_data setLength:0]; } }
(完)