Socket粘包处理

1,444 阅读4分钟

什么是粘包

TCP有粘包现象,而UDP不会出现粘包。

  • TCP(Transport Control Protocol,传输控制协议) 是面向连接的,面向流的。TCP的收发两端都要有成对的Socket,因此,发送端为了将更多有效的包发送出去,采用了合并优化算法(Nagle算法),将多次、间隔时间短、数据量小的数据合并为一个大的数据块,进行封包处理。这样的包对于接收端来说,就没办法分辨,所以需要一些特殊的拆包机制。
  • UDP(User Datagram Protocol,用户数据报协议) 是无连接的,面向消息的提供高效率服务。不会使用合并优化算法。UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。

举个例子

我们连续发送三个数据包,大小分别是1k,2k ,4k,这三个数据包,都已经到达了接收端的网络堆栈中,如果使用UDP协议,不管我们使用多大的接收缓冲区去接收数据,我们必须有三次接收动作,才能够把所有的数据包接收完.而使用TCP协议,我们只要把接收的缓冲区大小设置在7k以上,我们就能够一次把所有的数据包接收下来,只需要有一次接收动作。

如何处理粘包

  1. 提前通知接收端要传送的包的长度 粘包问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据。

不建议使用,因为程序的运行速度远快于网络传输速度,所以在发送一段字节前,先用send去发送该字节流长度,这样会放大网络延迟带来的性能损耗

  1. 加分割标识符 {数据段01}+标识符+{数据段02}+标识符 发送端和接收端约定好一个标识符来区分不同的数据包,如果接收到了这么一个分隔符,就表示一个完整的包接收完毕。

也不建议使用,因为要发送的数据很多,数据的内容格式也有很多,可能会出现标识符不唯一的情况

  1. 自定义包头(建议使用) 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];
    }