在上一篇文章《iOS之ProtocolBuffer搭建和示例demo》分享环境的搭建, 我们和服务器进行IM通讯用了github有名的框架CocoaAsynSocket, 然后和服务器之间的数据媒介是ProtoBuf。然后后面在开发的过程中也碰到了拆包和粘包问题,这方面网上资料很少,曲折了一下才解决,这里分享一下问题的解决过程!
首先描述下碰到的问题:
1、服务器发送内容很长的数据过来的时候,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(inti =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];
}