Разбор данных после отправки между устройствами iOS через NSStream

У меня установлено приложение для отправки данных между двумя устройствами iOS с использованием NSStreamчерез TCP соединение.

Отправленные данные состоят из двух частей:

  1. Целое число, указывающее размер объекта сообщения
  2. Объект сообщения, некоторые NSStrings и NSData объект, закодированный с помощью NSKeyedArchiver

Проблема:

  1. Когда размер NSData составляет около 1,5 МБ, я получаю непонятное архивное исключение, когда пытаюсь его декодировать. При чтении 4 байтов, следующих за тем, где должно быть сообщение, появляется большое число, а не размер следующего сообщения.
  2. Когда NSData объект размером около 80 КБ, два сообщения успешно декодируются, и тогда я получаю непонятное архивное исключение.

Кажется, что данные в какой-то момент выходят из строя... хотя вся цель TCP - поддерживать их в порядке. Итак, я должен быть проблемой!

Соответствующий код

сервер

sendData: передается объект Message, который был закодирован с помощью NSKeyedArchiver. Он вызывается за 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 становится больше, чем длина сообщения, указанная в первых 4 байтах self.buffer, объект 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,5 МБ, для общего размера сообщения около 1,6 МБ, только около 1,3 МБ получено клиентом... Это объясняет непонятное Архив ошибок. Почему все данные не будут доставлены?

2 ответа

Решение

Оказывается, что в некоторых случаях фактически отправлялась только часть данных, которые, как я предполагал, отправлялись. NSOutputStream"s write:maxLength: Метод возвращает количество байтов, которые были фактически записаны в поток. Итак hasSpaceAvailable метод выше может быть исправлен с

NSInteger i = [self.outputStream write:tmp.bytes maxLength:tmp.length];
if (i < tmp.length) {
    //send the difference
}

Я делаю что-то практически то же самое, но я пошел немного другим методом. Я решил получить размер заархивированного dataToSend, а затем преобразовать переменную int size в кусок mutableData. Затем я добавляю dataToSend к блоку dataSized, создавая один блок данных, который вы можете обработать и "объединить" для отправки вместе. Таким образом, все остается вместе. Вещи должны оставаться вместе, и они остаются. Вы должны настроить буферы соответственно. У меня есть данные размером от 400 до 450 байт, поэтому у меня есть 512-байтовый буфер для dataToSend, тогда сервер и клиенты имеют 4096 (4K) буфер для накопления отправленных данных. Но он никогда не проходит 1536 байт, когда 4 игрока обмениваются данными со скоростью 30 кадров в секунду...

Каждый игрок звонит:

- (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 для игрового сервера

- (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];
    }
}

следуя классической модели коммутатора

- (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 <- send -> для inputStream для inputBuffer обратно к данным.

Я думаю, что если вы перейдете к использованию сцепленных данных вместо того, чтобы пытаться отправить два разных объекта, вы будете чувствовать себя лучше, чем пытаться выяснить, кто есть кто, и когда вы получите то, что... и> = вместо <, и просто добавить блоки mutableData в Ваше dataQueue... ваши варианты использования могут отличаться...

Другие вопросы по тегам