3

我有一个应用程序设置为使用NSStreams 通过 TCP 连接在两个 iOS 设备之间发送数据。

发送的数据由两部分组成:

  1. 一个整数,表示即将到来的消息对象的大小
  2. 消息对象,一些NSStrings和一个NSData用 NSKeyedArchiver 编码的对象

问题:

  1. 当 NSData 对象大约为 1.5Mb 时,当我尝试对其进行解码时,我得到一个难以理解的存档异常。当读取消息应该在的位置后面的 4 个字节时,那里是一个大数字,而不是下一条消息的大小。
  2. 当 NSData 对象大约 80Kb 时,成功解码了两条消息,然后我得到了难以理解的存档异常。

似乎数据在某些时候出现了乱序……尽管 TCP 的全部目的是保持它的有序。所以,一定是我的问题!

相关代码

服务器

sendData:传递已使用 NSKeyedArchiver 编码的 Message 对象。它被要求在短时间内收到 100 条消息

// dataQueue is an NSMutableArray
- (void) sendData:(NSData *)data
{
  int size = data.length;
  NSData *msgSize = [NSData dataWithBytes:&size length:sizeof(int)];
  
  if (self.outputStream.hasSpaceAvailable && (self.dataQueue.count == 0)) {
    [self.dataQueue addObject:data];
    [self.outputStream write:msgSize.bytes maxLength:msgSize.length];
  }
  else {
    [self.dataQueue addObject:msgSize];
    [self.dataQueue addObject:data];
  }
}

//called by NSStreamDelegate method when space is available
- (void) hasSpaceAvailable
{
  if (self.dataQueue.count > 0) {
    NSData *tmp = [self.dataQueue objectAtIndex:0];
    [self.outputStream write:tmp.bytes maxLength:tmp.length];
    [self.dataQueue removeObjectAtIndex:0];
  }
}

客户

streamHasBytes:收集消息片段并将它们附加到 self.buffer。当 self.buffer 的长度大于 self.buffer 前 4 个字节指示的消息长度时,Message 对象被解析...

//Called by NSStreamDelegate method when bytes are available
- (void) streamHasBytes:(NSInputStream *)stream
{
  NSInteger       bytesRead;
  uint8_t         buffer[32768];
  
  bytesRead= [stream read:buffer maxLength:sizeof(buffer)];
  
  if (bytesRead == -1 || bytesRead == 0) //...err
  
  @synchronized(self) { //added to test concurrency
  [self.buffer appendBytes:buffer length:bytesRead];
  }
  [self checkForMessage];
}

- (void) checkForMessage
{
  @synchronized(self) { //added to test concurrency
  int msgLength = *(const int *)self.buffer.bytes;
        
  if (self.buffer.length < msgLength) return;
  
  //remove the integer from self.buffer
  [self.buffer replaceBytesInRange:NSMakeRange(0, sizeof(int)) withBytes:NULL length:0]; 
  
  //copy the actual message from self.buffer
  NSData *msgData = [NSData dataWithBytes:self.buffer.bytes length:msgLength];
        
  //remove the message from self.buffer
  [self.buffer replaceBytesInRange:NSMakeRange(0, msgLength) withBytes:NULL length:0];
                                  
  Message *theMsg = [NSKeyedUnarchiver unarchiveObjectWithData:msgData];
  [self.delegate didReceiveMessage:theMsg];
  }
}

编辑:

我现在注意到,在第一个消息中的 NSData 对象大约为 1.5Mb 的情况下,对于大约 1.6Mb 的总消息大小,客户端仅收到大约 1.3Mb ......这将解释难以理解的存档错误。为什么不能交付所有数据?

4

2 回答 2

3

事实证明,在某些情况下,我认为正在发送的数据中只有一小部分实际上正在发送。NSOutputStreamwrite:maxLength:方法返回实际写入流的字节数。所以hasSpaceAvailable上面的方法可以用

NSInteger i = [self.outputStream write:tmp.bytes maxLength:tmp.length];
if (i < tmp.length) {
    //send the difference
}
于 2012-07-17T23:27:37.583 回答
0

我正在做几乎相同的事情,但我采用了一些不同的方法。我选择获取归档 dataToSend 的大小,然后将 int 大小的变量转换为 mutableData 块。然后我将 dataToSend 附加到 dataSized 块中,创建一个您可以处理的数据块,并“连接”在一起发送。这样,一切都保持在一起。事情应该保持在一起,他们确实做到了。您必须相应地调整缓冲区。我有大约 400 到 450 字节的数据,所以我有一个 512 字节的缓冲区用于 dataToSend,然后服务器和客户端有 4096(4K)缓冲区来累积发送的数据。但它永远不会超过 1536 字节,4 个玩家以 30 fps 共享数据......

每个玩家呼叫:

- (void)sendData:(CAKNodeSpec *)sentSpec {

    NSMutableData *archivedData = [NSMutableData dataWithCapacity:512];
    NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:archivedData];

    [sentSpec encodeWithCoder:archiver];
    [archiver finishEncoding];

    [self.gameClient outputData:archivedData];
// gameClient is custom NSStreamDelegate with IN/OUT Streams
}

在 gameClient 和 gameConnect 中为 gameServer

- (void)outputData:(NSMutableData *)dataToSend {
    if (self.outputBuffer != nil) {

    unsigned int size = (unsigned int)dataToSend.length;
// maintains 32-bit/64-bit architecture compatibility & silence warnings

    NSMutableData *dataSized = [NSMutableData dataWithBytes:&size length:sizeof(unsigned int)];

    [dataSized appendBytes:dataToSend.bytes length:size];

    [self.outputBuffer appendData:dataSized];

    [self startOutput];
    }
}

- (void)startOutput {

    NSInteger actuallyWritten = [self.outputStream write:self.outputBuffer.bytes maxLength:self.outputBuffer.length];
        if (actuallyWritten > 0) {
        [self.outputBuffer replaceBytesInRange:NSMakeRange(0, (NSUInteger) actuallyWritten) withBytes:NULL length:0];
    } else {
        [self closeStreams];
    }
}

遵循经典的 switch-case-model

- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)streamEvent {
assert(aStream == self.inputStream || aStream == self.outputStream);
switch(streamEvent) {
    case NSStreamEventOpenCompleted: {
        if (aStream == self.inputStream) {
            self.inputBuffer = [[NSMutableData alloc] init];
            self.dataQueue = [[NSMutableArray alloc] init];
        } else {
            self.outputBuffer = [[NSMutableData alloc] init];
        }
    } break;
    case NSStreamEventHasSpaceAvailable: {
        if ([self.outputBuffer length] != 0) {
            [self startOutput];
        }
    } break;
    case NSStreamEventHasBytesAvailable: {
        uint8_t buffer[4096];
        NSInteger actuallyRead = [self.inputStream read:buffer maxLength:4096];
        if (actuallyRead > 0) {
            [self.inputBuffer appendBytes:buffer length:(NSUInteger)actuallyRead];

            NSLog(@"Read: %ld", (long)actuallyRead);

            [self chopaChunk];
            NSMutableData *specData;

            while ((specData = [self.dataQueue shift])) {
                [self.delegate handleData:specData]; // unarchives
            }
        } else {
            NSLog(@"empty buffer!");
        }
    } break;
    case NSStreamEventErrorOccurred:
    case NSStreamEventEndEncountered: {
        [self closeStreams];
    } break;
    default:
    break;
    }
}

这是真正的差异区域:

- (void)chopaChunk {

    unsigned int dataSize = *(const unsigned int *)self.inputBuffer.bytes;

    while (self.inputBuffer.length >= (sizeof(unsigned int) + dataSize)) {

    //remove the integer from self.inputBuffer
    [self.inputBuffer replaceBytesInRange:NSMakeRange(0, sizeof(unsigned int)) withBytes:NULL length:0];

    //copy the actual message from self.inputBuffer
    NSMutableData *specData = [NSMutableData dataWithBytes:self.inputBuffer.bytes length:dataSize];

    [self.dataQueue addObject:specData];

    //remove the message from self.inputBuffer
    [self.inputBuffer replaceBytesInRange:NSMakeRange(0, dataSize) withBytes:NULL length:0];

    if (self.inputBuffer.length > 0) {
        dataSize = *(const unsigned int *)self.inputBuffer.bytes;
// I just keep going adding, I guess you would add multiple 80K pieces here
        }
    }

}

按照您的指示,我选择检查 inputBuffer 是否 >= dataSize + (sizeof(unsigned int)),就像香肠制造商一样,我们只需在它们进来时将其拧断!:D

流程是数据到 outputBuffer 到 outputStream <- 发送 -> 到 inputStream 到 inputBuffer 返回数据。

我认为,如果您使用连接的数据而不是尝试发送两个不同的对象,那么您会比试图弄清楚谁是谁以及何时获得什么感觉要好...和> =而不是<,只需添加 mutableData 块您的数据队列...您的用例可能会有所不同...

于 2016-06-01T19:57:37.213 回答