Socket粘包处理

作者: 聪莞 | 来源:发表于2019-03-29 15:42 被阅读54次

    什么是粘包

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

    相关文章

      网友评论

        本文标题:Socket粘包处理

        本文链接:https://www.haomeiwen.com/subject/spulbqtx.html