1. 程式人生 > >TCP網路傳輸“粘包”問題,經典解決(附程式碼)

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];
    }
}

(完)