Socket粘包處理
什麼是粘包
TCP有粘包現象,而UDP不會出現粘包。
- TCP(Transport Control Protocol,傳輸控制協議) 是面向連線的,面向流的。TCP的收發兩端都要有成對的Socket,因此,傳送端為了將更多有效的包傳送出去,採用了合併優化演算法(Nagle演算法),將多次、間隔時間短、資料量小的資料合併為一個大的資料塊,進行封包處理。這樣的包對於接收端來說,就沒辦法分辨,所以需要一些特殊的拆包機制。
- UDP(User Datagram Protocol,使用者資料報協議) 是無連線的,面向訊息的提供高效率服務。不會使用合併優化演算法。UDP支援的是一對多的模式,所以接收端的skbuff(套接字緩衝區)採用了鏈式結構來記錄每一個到達的UDP包,在每個UDP包中就有了訊息頭(訊息來源地址,埠等資訊),這樣,對於接收端來說,就容易進行區分處理了。
舉個例子
我們連續傳送三個資料包,大小分別是1k,2k ,4k,這三個資料包,都已經到達了接收端的網路堆疊中,如果使用UDP協議,不管我們使用多大的接收緩衝區去接收資料,我們必須有三次接收動作,才能夠把所有的資料包接收完.而使用TCP協議,我們只要把接收的緩衝區大小設定在7k以上,我們就能夠一次把所有的資料包接收下來,只需要有一次接收動作。
如何處理粘包
-
提前通知接收端要傳送的包的長度
粘包問題的根源在於,接收端不知道傳送端將要傳送的位元組流的長度,所以解決粘包的方法就是圍繞,如何讓傳送端在傳送資料前,把自己將要傳送的位元組流總大小讓接收端知曉,然後接收端來一個死迴圈接收完所有資料。
不建議使用,因為程式的執行速度遠快於網路傳輸速度,所以在傳送一段位元組前,先用send去傳送該位元組流長度,這樣會放大網路延遲帶來的效能損耗
-
加分割識別符號
{資料段01}+識別符號+{資料段02}+識別符號
傳送端和接收端約定好一個識別符號來區分不同的資料包,如果接收到了這麼一個分隔符,就表示一個完整的包接收完畢。
也不建議使用,因為要傳送的資料很多,資料的內容格式也有很多,可能會出現識別符號不唯一的情況
-
自定義包頭(建議使用)
image.png
在開始傳輸資料時,在包頭拼上自定義的一些資訊,比如前4個位元組表示包的長度,5-8個位元組表示傳輸的型別(Type:做一些業務區分),後面為實際的資料包。
-
傳送資料
以傳輸字串 ”hello“ 為例:
//要傳輸的資料 NSData * data = [@"hello" dataUsingEncoding:NSUTF8StringEncoding]; //實際傳輸的資料 NSMutableData * mData = [NSMutableData data]; //計算資料總長度 unsigned int totalLength = 4 + 4 + (int)data.length; //拼前4位 NSData * lengthData = [NSData dataWithBytes:&totalLength length:4]; [mData appendData:lengthData]; //拼5-8位 int type = 1; NSData * typeData = [NSData dataWithBytes:&type length:4]; [mData appendData:typeData]; //拼接最後的data [mData appendData:data]; //傳送mData 。。。
- 接收資料
@property (nonatomic, strong) NSMutableData*dataM;//接收的完整的一個數據包的data @property (nonatomic, assign) unsigned int totalSize;//一個完整的資料包大小 BOOL isNewPackage = self.dataM.length == 0; if (isNewPackage) { //接收一個新的資料包 //獲取總大小 NSData * totalSizeData = [data subdataWithRange:NSMakeRange(0, 4)]; unsigned int totalSize = 0; [totalSizeData getBytes:&totalSize length:4]; self.totalSize = totalSize; NSLog(@"接收總資料的大小 %u",totalSize); //獲取type NSData * typeData = [data subdataWithRange:NSMakeRange(4, 4)]; unsigned int type = 0; [typeData getBytes:&type length:4]; NSLog(@"接收總資料的型別 %u",type); //獲取資料段 NSData * realData = [data subdataWithRange:NSMakeRange(8, data.length - 8)]; [self.dataM appendData:realData]; } else { //不是一個新的資料包直接追加進去 [self.dataM appendData:data]; } //判斷是否接收完成 if (self.dataM.length == self.totalSize - 8) { //已經接收完整 //處理data NSString * string = [[NSString alloc] initWithData:self.dataM encoding:NSUTF8StringEncoding]; NSLog(@"接收到的資料為:%@",string); //dataM重置 self.dataM = [NSMutableData data]; }