在上一篇文章《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