1. 程式人生 > >socket程式設計[oc](粘包、半包處理)

socket程式設計[oc](粘包、半包處理)

在做socket程式設計時,如果是做tcp連線,那就不可避免的會遇到粘包與半包的問題,粘包就是多組資料被一併接收了,粘在了一起,無法做劃分;半包就是有資料接收不完整,無法處理。要解決粘包、半包的問題,一般在設計資料(訊息)格式時會約定好一個欄位專門用於描述資料包的長度,這樣就使資料有了邊界,依靠這個邊界,就能把每組資料劃分出來,資料不完整時也能獲知資料的缺失。

(當然也可以把資料設計成定長資料,但這樣不夠靈活;或者用\n,\r這類字元作為資料劃分依據,但不直觀、不明確,同時也不靈活)

舉個栗子:

訊息=訊息頭+訊息體。訊息頭用於描述訊息本身的基本資訊,訊息體則為訊息的具體內容


如上圖所示,假如我們的一個訊息是這麼定義的

訊息頭 = msgId(4B)+version(2B)+len(4B),共佔用10位元組

訊息體 =  len中描述的16位元組長

所以這條訊息的長度就是 26位元組

可以看到,要想知道一條完整資料的邊界,關鍵就是訊息頭中的len欄位

假如我們現在接收到的資料是這樣的:


這個情況下即包含了粘包,也出現了半包的情況,三個資料包粘在了一起,最後一個數據包沒有接收完全,出現了半包的情況,看看程式碼如何處理

- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
    while (_readBuf.length >= 10)//因為頭部固定10個位元組,資料長度至少要大於10個位元組,我們才能得到完整的訊息描述資訊
    {
        NSData *head = [_readBuf subdataWithRange:NSMakeRange(0, 10)];//取得頭部資料
        NSData *lengthData = [head subdataWithRange:NSMakeRange(6, 4)];//取得長度資料
        NSInteger length = [[[NSString alloc] initWithData:lengthData encoding:NSUTF8StringEncoding] integerValue];//得出內容長度
        NSInteger complateDataLength = length + 10;//算出一個包完整的長度(內容長度+頭長度)
        if (_readBuf.length >= complateDataLength)//如果快取中資料夠一個整包的長度
        {
            NSData *data = [_readBuf subdataWithRange:NSMakeRange(0, complateDataLength)];//擷取一個包的長度(處理粘包)
            [self handleTcpResponseData:data];//處理包資料
            //從快取中截掉處理完的資料,繼續迴圈
            _readBuf = [NSMutableData dataWithData:[_readBuf subdataWithRange:NSMakeRange(complateDataLength, _readBuf.length - complateDataLength)]];
        }
        else//如果快取中的資料長度不夠一個包的長度,則包不完整(處理半包,繼續讀取)
        {
            [_socket readDataWithTimeout:-1 buffer:_readBuf bufferOffset:_readBuf.length tag:0];//繼續讀取資料
            return;
        }
    }
    //快取中資料都處理完了,繼續讀取新資料
    [_socket readDataWithTimeout:-1 buffer:_readBuf bufferOffset:_readBuf.length tag:0];//繼續讀取資料
}

下一篇    socket程式設計[oc](邏輯資料的處理)   點選開啟連結