2014-04-07 4 views
10

Ich bin gerade mit der Socket-Programmierung auf iOS gestartet und ich habe Schwierigkeiten, die Verwendung der NSStreamEventHasSpaceAvailable Ereignis für NSOutputStreams zu bestimmen.Richtiger Weg zum Senden von Daten über einen Socket mit NSOutputStream

Auf der einen Seite, zeigt, daß in den Apple's official documentation (Listing 2)-stream:handleEvent: Delegatmethode sollten die Daten an den Ausgangspuffer mit -write:maxLength: Nachricht geschrieben werden, Daten fortlaufend vorbei aus einem Puffer, wann immer das NSStreamEventHasSpaceAvailable Ereignis empfangen wird.

Auf der anderen Seite, this tutorial from Ray Wenderlich und this iOS TCP socket example on GitHub ignorieren die NSStreamEventHasSpaceAvailable Ereignis zusammen, und einfach weiter und -write:maxLength: in den Puffer gehen, wann immer sie brauchen (auch -hasSpaceAvailable ignorieren).

Drittens gibt es this example code die beide zu tun scheint ...

Meine Frage ist also, was ist der richtige Weg (s) zu einer NSOutputStream Schreiben von Daten zu verarbeiten, die an eine Steckdose angeschlossen ist? Und welchen Nutzen hat der NSStreamEventHasSpaceAvailable Ereigniscode, wenn er (scheinbar) ignoriert werden kann? Es scheint mir, dass entweder sehr glücklicher UB passiert (in Beispiel 2 und 3), oder es gibt mehrere Möglichkeiten, Daten über ein Socket-basiertes NSOutputStream ...

Antwort

13

zu senden. Sie können jederzeit in einen Stream schreiben , aber für Netzwerk-Streams kehrt -write:maxLength: nur zurück, bis mindestens ein Byte in den Socket-Schreibpuffer geschrieben wurde. Wenn der Socket-Schreibpuffer voll ist (z. B. weil das andere Ende der Verbindung die Daten nicht schnell genug liest), wird Block der aktuelle Thread. Wenn Sie über den Hauptthread schreiben, blockiert dies die Benutzeroberfläche .

Das Ereignis NSStreamEventHasSpaceAvailable wird signalisiert, wenn Sie in den Stream schreiben können, ohne zu blockieren. Schreiben nur als Antwort auf dieses Ereignis vermeidet, dass der aktuelle Thread und möglicherweise die Benutzerschnittstelle blockiert wird.

Alternativ können Sie von einem separaten "Writer-Thread" in einen Netzwerkstream schreiben.

12

Nachdem ich @ MartinRs Antwort gesehen habe, lese ich die Apple Docs erneut und lese etwas über NSRunLoop Ereignisse. Die Lösung war nicht so trivial wie ich zuerst dachte und erfordert eine zusätzliche Pufferung.

Schlussfolgerungen

Während der Ray Wenderlich Beispiel funktioniert, ist es nicht optimal ist - wie von @MartinR erwähnt, wenn es kein Raum in dem ausgehenden TCP-Fenster ist, wird der Anruf an write:maxLength blockieren. Der Grund, warum das Beispiel von Ray Wenderlich funktioniert, ist, dass die gesendeten Nachrichten klein und selten sind und bei einer fehlerfreien Internetverbindung mit großer Bandbreite "wahrscheinlich" funktionieren wird. Wenn Sie beginnen, mit (viel) größeren Datenmengen zu arbeiten, die (viel) häufiger gesendet werden, können die write:maxLength: Anrufe jedoch blockieren und die App wird zum Stillstand kommen ...

Für den NSStreamEventHasSpaceAvailable Fall Apples Dokumentation folgende Ratschläge hat:

Wenn die Delegierten ein NSStreamEventHasSpaceAvailable Ereignis empfangen und alles, was in den Stream nicht schreiben, es erhält keine weiteren raum verfügbaren Ereignisse aus dem Laufe Schleife, bis das NSOutputStream-Objekt mehr Bytes empfängt. ... ... Sie können festlegen, dass der Delegat ein Flag setzt, wenn er beim Empfang eines NS- StreamEventHasSpaceAvailable-Ereignisses nicht in den Stream schreibt. Später, wenn Ihr Programm mehr Bytes zum Schreiben hat, kann es dieses Flag überprüfen und, falls gesetzt, direkt in die Output-Stream-Instanz schreiben.

Es ist daher nur ‚garantiert sicher zu sein‘ write:maxLength: in zwei Szenarien zu nennen:

  1. Innerhalb des Rückrufs (bei Empfang des NSStreamEventHasSpaceAvailable Ereignisses).
  2. Außerhalb des Rückrufs, wenn und nur wenn wir bereits die NSStreamEventHasSpaceAvailable empfangen haben, aber nicht innerhalb des Callbacks write:maxLength: aufrufen (z. B. hatten wir keine Daten zu schreiben).

Für Szenario (2), werden wir den Rückruf wieder nicht empfangen, bis write:maxLength tatsächlich direkt aufgerufen wird - von Apple schlägt eine Flagge innerhalb der Delegierten Rückruf-Einstellung (siehe oben), um anzuzeigen, wenn wir dies tun dürfen.

Meine Lösung war eine zusätzliche Pufferungsebene - Hinzufügen einer NSMutableArray als Datenwarteschlange. Mein Code für Daten an einen Socket schreiben sieht wie folgt aus (Kommentare und Fehler der Kürze halber weggelassen Prüfung, die currentDataOffset Variable gibt an, wie viel von der ‚aktuellen‘ NSData Objekt, das wir gesendet haben):

// Public interface for sending data. 
- (void)sendData:(NSData *)data { 
    [_dataWriteQueue insertObject:data atIndex:0]; 
    if (flag_canSendDirectly) [self _sendData]; 
} 

// NSStreamDelegate message 
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode { 
    // ... 
    case NSStreamEventHasSpaceAvailable: { 
     [self _sendData]; 
     break; 
    } 
} 

// Private 
- (void)_sendData { 
    flag_canSendDirectly = NO; 
    NSData *data = [_dataWriteQueue lastObject]; 
    if (data == nil) { 
     flag_canSendDirectly = YES; 
     return; 
    } 
    uint8_t *readBytes = (uint8_t *)[data bytes]; 
    readBytes += currentDataOffset; 
    NSUInteger dataLength = [data length]; 
    NSUInteger lengthOfDataToWrite = (dataLength - currentDataOffset >= 1024) ? 1024 : (dataLength - currentDataOffset); 
    NSInteger bytesWritten = [_outputStream write:readBytes maxLength:lengthOfDataToWrite]; 
    currentDataOffset += bytesWritten; 
    if (bytesWritten > 0) { 
     self.currentDataOffset += bytesWritten; 
     if (self.currentDataOffset == dataLength) { 
      [self.dataWriteQueue removeLastObject]; 
      self.currentDataOffset = 0; 
     } 
    } 
} 
+0

Danke für die Einsicht, Eintagsfliegen , sieht aus, als hätte ich das gleiche Maß an Verwirrung wie du. Danke noch einmal! – Patricia

+0

Beachten Sie, dass bytesWritten negativ sein kann, was auf einen Fehler hinweist, daher würde der obige Code im Falle eines Fehlers hängen bleiben, weil currentDataOffset nicht übereinstimmen würde ... Vorschlag bearbeiten –

+0

Könnten Sie mir helfen zu verstehen, wie der Empfänger weiß, ob er nur einen anderen empfängt Stück Daten oder Daten als Ganzes? Vielleicht hat mein Puffer eine Kapazität von 2 Bytes und ich kann nur zwei ASCII-Zeichen gleichzeitig schreiben. Wie der Empfänger sagt, ob ich "do" oder "dope" Worte an ihn sende? –