iOS 处理socket粘包问题

1.什么是粘包?

  粘包一般出如今TCP的协议里面,对于UDP来讲是不会出现粘包情况的,之因此出现这种情况的缘由,涉及到一种名为Nagle的算法。git

  Nagle算法经过减小必须发送的封包的数量,提升网络应用程序系统的效率,解决负载问题。通俗的讲,就是在发包的时候会创建一个缓存区,发送的数据都会先进入这个缓存区,当上一条数据的接收被确认或者到达最大等待时间以后,才会将缓存区的数据一块发送过去,如此反复。将小包进行整合,避免小包屡次发送形成的传输速度慢等问题。github

2.何时才须要处理粘包?

  理论上来说,只要是基于TCP的socket连接,都须要处理粘包的状况。算法

  可能有些人在测试的时候并无出现粘包的状况,认为并不须要对粘包进行处理,这种想法是错误的。json

  首先,在测试的时候之因此没有出现粘包状况,极有多是由于网络路由问题,致使TCP的MTU会有所变化,Internet上的标准MTU值为576,以太网MTU为1500。因此测试粘包通常之外网环境进行测试。缓存

3.粘包解决方案

  因为Nagle算法已经成为了默认的执行方式,因此对于粘包,在Server端和Client端都须要解决粘包的问题,因为问题一致,因此解决方案也基本是通用的,只是相关语法须要变换一下。服务器

  因为iOS的底层对于socket的封装并非那么完善,使用的话还得采用C语言,所以使用了一个名为CocoaAsyncSocket的第三方,将C的socket封装成了适合OC面向对象的形式,github地址以下:https://github.com/robbiehanson/CocoaAsyncSocket网络

  导入成功后,正式开始处理粘包问题。app

  首先,跟服务器肯定数据头的问题,对于粘包,通常有两种解决方案,第一种就是服务器返回的字段中有可识别的头和尾,咱们能够根据可识别的头和尾来拆包。第二种是服务器返回的数据只包含头,头里面有数据的长度,咱们能够根据这个头包含的数据长度来进行拆包。本文采用的即是第二种方案。socket

  服务器返回的数据为data形式的数据,打印出来的数据以下:测试

<fefd00f9 7b22736e 223a342c 22766572 73696f6e 223a2231 2e30222c 226e6574 466c6167 223a312c 22636d64 54797065 223a312c 22706475 223a7b22 70647554 79706522 3a343039 382c2264 65764461 7461223a 5b7b2264 65764964 223a2231 32333435 36373822 2c226465 764e616d 65223a22 e59b9ee8 b7afe68e a7e588b6 222c2270 4964223a 22343039 3833222c 22706172 616d223a 5b7b2274 79706522 3a383232 372c2276 616c7565 223a317d 2c7b2274 79706522 3a383232 382c2276 616c7565 223a307d 2c7b2274 79706522 3a383232 392c2276 616c7565 223a317d 2c7b2274 79706522 3a383233 302c2276 616c7565 223a317d 5d7d5d7d 7d>

  其中fefd00f9为服务器返回的头,后面为具体的数据。其中fefd占两个字节,00f9占两个字节,fefd是固定的值不作任何处理,00f9就是数据的长度

  接下来,在连接成功以后,在回调方法里面建立一个缓存区

- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port
{
    self.readBuf = [[NSMutableData alloc] init];
  
NSLog(@"连接成功后的其余操做");

}

  而后在收到服务器发送的数据的时候,对这个数据进行处理

//服务器发送的数据
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
    //将数据存入缓存区
    [self.readBuf appendData:data];
    
    //数据中前面有4个字节的头信息,其中前两位是固定的头长度(用处不大),后两位才是数据的长度。
    //若是大于4个字节证实有消息,由于服务器只要发送数据,一定包含头
    while (self.readBuf.length > 4) {
        
        //将消息转化成byte,计算总长度 = 数据的内容长度 + 前面4个字节的头长度
        Byte *bytes = (Byte *)[self.readBuf bytes];
        NSUInteger allLength = (bytes[2]<<8) + bytes[3] +4;
        
        //缓存区的长度大于总长度,证实有完整的数据包在缓存区,而后进行处理
        if (self.readBuf.length >= allLength) {
            NSMutableData *msgData = [[self.readBuf subdataWithRange:NSMakeRange(0, allLength)] mutableCopy];
            //提取出前面4个字节的头内容,之因此提取出来,是由于在处理数据问题的时候,好比data转json的时候,头内容里面包含非法字符,会致使转化出来的json内容为空,因此要先去掉再处理数据问题
            [msgData replaceBytesInRange:NSMakeRange(0, 4) withBytes:NULL length:0];
    
            NSLog(@"开始处理数据问题");

            //处理完数据后将处理过的数据移出缓存区
            _readBuf = [NSMutableData dataWithData:[_readBuf subdataWithRange:NSMakeRange(allLength, _readBuf.length - allLength)]];
        }else{
            //缓存区内数据包不是完整的,再次从服务器获取数据,中断while循环
            [self.clientSocket readDataWithTimeout:-1 tag:0];
            break;
        }
    }
    
    //读取到服务端数据值后,能再次读取
    [self.clientSocket readDataWithTimeout:-1 tag:0];
}    

  这样处理以后,能够将粘包问题解决

相关文章
相关标签/搜索