当前位置:学者斋 >

计算机 >网络技术 >

包与流之间的转换方法

包与流之间的转换方法

引导语:网络传输中,数据包与数据流的相互转换都有哪些方法呢?以下是小编整理的包与流之间的转换方法,欢迎参考阅读!

包与流之间的转换方法

办法一:特殊切割符来分割包

这种办法粗暴简单,我们使用一个特殊字符来作为包与包之间的分隔符,不过这个分隔符要特殊,特殊到几乎不出现在包的内容当中,否则会影响接收方切割包的过程。

作为发送方,我们可以用如下代码(示意用):

#define kSeparatorChar @"¤"

+ (NSString*)encodeTextPayload:(NSString*)payload {

NSString* str = [NSString stringWithFormat:@"%@%@", kSeparatorChar, payload];

return str;

}

¤ 就是一个非常特殊的字符,一般应用层的文本都不会涉及到,所以可以用作我们的特殊分隔符。接收端只需要以 ¤ 为分隔符,再把数据做一次切割即可:

+ (NSString*)decodeTextPayloadString:(NSString*)str {

NSString* payload;

NSArray* arr = [str componentsSeparatedByString:kSeparatorChar];

if (t < 2) {

return nil;

}

payload = arr[1];

return payload;

}

这种做法的缺陷也是显而易见的,必须严格要求包体中不会出现该特殊字符,所以这种办法只能应用于非常特殊的场景。

办法二:每个包都是固定长度

这种办法也是粗暴简单,甚至不需要分隔符,每次接收方从 stream 中取出固定长度的字节,还原成一个包,代码也比较简单,在 receive() 回调里,每次检查是否达到了固定的长度,是则取出固定长度还原,否则继续等待,代码就不演示啦。

这种做法的缺陷就更大了,会造成包体的浪费,无法适应不同大小的包。

办法三:自定义协议,支持可变长度的包

之前一篇介绍自定义通讯协议的文章里,简单的提到过如何设计一个可用的协议,这里我们具体看下代码。

当我们需要描述可变长度的包时,需要定义一个 header 来详细描述包相关的信息,比如最简单的,记录包的长度。如何记录包的大小呢?我们可以用位操作的特性,来将应用层的 int 值放入到包的 header 中,代码如下(代码摘自以前的.项目,稍有改动):

- (NSData*)encodeData:(NSData*)data withHeader:(NSString*)header {

int dataSize = (int)th;

char buffer[4];

buffer[0] = dataSize >> 24;

buffer[1] = (dataSize << 8) >> 24;

buffer[2] = (dataSize << 16) >> 24;

buffer[3] = (dataSize << 24) >> 24;

NSMutableData* packet = [NSMutableData new];

[packet appendBytes:[header UTF8String] length:2];

[packet appendBytes:buffer length:4];

[packet appendData:data];

return packet;

}

这是一个通用的技巧,当我们需要在 stream 中记录可变长度的数据时,都可以用这种位操作来做转换,只需要 2 个字节的长度,即可记录长达 64 KB 的数据长度,4 个字节则能记录长达 4 GB 的长度。

接收方在收到 NSData 之后,可以先读取 4 个字节的长度信息,还原成 int 值,再读取 int 值所记录的字节数,这些字节就是我们的包了,代码如下:

- (TDecodedData*)decodeData:(NSData*)data {

TDecodedData* d = [TDecodedData new];

if (th < 6) { //minimal packet length

return nil;

}

if ([headerStr isEqualToString:kPacketStreamHeader] == true) {

int realSize = 0;

unsigned char buffer[4];

[data getBytes:buffer range:NSMakeRange(2, 4)];

realSize += buffer[0] << 24;

realSize += buffer[1] << 16;

realSize += buffer[2] << 8;

realSize += buffer[3] << 0;

if (th - 6 < realSize) {

return nil;

}

er = kPacketStreamHeader;

NSData* payloadBytes = [data subdataWithRange:NSMakeRange(6, realSize)];

if (th > 0) {

dedData = payloadBytes;

}

//remove from data

int handledLength = 6 + realSize;

NSData* nd = [NSData dataWithBytes:s + handledLength length:th-handledLength];

ledData = nd;

}

return d;

}

上面的代码主要是向大家展示,如何以添加 header 的方式,来记录可变长度的包体信息。如此,发送方所发送的 NSData 就和接收方所接受的 NSData 一一对应起来了,就就不存在所谓的粘包和拆包问题了。

我们之所以可以对一个 stream 做切分,是因为 TCP 已经做了可靠传输的保证,接收方收到的 stream 和发送方发送的 stream 严格一致,一个字节都不会差,所以我们只需要先读取长度值,再按长度值读取后续的数据,就能把一个 stream 分割成一个个的 NSData,这些分割好的 NSData 就是发送方所发送的包了。

接收方将 stream 分割成 NSData 之后,需要进一步将 data 反序列化成应用层的包,这里就必须提到 google 开源的 protobuf 了,序列化和反序列化神器,造福了无数的框架和应用,甚至有 Objective C 的版本。

  • 文章版权属于文章作者所有,转载请注明 https://xuezhezhai.com/jsj/wl/go9z62.html