3

NSStreamTCP 接続を介して s を使用して 2 つの iOS デバイス間でデータを送信するようにアプリケーションをセットアップしました。

送信されるデータは、次の 2 つの部分で構成されます。

  1. 受信するメッセージ オブジェクトのサイズを示す整数
  2. メッセージ オブジェクト、一部NSStrings、およびNSDataNSKeyedArchiver でエンコードされたオブジェクト

問題:

  1. NSData オブジェクトが約 1.5Mb の場合、デコードしようとすると、理解できないアーカイブ例外が発生します。メッセージがあるはずの場所に続く 4 バイトを読み取ると、次のメッセージのサイズではなく、大きな数値が存在します。
  2. NSData オブジェクトが約 80Kb の場合、2 つのメッセージが正常にデコードされ、理解できないアーカイブ例外が発生します。

ある時点でデータの順序が乱れているように見えます... 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 チャンクに追加して、処理可能な 1 つのデータ チャンクを作成し、一緒に送信するために "連結" します。そうすれば、すべてが一緒にとどまります。物事は一緒にとどまるべきであり、そうしています。それに応じてバッファを調整する必要があります。約 400 から 450 バイトのデータがあるため、dataToSend 用に 512 バイトのバッファーがあり、サーバーとクライアントには、送信されたデータを蓄積するための 4096 (4K) バッファーがあります。しかし、4 人のプレイヤーが 30 fps でデータを共有している場合、1536 バイトを超えることはありません...

各プレイヤーの呼び出し:

- (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、およびgameServerの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←→送信→inputStream→inputBuffer→dataという流れです。

2 つの異なるオブジェクトを送信する代わりに、連結されたデータを使用すると、誰が誰で、いつ何を取得したかを把握しようとするよりも気分が良くなると思います...そして < の代わりに >= を使用し、単に mutableData ブロックを追加しますあなたのdataQueue ...ユースケースは異なる場合があります...

于 2016-06-01T19:57:37.213 に答える