socket编程[oc](逻辑数据的处理)
Posted WoodBear009
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了socket编程[oc](逻辑数据的处理)相关的知识,希望对你有一定的参考价值。
之前写了一下socket编程中半包、粘包的处理点击打开链接,这篇再写写另一个相关问题,逻辑数据的处理
物理数据包与逻辑数据包:
首先说明的是,socket传输中物理数据包、逻辑数据包的概念是我自己臆想、“定义”的,主要是方便我后面去描述问题.那么什么是我所说的物理数据包与逻辑数据包呢? 举个例子,假如我想传送一段文本“这篇博客写的是socket编程[oc](逻辑数据的处理)”,可能因为某些考虑,我觉得这段文本太长了,于是决定在传输时进行分段发送,先发送“这篇博客写的是socket”,再发送“[oc](逻辑数据的处理)”,于是在传输过程中就产生了两个物理数据包。而文本被拆分后,每个一单独的文本对于接收者来说并没有实际的意义,无法完整描述出语句的意思,必需两段文字都接收到,拼凑到一起,才能真正得到正确表意的完整语句,这个能正确表意的语句就是所谓的逻辑数据包(能产生真正的逻辑意义)。 再举个例子,假如我想传送我的好友列表,“test1、test2、test3、test4、test5、test6”,在传输过程中,我将这个列表分成了三段去传输,于是就产生了三个物理数据包,“test1、test2”、“test3、test4”、“test5、test6”。而这三段物理数据包,我拿到任何一段或两段都不算成功获得了完整的好友列表,无法产生实际的意义,必需全部拿到,最后拼凑起来,得到“test1、test2、test3、test4、test5、test6“,才能对接受者产生实际的价值(完整的好友列表),这个完整的列表就是逻辑数据包。物理数据包:一个实的概念,传输过程中实实在在的每一段完整的数据结构
逻辑数据包:一个虚的概念,可能由一个或多个物理数据包组成,能完整、正确的表述出逻辑意义(其实就是发送方发送的完整原始数据)
如何设计数据结构
上篇文章中,半包、粘包的处理针对的都是物理数据包的拆分与拼接,在数据包中我们加入了len字段用于描述每个物理数据包的边界,以得到每个正确独立的物理数据包。在能正确得到每个物理数据包的前提下,像上一段说的,物理数据包对接收方并不一定具有实际的逻辑意义,下一步我们还需要考虑如何将物理数据包之间建立起联系,最后组成完整的逻辑数据包。
借助的方式还是引入新的字段,比如这样
数据 = 数据头 + 数据体
数据头 = id + version + len +phase
数据体 = 具体数据内容
phase可以定义 s、p、e三种状态,分别表示开始部分、主体部分、结尾部分,用来描述出这段物理数据包在逻辑数据包中所处的位置,借助id建立起每个独立物理数据包之间的逻辑联系(相同id的物理数据包描述的是同一个逻辑,有关联性,上下文关系),借助s、e两个状态,划分出逻辑数据包的边界。
套用到上面的例子,可以得到以下几个物理数据包
你也可以这样定义你的数据结构
数据 = 数据头 + 数据体
数据头 = id + version + len + count + index
数据体 = 具体数据内容
count标示相应的逻辑数据包共由几个物理数据包组成;index还可以标示出当前物理数据包在逻辑数据包中所处位置。
为了减少字段的引入,同时减少传输的数据量,也可以这样定义你的数据结构
数据 = 数据头 + 数据体
数据头 = id + version + len + flag
数据体 = 具体数据内容
flag为n位二进制数,比如前几位代表count,后几位代表index,都是可以的。。。。。。
总之,定义方式各种各样,但思想都是通过引入新字段的方式,建立起物理数据包之间的逻辑联系与逻辑边界。
如何处理
具体到我们的代码中应如何处理,套用上文中第一种数据结构(借助phase字段),写个简单的例子
注:1.仅是demo,描述思想,逻辑并不完备、严谨
2.简化问题,没有考虑逻辑数据包之间交叉情况的发生(这个可以通过发送端进行控制,避免交叉情况发生)(例子中用到了NSMutabelData临时保存物理数据段,如果存在逻辑数据包之间交叉情况,可以考虑借助字典+NSMutabelData临时保存物理数据段,借助id建立起物理数据包之间的逻辑联系)
3.忽略了一些异常情况的处理
具体数据定义细节:
数据 = 数据头 + 数据体
数据头 = id(4B) + version(2B) + len(4B) +phase(1B) 共占用11个字节
数据体 = 具体数据内容
代码:
/*
1._currentLogicData是外部定义的一个NSMutableData,在逻辑数据包未完全接收完整时,用于临时存放子物理数据包
2.下面代码中只判断了phase的e(结束)状态,因为已假设了前提,逻辑数据包之间不存在交叉情况,如果存在交叉情况,就需要对s(启始)状态进行判断处理
*/
- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
while (_readBuf.length >= 11)//因为头部固定11个字节,数据长度至少要大于11个字节,我们才能得到完整的消息描述信息
NSData *head = [_readBuf subdataWithRange:NSMakeRange(0, 11)];//取得头部数据
NSData *lengthData = [head subdataWithRange:NSMakeRange(6, 4)];//取得长度数据
NSInteger length = [[[NSString alloc] initWithData:lengthData encoding:NSUTF8StringEncoding] integerValue];//得出内容长度
NSInteger complateDataLength = length + 11;//算出一个包完整的长度(内容长度+头长度)
if (_readBuf.length >= complateDataLength)//如果缓存中数据够一个整包的长度
NSData *data = [_readBuf subdataWithRange:NSMakeRange(0, complateDataLength)];//截取一个包的长度(处理粘包)
NSData *phase = [head subdataWithRange:NSMakeRange(10, 1)];//取得phase
NSString *phaseStr = [[NSString alloc] initWithData:phase encoding:NSUTF8StringEncoding];
if (![phaseStr isEqualToString:@"e"]) //不是逻辑数据包中最后一个物理数据,说明逻辑数据包暂不完整
[_currentLogicData appendData:data];//暂存该物理数据包
else//收到逻辑数据包中的最后一个物理数据包
[_currentLogicData appendData:data];//暂存该物理数据包
[self handleTcpResponseData:data];//逻辑数据包已完整,处理数据
_currentLogicData = [[NSMutableData alloc] init];//(新建)清空物理数据包临时存储空间
//从缓存中截掉处理完的数据,继续循环
_readBuf = [NSMutableData dataWithData:[_readBuf subdataWithRange:NSMakeRange(complateDataLength, _readBuf.length - complateDataLength)]];
else//如果缓存中的数据长度不够一个包的长度,则包不完整(处理半包,继续读取)
[_socket readDataWithTimeout:-1 buffer:_readBuf bufferOffset:_readBuf.length tag:0];//继续读取数据
return;
//缓存中数据都处理完了,继续读取新数据
[_socket readDataWithTimeout:-1 buffer:_readBuf bufferOffset:_readBuf.length tag:0];//继续读取数据
以上是关于socket编程[oc](逻辑数据的处理)的主要内容,如果未能解决你的问题,请参考以下文章