iphone 模拟器和 iPhone 6 plus 上的套接字流缓冲区不同?
iphone socket stream buffer different on simulator and iPhone 6 plus?
我从这个 iPhone chat server.
的例子中获取了这个示例代码
在我的例子中,我需要发送更大量的数据,100k-200k,因此我将缓冲区大小更改为可以容纳我想要的大小。
在 iOS 模拟器(模拟 6plus)上一切正常,只要我在实施 @tc 的建议后尝试在我的 iPhone 6plus 上进行调试。我收到的传入消息分为 3-4 个部分!有什么想法吗?
2015-02-03 22:42:34.756 Sock1[7390:3773054] Incoming message: XML part 1
2015-02-03 22:42:34.759 Sock1[7390:3773054] Incoming message: XML part 2
2015-02-03 22:42:34.774 Sock1[7390:3773054] Incoming message: XML part 3
2015-02-03 22:42:34.794 Sock1[7390:3773054] Incoming message: XML part 4
XML 第 1-4 部分组成了整个消息,因为其中没有空字节字符。
只是为了让事情变得更奇怪,当我在这一行添加一个断点时:
[currentMessage appendBytes:buffer length:len];
并逐步完成(69-70 按继续)一切正常!完全没有错误!所以这与解析或接收的速度或我无法弄清楚的事情有关?
我确定的是,除了消息末尾的终止字符外,服务器不会发送任何空字节字符。
我使用的代码是这样的:
@implementation ViewController
bool connectionError = true;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)initNetworkCommunication {
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (CFStringRef)@"192.168.1.1", 6035, &readStream, &writeStream);
inputStream = (__bridge NSInputStream *)readStream;
outputStream = (__bridge NSOutputStream *)writeStream;
[inputStream setDelegate:self];
[outputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];
[outputStream open];
}
- (void)closeAll {
NSLog(@"Closing streams.");
[inputStream close];
[outputStream close];
[inputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream setDelegate:nil];
[outputStream setDelegate:nil];
inputStream = nil;
outputStream = nil;
}
- (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent {
switch (streamEvent) {
case NSStreamEventOpenCompleted:
NSLog(@"Stream opened");
break;
case NSStreamEventHasBytesAvailable:
connectionError = false;
if (theStream == inputStream) {
uint8_t buffer[1024];
long len;
NSMutableData *currentMessage;
currentMessage = [NSMutableData dataWithBytes:"" length:strlen("")];
while ([inputStream hasBytesAvailable]) {
len = [inputStream read:buffer maxLength:sizeof(buffer)];
[currentMessage appendBytes:buffer length:len];
}
NSString *newMessage = [[NSString alloc] initWithData:currentMessage encoding:NSUTF8StringEncoding];
NSLog(@"Incoming message: %@",newMessage);
NSData *nullByte = [NSMutableData dataWithLength:1];
len = currentMessage.length;
NSRange searchRange = {0, len};
for (NSRange r; (r = [currentMessage rangeOfData:nullByte options:0 range:searchRange]).length; ) {
NSString *message = [[NSString alloc] initWithBytes:currentMessage.bytes length:r.location encoding:NSUTF8StringEncoding];
searchRange.location = r.location+r.length;
searchRange.length = len - searchRange.location;
[self messageReceived:message];
}
[currentMessage replaceBytesInRange:(NSRange){0, searchRange.location} withBytes:NULL length:0];
}
break;
case NSStreamEventErrorOccurred:
NSLog(@"Can not connect to the host!");
connectionError = true;
[self closeAll];
[self connectionLost];
break;
case NSStreamEventEndEncountered:
break;
default:
NSLog(@"Unknown event");
}
}
- (void) connectionLost {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Alert!"
message:@"Connection to the server lost!"
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[alert show];
}
- (void) messageReceived:(NSString *)message {
[messages addObject:message];
if (inputStream.streamStatus == NSStreamStatusClosed || inputStream.streamStatus == NSStreamStatusError || inputStream.streamStatus == NSStreamStatusNotOpen) {
[self closeAll];
[self initNetworkCommunication];
}
// do things with the message, XML parsing...
}
- (void) initConnection {
[self initNetworkCommunication];
messages = [[NSMutableArray alloc] init];
}
- (IBAction)joinChat:(id)sender {
[self initConnection];
[self sendSocketMessage: @"iam:" message: _inputNameField.text];
}
- (void) sendSocketMessage:(NSString*) sendCommand message:(NSString*) sendMessage
{
// do something...
if (outputStream.streamStatus == NSStreamStatusClosed || outputStream.streamStatus == NSStreamStatusError || outputStream.streamStatus == NSStreamStatusNotOpen) {
[self closeAll];
[self initNetworkCommunication];
}
NSString *response = [NSString stringWithFormat: @"%@%@", sendCommand, sendMessage];
NSData *data = [[NSData alloc] initWithData:[response dataUsingEncoding:NSASCIIStringEncoding]];
[outputStream write:[data bytes] maxLength:[data length]];
NSLog(@"clint sent: %@", response);
}
@end
简短的回答是 TCP 提供字节流,而不是可变长度消息流¹。我不知道为什么它在模拟器中工作,除非 192.168.1.1 是同一台机器(在这种情况下它不受通常的流量控制,尽管内核缓冲区那么大可能令人惊讶)。
要解决此问题,您需要以某种方式发出消息结尾的信号。常用的有两种方式:
- 长度前缀。示例包括 DJB 的网络字符串(例如 5:abcde 用于字节串 "abcde")、HTTP 的内容长度、HTTP 1.1 的分块编码和各种二进制协议,通常具有 8 位、16 位或可变长度编码长度。
- 定界符。示例包括换行符(传统上为 CRLF,例如在 Telnet/SMTP/HTTP/IRC 中)、空字节、MIME 边界 (ick)。
在这种情况下,我们可以利用 XML 中不允许空字节这一事实。 currentMessage
被假定为 NSMutableData
ivar 而不是 nil
。
while ([inputStream hasBytesAvailable]) {
len = [inputStream read:buffer maxLength:sizeof(buffer)];
[currentMessage appendBytes:buffer length:len];
}
NSData * nullByte = [NSMutableData dataWithLength:1];
len = currentMessage.length;
NSRange searchRange = {0, len};
for (NSRange r; (r = [currentMessage rangeOfData:nullByte options:0 range:searchRange]).length; ) {
NSString * message = [[NSString alloc] initWithBytes:currentMessage.bytes length:r.location encoding:NSUTF8StringEncoding];
searchRange.location = r.location+r.length;
searchRange.length = len - searchRange.location;
[self messageReceived:message];
}
[currentMessage replaceBytesInRange:(NSRange){0, searchRange.location} withBytes:NULL length:0];
另请注意,如果可以卸载视图,则在 -viewDidLoad
中初始化内容需要注意(如果您仅支持 iOS 6+,则问题不大,因为视图不再自动加载已卸载)。
我从这个 iPhone chat server.
的例子中获取了这个示例代码在我的例子中,我需要发送更大量的数据,100k-200k,因此我将缓冲区大小更改为可以容纳我想要的大小。
在 iOS 模拟器(模拟 6plus)上一切正常,只要我在实施 @tc 的建议后尝试在我的 iPhone 6plus 上进行调试。我收到的传入消息分为 3-4 个部分!有什么想法吗?
2015-02-03 22:42:34.756 Sock1[7390:3773054] Incoming message: XML part 1
2015-02-03 22:42:34.759 Sock1[7390:3773054] Incoming message: XML part 2
2015-02-03 22:42:34.774 Sock1[7390:3773054] Incoming message: XML part 3
2015-02-03 22:42:34.794 Sock1[7390:3773054] Incoming message: XML part 4
XML 第 1-4 部分组成了整个消息,因为其中没有空字节字符。
只是为了让事情变得更奇怪,当我在这一行添加一个断点时:
[currentMessage appendBytes:buffer length:len];
并逐步完成(69-70 按继续)一切正常!完全没有错误!所以这与解析或接收的速度或我无法弄清楚的事情有关?
我确定的是,除了消息末尾的终止字符外,服务器不会发送任何空字节字符。
我使用的代码是这样的:
@implementation ViewController
bool connectionError = true;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)initNetworkCommunication {
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (CFStringRef)@"192.168.1.1", 6035, &readStream, &writeStream);
inputStream = (__bridge NSInputStream *)readStream;
outputStream = (__bridge NSOutputStream *)writeStream;
[inputStream setDelegate:self];
[outputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];
[outputStream open];
}
- (void)closeAll {
NSLog(@"Closing streams.");
[inputStream close];
[outputStream close];
[inputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream setDelegate:nil];
[outputStream setDelegate:nil];
inputStream = nil;
outputStream = nil;
}
- (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent {
switch (streamEvent) {
case NSStreamEventOpenCompleted:
NSLog(@"Stream opened");
break;
case NSStreamEventHasBytesAvailable:
connectionError = false;
if (theStream == inputStream) {
uint8_t buffer[1024];
long len;
NSMutableData *currentMessage;
currentMessage = [NSMutableData dataWithBytes:"" length:strlen("")];
while ([inputStream hasBytesAvailable]) {
len = [inputStream read:buffer maxLength:sizeof(buffer)];
[currentMessage appendBytes:buffer length:len];
}
NSString *newMessage = [[NSString alloc] initWithData:currentMessage encoding:NSUTF8StringEncoding];
NSLog(@"Incoming message: %@",newMessage);
NSData *nullByte = [NSMutableData dataWithLength:1];
len = currentMessage.length;
NSRange searchRange = {0, len};
for (NSRange r; (r = [currentMessage rangeOfData:nullByte options:0 range:searchRange]).length; ) {
NSString *message = [[NSString alloc] initWithBytes:currentMessage.bytes length:r.location encoding:NSUTF8StringEncoding];
searchRange.location = r.location+r.length;
searchRange.length = len - searchRange.location;
[self messageReceived:message];
}
[currentMessage replaceBytesInRange:(NSRange){0, searchRange.location} withBytes:NULL length:0];
}
break;
case NSStreamEventErrorOccurred:
NSLog(@"Can not connect to the host!");
connectionError = true;
[self closeAll];
[self connectionLost];
break;
case NSStreamEventEndEncountered:
break;
default:
NSLog(@"Unknown event");
}
}
- (void) connectionLost {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Alert!"
message:@"Connection to the server lost!"
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[alert show];
}
- (void) messageReceived:(NSString *)message {
[messages addObject:message];
if (inputStream.streamStatus == NSStreamStatusClosed || inputStream.streamStatus == NSStreamStatusError || inputStream.streamStatus == NSStreamStatusNotOpen) {
[self closeAll];
[self initNetworkCommunication];
}
// do things with the message, XML parsing...
}
- (void) initConnection {
[self initNetworkCommunication];
messages = [[NSMutableArray alloc] init];
}
- (IBAction)joinChat:(id)sender {
[self initConnection];
[self sendSocketMessage: @"iam:" message: _inputNameField.text];
}
- (void) sendSocketMessage:(NSString*) sendCommand message:(NSString*) sendMessage
{
// do something...
if (outputStream.streamStatus == NSStreamStatusClosed || outputStream.streamStatus == NSStreamStatusError || outputStream.streamStatus == NSStreamStatusNotOpen) {
[self closeAll];
[self initNetworkCommunication];
}
NSString *response = [NSString stringWithFormat: @"%@%@", sendCommand, sendMessage];
NSData *data = [[NSData alloc] initWithData:[response dataUsingEncoding:NSASCIIStringEncoding]];
[outputStream write:[data bytes] maxLength:[data length]];
NSLog(@"clint sent: %@", response);
}
@end
简短的回答是 TCP 提供字节流,而不是可变长度消息流¹。我不知道为什么它在模拟器中工作,除非 192.168.1.1 是同一台机器(在这种情况下它不受通常的流量控制,尽管内核缓冲区那么大可能令人惊讶)。
要解决此问题,您需要以某种方式发出消息结尾的信号。常用的有两种方式:
- 长度前缀。示例包括 DJB 的网络字符串(例如 5:abcde 用于字节串 "abcde")、HTTP 的内容长度、HTTP 1.1 的分块编码和各种二进制协议,通常具有 8 位、16 位或可变长度编码长度。
- 定界符。示例包括换行符(传统上为 CRLF,例如在 Telnet/SMTP/HTTP/IRC 中)、空字节、MIME 边界 (ick)。
在这种情况下,我们可以利用 XML 中不允许空字节这一事实。 currentMessage
被假定为 NSMutableData
ivar 而不是 nil
。
while ([inputStream hasBytesAvailable]) {
len = [inputStream read:buffer maxLength:sizeof(buffer)];
[currentMessage appendBytes:buffer length:len];
}
NSData * nullByte = [NSMutableData dataWithLength:1];
len = currentMessage.length;
NSRange searchRange = {0, len};
for (NSRange r; (r = [currentMessage rangeOfData:nullByte options:0 range:searchRange]).length; ) {
NSString * message = [[NSString alloc] initWithBytes:currentMessage.bytes length:r.location encoding:NSUTF8StringEncoding];
searchRange.location = r.location+r.length;
searchRange.length = len - searchRange.location;
[self messageReceived:message];
}
[currentMessage replaceBytesInRange:(NSRange){0, searchRange.location} withBytes:NULL length:0];
另请注意,如果可以卸载视图,则在 -viewDidLoad
中初始化内容需要注意(如果您仅支持 iOS 6+,则问题不大,因为视图不再自动加载已卸载)。