當前位置:才華齋>計算機>網路技術>

包與流之間的轉換方法

網路技術 閱讀(2.27W)

引導語:網路傳輸中,資料包與資料流的相互轉換都有哪些方法呢?以下是小編整理的包與流之間的轉換方法,歡迎參考閱讀!

包與流之間的轉換方法

辦法一:特殊切割符來分割包

這種辦法粗暴簡單,我們使用一個特殊字元來作為包與包之間的分隔符,不過這個分隔符要特殊,特殊到幾乎不出現在包的內容當中,否則會影響接收方切割包的過程。

作為傳送方,我們可以用如下程式碼(示意用):

#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 的版本。