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+,则问题不大,因为视图不再自动加载已卸载)。