CocoaAsyncSocket + Protobuf 处理粘包和拆包问题_移动开发_编程开发_程序员俱乐部

中国优秀的程序员网站程序员频道CXYCLUB技术地图
热搜:
更多>>
 
您所在的位置: 程序员俱乐部 > 编程开发 > 移动开发 > CocoaAsyncSocket + Protobuf 处理粘包和拆包问题

CocoaAsyncSocket + Protobuf 处理粘包和拆包问题

 2017/4/17 5:32:39  xmTan  程序员俱乐部  我要评论(0)
  • 摘要:在上一篇文章《iOS之ProtocolBuffer搭建和示例demo》分享环境的搭建,我们和服务器进行IM通讯用了github有名的框架CocoaAsynSocket,然后和服务器之间的数据媒介是ProtoBuf。然后后面在开发的过程中也碰到了拆包和粘包问题,这方面网上资料很少,曲折了一下才解决,这里分享一下问题的解决过程!首先描述下碰到的问题:1、服务器发送内容很长的数据过来的时候,GCDAsyncSocket监听收到的一个包解析不了,一直要接收好几个包拼接才是这条数据的完整包,即所谓的拆包
  • 标签:问题 socket

      在上一篇文章《iOS之ProtocolBuffer搭建和示例demo》分享环境的搭建, 我们和服务器进行IM通讯用了github有名的框架CocoaAsynSocket, 然后和服务器之间的数据媒介是ProtoBuf。然后后面在开发的过程中也碰到了拆包和粘包问题,这方面网上资料很少,曲折了一下才解决,这里分享一下问题的解决过程!

  首先描述下碰到的问题:

  1、服务器发送内容很长的数据过来的时候,class="s1">GCDAsyncSocket监听收到的一个包解析不了,一直要接收好几个包拼接才是这条数据的完整包,即所谓的拆包;

  2、服务器快速发送多条数据过来,传到客户端这边的时候几条数据合成了一个包,即所谓的粘包。所以想解析这些粘在一起的数据,必须知道每条数据的长度,才能正确切割解析

 

  先上关键代码解决读取每条数据的头部字节,根据头部字节读取这条数据的内容长度。这样才能完美的解决粘包问题。由于根据数据的长度不一样,导致头部字节占用的长度也会不一样,比如说我这里反复测试的结果头部占用字节一般为1和2,短内容数据头部占用字节长度为1,长内容数据头部占用字节长度为2。这里的代码参考了谷歌提供的Protobuf的objectivec版的源码。

/** 关键代码:获取data数据的内容长度和头部长度: index --> 头部占用长度 (头部占用长度1-4个字节) */
- (int32_t)getContentLength:(NSData *)data withHeadLength:(int32_t *)index{
    
    int8_t tmp = [self readRawByte:data headIndex:index];
    
    if (tmp >= 0) return tmp;
    
    int32_t result = tmp & 0x7f;
    if ((tmp = [self readRawByte:data headIndex:index]) >= 0) {
        result |= tmp << 7;
    } else {
        result |= (tmp & 0x7f) << 7;
        if ((tmp = [self readRawByte:data headIndex:index]) >= 0) {
            result |= tmp << 14;
        } else {
            result |= (tmp & 0x7f) << 14;
            if ((tmp = [self readRawByte:data headIndex:index]) >= 0) {
                result |= tmp << 21;
            } else {
                result |= (tmp & 0x7f) << 21;
                result |= (tmp = [self readRawByte:data headIndex:index]) << 28;
                if (tmp < 0) {
                    for (int i = 0; i < 5; i++) {
                        if ([self readRawByte:data headIndex:index] >= 0) {
                            return result;
                        }
                    }
                    
                    result = -1;
                }
            }
        }
    }
    return result;
}

/** 读取字节 */
- (int8_t)readRawByte:(NSData *)data headIndex:(int32_t *)index{
    
    if (*index >= data.length) return -1;
    
    *index = *index + 1;
    
    return ((int8_t *)data.bytes)[*index - 1];
}

 

解决了读取每条数据的头部占用字节,和内容的长度,粘包问题就好解决了。

再上比较完整的代码:从客户端监听服务器发送过来的数据到处理拆包和粘包问题,然后解析成自定义的protobuf模型类。

/** 监听来自服务器的消息代理方法 */
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
    [self.receiveData appendData:data];
    
    //读取data的头部占用字节 和 从头部读取内容长度
    //验证结果:数据比较小时头部占用字节为1,数据比较大时头部占用字节为2
    int32_t headL = 0;
    int32_t contentL = [self getContentLength:self.receiveData withHeadLength:&headL];
    
    if (contentL < 1){
        [sock readDataWithTimeout:-1 tag:0];
        return;
    }
    
    //拆包情况下:继续接收下一条消息,直至接收完这条消息所有的拆包,再解析
    if (headL + contentL > self.receiveData.length){
        [sock readDataWithTimeout:-1 tag:0];
        return;
    }
    
    //当receiveData长度不小于第一条消息内容长度时,开始解析receiveData
    [self parseContentDataWithHeadLength:headL withContentLength:contentL];
    [sock readDataWithTimeout:-1 tag:tag];
} 

#pragma mark - private methods  辅助方法
/** 解析二进制数据:NSData --> 自定义模型对象 */
- (void)parseContentDataWithHeadLength:(int32_t)headL withContentLength:(int32_t)contentL{
    
    NSRange range = NSMakeRange(0, headL + contentL);   //本次解析data的范围
    NSData *data = [self.receiveData subdataWithRange:range]; //本次解析的data
    
    GPBCodedInputStream *inputStream = [GPBCodedInputStream streamWithData:data];
    
    NSError *error;
    ChatMsg *obj = [ChatMsg parseDelimitedFromCodedInputStream:inputStream extensionRegistry:nil error:&error];
    
    if (!error){
        if (obj) [self saveReceiveInfo:obj];  //保存解析正确的模型对象
        [self.receiveData replaceBytesInRange:range withBytes:NULL length:0];  //移除已经解析过的data
    }
    
    if (self.receiveData.length < 1) return;
    
    //对于粘包情况下被合并的多条消息,循环递归直至解析完所有消息
    headL = 0;
    contentL = [self getContentLength:self.receiveData withHeadLength:&headL];
    [self parseContentDataWithHeadLength:headL withContentLength:contentL]; //继续解析下一条
}

/** 获取data数据的内容长度和头部长度: index --> 头部占用长度 (头部占用长度1-4个字节) */
- (int32_t)getContentLength:(NSData *)data withHeadLength:(int32_t *)index{
    
    int8_t tmp = [self readRawByte:data headIndex:index];
    
    if (tmp >= 0) return tmp;
    
    int32_t result = tmp & 0x7f;
    if ((tmp = [self readRawByte:data headIndex:index]) >= 0) {
        result |= tmp << 7;
    } else {
        result |= (tmp & 0x7f) << 7;
        if ((tmp = [self readRawByte:data headIndex:index]) >= 0) {
            result |= tmp << 14;
        } else {
            result |= (tmp & 0x7f) << 14;
            if ((tmp = [self readRawByte:data headIndex:index]) >= 0) {
                result |= tmp << 21;
            } else {
                result |= (tmp & 0x7f) << 21;
                result |= (tmp = [self readRawByte:data headIndex:index]) << 28;
                if (tmp < 0) {
                    for (int i = 0; i < 5; i++) {
                        if ([self readRawByte:data headIndex:index] >= 0) {
                            return result;
                        }
                    }
                    
                    result = -1;
                }
            }
        }
    }
    return result;
}

/** 读取字节 */
- (int8_t)readRawByte:(NSData *)data headIndex:(int32_t *)index{
    
    if (*index >= data.length) return -1;
    
    *index = *index + 1;
    
    return ((int8_t *)data.bytes)[*index - 1];
}

/** 处理解析出来的信息 */
- (void)saveReceiveInfo:(ChatMsg *)obj{
    //...
}
logs_code_collapse">View Code

 

 示例demo下载地址:https://github.com/xiaotanit/Tan_ProtocolBuffer

 原文链接:http://www.cnblogs.com/tandaxia/p/6718695.html

 

发表评论
用户名: 匿名