如何在 XCode 中从 NSInputStream 中可靠地检索 NSData 对象
How to reliably retrieve NSData objects from NSInputStream in XCode
所以我的应用程序是按照这些思路工作的:
- iPod 不断发送 NSDictionaries,其中包含:以 JPEG 编码的图像和一些图像属性作为 NSStrings。
- NSDictionary 使用 NSPropertyListSerialization 编码,格式为 BinaryFormat_v1_0,并通过 NSStream 以 1024 字节的数据包形式发送到中央计算机 运行 OSX 上的一个应用程序。
- OSX 应用程序接收数据包,不断附加到单个 NSMutableData 对象,直到它看到下一个 NSData 对象的第一个数据包(我发现它以二进制格式开始为 'bplist').
- 通过调用 NSPropertyListSerialization,NSData 被转换回 NSDictionary 供 OSX 应用程序使用。
- 一旦 NSData 成功转换(或未成功),NSData 对象将设置回零以开始读取下一轮数据包。
更多注意事项:NSInputStream 和 NSOutput 流都在各自设备的 NSDefaultRunLoopMode 中的 currentRunLoop 上 运行。
当 运行 这个过程时,有时转换回 NSDictionary 工作正常,没有错误(大约 1/3 的尝试),但其他时候转换 returns 这个错误:
Error: Failed to convert NSData to NSDict : Error Domain=NSCocoaErrorDomain Code=3840 "Unexpected character b at line 1" UserInfo={NSDebugDescription=Unexpected character b at line 1, kCFPropertyListOldStyleParsingError=Error Domain=NSCocoaErrorDomain Code=3840 "Conversion of string failed." UserInfo={NSDebugDescription=Conversion of string failed.}}
以下是从流中解析数据的程序部分:
...处理流事件的方法:
-(void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
switch(eventCode) {
case NSStreamEventHasBytesAvailable: {
uint8_t buf[1024];
unsigned int len = (unsigned)[(NSInputStream *)aStream read:buf maxLength:1024];
if(len) {
[self handleEventBuffer:buf WithLength:len];
}
...
...以及处理数据的方法:
-(void)handleEventBuffer:(uint8_t*)buf WithLength:(unsigned int)len {
...
NSString *bufStr = [NSString stringWithFormat:@"%s",(const char*)buf];
if ([bufStr containsString:@"bplist00"] && [self.cameraData length] > 0) {
// Detected new file, enter in all the old data and reset for new data
NSError *error;
NSDictionary *tempDict = [[NSDictionary alloc] init];
tempDict = [NSPropertyListSerialization propertyListWithData:self.cameraData
options:0
format:NULL
error:&error];
if (error != nil) {
// Expected good file but no good file, erase and restart
NSLog(@"Error: Failed to convert NSData to NSDict : %@", [error description]);
[self.cameraData setLength:0];
}
...
[self.cameraData setLength:0];
[self.cameraData appendBytes:buf length:len];
} else {
// Still recieving data
[self.cameraData appendBytes:buf length:len];
}
所以,我要问的问题是:
- 如何修复我的解析方法以提供不会随机无法转换的可靠结果?
- 或者是否有比这更好的方法来为此目的解析缓冲区流?
- 或者我只是在做一些愚蠢的事情还是遗漏了一些明显的事情?
您似乎依赖于对流的每次写入都会产生相同大小的匹配读取,您知道 NSStream
可以保证这一点吗?如果不是,那么任何读取都可能包含两个(或更多)编码词典的一部分,并且您会得到您看到的解析错误。
替代方法:
对于每个要发送的编码词典:
写入结束:
- 发送一条消息,其中包含随后的编码字典的字节大小。
- 分块写编码字典,最后一个块可能很短
- 重复
阅读结束:
- 读取指定其确切字节长度的大小消息。
- 分块读取编码字典,确保只读取 (1) 报告的字节数。
- 重复。
如果您使用的是可靠的通信流,这应该使您能够可靠地阅读每个编码字典。它避免了您试图弄清楚每个编码字典之间的边界在哪里,因为该信息是您协议的一部分。
HTH
所以我的应用程序是按照这些思路工作的:
- iPod 不断发送 NSDictionaries,其中包含:以 JPEG 编码的图像和一些图像属性作为 NSStrings。
- NSDictionary 使用 NSPropertyListSerialization 编码,格式为 BinaryFormat_v1_0,并通过 NSStream 以 1024 字节的数据包形式发送到中央计算机 运行 OSX 上的一个应用程序。
- OSX 应用程序接收数据包,不断附加到单个 NSMutableData 对象,直到它看到下一个 NSData 对象的第一个数据包(我发现它以二进制格式开始为 'bplist').
- 通过调用 NSPropertyListSerialization,NSData 被转换回 NSDictionary 供 OSX 应用程序使用。
- 一旦 NSData 成功转换(或未成功),NSData 对象将设置回零以开始读取下一轮数据包。
更多注意事项:NSInputStream 和 NSOutput 流都在各自设备的 NSDefaultRunLoopMode 中的 currentRunLoop 上 运行。
当 运行 这个过程时,有时转换回 NSDictionary 工作正常,没有错误(大约 1/3 的尝试),但其他时候转换 returns 这个错误:
Error: Failed to convert NSData to NSDict : Error Domain=NSCocoaErrorDomain Code=3840 "Unexpected character b at line 1" UserInfo={NSDebugDescription=Unexpected character b at line 1, kCFPropertyListOldStyleParsingError=Error Domain=NSCocoaErrorDomain Code=3840 "Conversion of string failed." UserInfo={NSDebugDescription=Conversion of string failed.}}
以下是从流中解析数据的程序部分:
...处理流事件的方法:
-(void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
switch(eventCode) {
case NSStreamEventHasBytesAvailable: {
uint8_t buf[1024];
unsigned int len = (unsigned)[(NSInputStream *)aStream read:buf maxLength:1024];
if(len) {
[self handleEventBuffer:buf WithLength:len];
}
...
...以及处理数据的方法:
-(void)handleEventBuffer:(uint8_t*)buf WithLength:(unsigned int)len {
...
NSString *bufStr = [NSString stringWithFormat:@"%s",(const char*)buf];
if ([bufStr containsString:@"bplist00"] && [self.cameraData length] > 0) {
// Detected new file, enter in all the old data and reset for new data
NSError *error;
NSDictionary *tempDict = [[NSDictionary alloc] init];
tempDict = [NSPropertyListSerialization propertyListWithData:self.cameraData
options:0
format:NULL
error:&error];
if (error != nil) {
// Expected good file but no good file, erase and restart
NSLog(@"Error: Failed to convert NSData to NSDict : %@", [error description]);
[self.cameraData setLength:0];
}
...
[self.cameraData setLength:0];
[self.cameraData appendBytes:buf length:len];
} else {
// Still recieving data
[self.cameraData appendBytes:buf length:len];
}
所以,我要问的问题是:
- 如何修复我的解析方法以提供不会随机无法转换的可靠结果?
- 或者是否有比这更好的方法来为此目的解析缓冲区流?
- 或者我只是在做一些愚蠢的事情还是遗漏了一些明显的事情?
您似乎依赖于对流的每次写入都会产生相同大小的匹配读取,您知道 NSStream
可以保证这一点吗?如果不是,那么任何读取都可能包含两个(或更多)编码词典的一部分,并且您会得到您看到的解析错误。
替代方法:
对于每个要发送的编码词典:
写入结束:
- 发送一条消息,其中包含随后的编码字典的字节大小。
- 分块写编码字典,最后一个块可能很短
- 重复
阅读结束:
- 读取指定其确切字节长度的大小消息。
- 分块读取编码字典,确保只读取 (1) 报告的字节数。
- 重复。
如果您使用的是可靠的通信流,这应该使您能够可靠地阅读每个编码字典。它避免了您试图弄清楚每个编码字典之间的边界在哪里,因为该信息是您协议的一部分。
HTH