NSStream, NSInputStream, NSOutputStream

  • I modified slightly the Echo Server code sample from Apple with the
    following results.  One, I couldn't stream a large file from a
    polling routine.  More than likely it would cancel because of a
    Sigterm 15.

    It appears from reading the docs that the user cannot detect the end
    of a stream and that the NSStreamEventEndEncountered only detects a
    close. Two, when unarchiving a file in a client input stream delegate
    method, if the stream terminated from the server because it was too
    large, the client terminates on an unarchiver error because it didn't
    get the whole stream.  Third, the output stream methods shown in Echo
    Server are polling methods, not delegate methods.

    So:

    1. How do I stream a large file between connections or is NSStream
    the wrong tool?  Can the stream size be modified?

    2. What is the largest stream size?

    3. Is it possible to detect a valid archive before I unarchive it, or
    do I simply have to intercept the exception?

    4. How does one trigger and make available a file an output stream so
    that the delegate methods can be used?

    Thank,

    John MacMullin
  • On 18 May '08, at 8:50 AM, John MacMullin wrote:

    > I modified slightly the Echo Server code sample from Apple with the
    > following results.  One, I couldn't stream a large file from a
    > polling routine.  More than likely it would cancel because of a
    > Sigterm 15.

    Whenever you report a crash, a backtrace is very helpful. Or at least
    tell us what function/method the crash was in. This shouldn't crash,
    so the problem is probably in something in your code.

    > It appears from reading the docs that the user cannot detect the end
    > of a stream and that the NSStreamEventEndEncountered only detects a
    > close.

    "End of stream" and "close" are the same thing: a TCP input stream
    ends when the other side closes the connection (or crashes...)

    > Two, when unarchiving a file in a client input stream delegate
    > method, if the stream terminated from the server because it was too
    > large, the client terminates on an unarchiver error because it
    > didn't get the whole stream.

    A stream won't ever terminate due to length. You can send gigabyte
    after gigabyte over a TCP stream, as many happy BitTorrent users can
    attest ;-)

    But yes, if the incoming stream closed before sending all the data the
    reader needed, then I would expect the reader to report an error. This
    shouldn't cause a crash, though; it sounds like your code isn't
    handling the error gracefully.

    > Third, the output stream methods shown in Echo Server are polling
    > methods, not delegate methods.

    The CocoaEcho sample? I just looked at it, and you're right. The
    writing code is badly designed. The client will block in a 'while'
    loop until all the data is sent, and the server code will simply drop
    response data on the floor if there isn't room to send it. I just
    filed feedback on the web page for the sample.

    > 1. How do I stream a large file between connections or is NSStream
    > the wrong tool?  Can the stream size be modified?

    Most of the CocoaEcho code is reasonable as a base for doing this. Rip
    out the server-side code that echoes the data back, and instead write
    the data to a file.

    On the client/sender side, it's best to use the 'spaceAvailable'
    delegate call, as you said. In response to that call, read some data
    from the file into a buffer, then write it to the stream. Something
    like 4k will do. Pay attention to the return value of the write, which
    tells you how many bytes actually got written, and advance a file-
    position instance variable by that amount. That'll tell you the
    position to read from in the input file next time.

    > 2. What is the largest stream size?

    There isn't one. TCP was designed to handle arbitrary length streams.
    There are internal byte counters but they just wrap around harmlessly
    after 4GB.

    > 3. Is it possible to detect a valid archive before I unarchive it,
    > or do I simply have to intercept the exception?

    No. If you have data in archive format, you have to just hand it to
    NSUnarchiver and wrap the call in @try/@catch.

    But you said you were sending a file? In that case you should just
    send it as a stream of bytes. If you're reading the entire file into
    an NSData and then sending that with NSArchiver, that's a huge amount
    of overhead for no gain.

    > 4. How does one trigger and make available a file an output stream
    > so that the delegate methods can be used?

    I don't understand that question. Can you give more detail? (Or
    perhaps I answered it above under #1.)

    —Jens
  • On May 18, 2008, at 10:50 AM, John MacMullin wrote:

    > I modified slightly the Echo Server code sample from Apple with the
    > following results.

    To which sample are you referring?  I assume you mean the sample
    called CocoaEcho at <http://developer.apple.com/samplecode/CocoaEcho/>.

    > One, I couldn't stream a large file from a polling routine.  More
    > than likely it would cancel because of a Sigterm 15.

    SIGTERM, really?  I can't think of why or how a SIGTERM would be
    raised by the system or the frameworks.  I can see SIGPIPE, SIGTRAP,
    or SIGABRT.  And, of course, programming error can result in SIGBUS or
    SIGSEGV.

    Hmm.  Perhaps launchd might terminate a service with SIGTERM if it
    determined that the service was misbehaving somehow.

    > It appears from reading the docs that the user cannot detect the end
    > of a stream and that the NSStreamEventEndEncountered only detects a
    > close.

    What is it that you would call the end of a stream other than it being
    closed?

    > Two, when unarchiving a file in a client input stream delegate
    > method, if the stream terminated from the server because it was too
    > large, the client terminates on an unarchiver error because it
    > didn't get the whole stream.

    That doesn't seem surprising.

    > Third, the output stream methods shown in Echo Server are polling
    > methods, not delegate methods.

    Well, it does blocking output, true.  And it doesn't account for the
    possibility of partial writes.  If it were to account for that, I
    would expect that it would loop around the [ostream write:buffer
    maxLength:actuallyRead], advancing the buffer and decrementing the
    length each time.  And it would handle errors.

    For it to properly perform asynchronous output using delegate methods,
    it would need to buffer the data it receives from clients.  But it
    would have to impose some limit on the amount of data it is willing to
    buffer before it can be written out.  When it hit that limit, it would
    have to stop reading from that client until it could send some of its
    output.

    By writing synchronously, it's effectively doing that with some
    automatic, smallish buffer supplied by the underlying APIs.  However,
    a block on one client causes a block of the entire server.

    A better approach might be to suspend processing of that one client's
    input stream by removing it from the runloop.  Then, when that
    client's buffer drained a bit, due to some
    NSStreamEventHasSpaceAvailable events, the input stream could be re-
    added to the runloop.  If you just ignore
    NSStreamEventHasSpaceAvailable events rather than unscheduling the
    input stream, then I think you'll get them in a tight loop and your
    server will eat CPU like mad.

    > 1. How do I stream a large file between connections or is NSStream
    > the wrong tool?  Can the stream size be modified?
    >
    > 2. What is the largest stream size?

    There's no such thing as "stream size".  That's one of the defining
    characteristics of a stream.

    > 3. Is it possible to detect a valid archive before I unarchive it,
    > or do I simply have to intercept the exception?

    I think you just have to catch the exception.

    > 4. How does one trigger and make available a file an output stream
    > so that the delegate methods can be used?

    I don't understand what you're asking here.

    -Ken
  • Jens and Ken:

    Thank you for your  responses.  I believe I now understand in part as
    to what is happening as to the streams.  Let me take the output
    "trigger" first.  The Stream Programming Guide states:  "if the
    delegate receives an NSStreamEventHasSpaceAvailable event and does
    not write anything to the stream, it does not receive further space-
    available events from the run loop until the NSOutputStream object
    receives more bytes."  This may answer the question of the
    "trigger", which is I now believe is simply the write statement.
    That means that instead of doing a polling loop outside of the
    delegate, (or doing writes inside the NSStreamEventHasBytesAvailable
    section of the delegate during reads) I think that all one needs to
    do is to write one byte in the "external" function, then handle the
    rest of the writes through the delegate
    NSStreamEventHasSpaceAvailable event.  That should be a lot easier
    than doing a custom polling loop and trying to resolve the space
    available issues outside of the delegate.  In other words, it seems
    that the delegate should do it for you after doing a one byte write.

    I'll let you know how it works out.  And I will address the other
    questions after I have tested this issue.

    Best regards,

    John

    On May 18, 2008, at 11:10 AM, Jens Alfke wrote:

    >
    > On 18 May '08, at 8:50 AM, John MacMullin wrote:
    >
    >> I modified slightly the Echo Server code sample from Apple with
    >> the following results.  One, I couldn't stream a large file from a
    >> polling routine.  More than likely it would cancel because of a
    >> Sigterm 15.
    >
    > Whenever you report a crash, a backtrace is very helpful. Or at
    > least tell us what function/method the crash was in. This shouldn't
    > crash, so the problem is probably in something in your code.
    >
    >> It appears from reading the docs that the user cannot detect the
    >> end of a stream and that the NSStreamEventEndEncountered only
    >> detects a close.
    >
    > "End of stream" and "close" are the same thing: a TCP input stream
    > ends when the other side closes the connection (or crashes...)
    >
    >> Two, when unarchiving a file in a client input stream delegate
    >> method, if the stream terminated from the server because it was
    >> too large, the client terminates on an unarchiver error because it
    >> didn't get the whole stream.
    >
    > A stream won't ever terminate due to length. You can send gigabyte
    > after gigabyte over a TCP stream, as many happy BitTorrent users
    > can attest ;-)
    >
    > But yes, if the incoming stream closed before sending all the data
    > the reader needed, then I would expect the reader to report an
    > error. This shouldn't cause a crash, though; it sounds like your
    > code isn't handling the error gracefully.
    >
    >> Third, the output stream methods shown in Echo Server are polling
    >> methods, not delegate methods.
    >
    > The CocoaEcho sample? I just looked at it, and you're right. The
    > writing code is badly designed. The client will block in a 'while'
    > loop until all the data is sent, and the server code will simply
    > drop response data on the floor if there isn't room to send it. I
    > just filed feedback on the web page for the sample.
    >
    >> 1. How do I stream a large file between connections or is NSStream
    >> the wrong tool?  Can the stream size be modified?
    >
    > Most of the CocoaEcho code is reasonable as a base for doing this.
    > Rip out the server-side code that echoes the data back, and instead
    > write the data to a file.
    >
    > On the client/sender side, it's best to use the 'spaceAvailable'
    > delegate call, as you said. In response to that call, read some
    > data from the file into a buffer, then write it to the stream.
    > Something like 4k will do. Pay attention to the return value of the
    > write, which tells you how many bytes actually got written, and
    > advance a file-position instance variable by that amount. That'll
    > tell you the position to read from in the input file next time.
    >
    >> 2. What is the largest stream size?
    >
    > There isn't one. TCP was designed to handle arbitrary length
    > streams. There are internal byte counters but they just wrap around
    > harmlessly after 4GB.
    >
    >> 3. Is it possible to detect a valid archive before I unarchive it,
    >> or do I simply have to intercept the exception?
    >
    > No. If you have data in archive format, you have to just hand it to
    > NSUnarchiver and wrap the call in @try/@catch.
    >
    > But you said you were sending a file? In that case you should just
    > send it as a stream of bytes. If you're reading the entire file
    > into an NSData and then sending that with NSArchiver, that's a huge
    > amount of overhead for no gain.
    >
    >> 4. How does one trigger and make available a file an output stream
    >> so that the delegate methods can be used?
    >
    > I don't understand that question. Can you give more detail? (Or
    > perhaps I answered it above under #1.)
    >
    > —Jens
  • On 18 May '08, at 9:19 PM, John MacMullin wrote:

    > I think that all one needs to do is to write one byte in the
    > "external" function, then handle the rest of the writes through the
    > delegate NSStreamEventHasSpaceAvailable event.

    You don't even need to do the one byte externally. Your delegate will
    be told there's space available as soon as the socket's opened.

    —Jens
  • Continuing on my efforts re: my modified Echo Server, the following
    code is crashing.

    - (void)startStreamWrite:(NSOutputStream *)ostream
    {
        NSAutoreleasePool * pool = [[NSAutoreleasePool alloc]init];
    while ([ostream hasSpaceAvailable]) {
      if (remainingToWrite > 0) {
      actuallyWritten = 0;
      actuallyWritten = [ostream write:marker maxLength:1];
      if ((actuallyWritten == -1) || (actuallyWritten == -1)) {
        NSLog(@"[ostream streamError]:\n%@\n", [ostream streamError]);
        NSLog(@"remainingToWrite:\n%u\n", remainingToWrite);
        NSLog(@"ostream:\n%@\n", ostream);
      } else {
        remainingToWrite -= actuallyWritten;
        marker += actuallyWritten;
        NSLog(@"remainingToWrite:\n%u\n", remainingToWrite);
      }
      }
    }
        [pool release];
    }

    The error returned is: [ostream streamError]:
    NSError "POSIX error: Broken pipe" Domain=NSPOSIXErrorDomain Code=32

    I have looked at the stream error docs in NSStream and this error is
    not even reference.

    Any ideas on what this means and how I fix it would be greatly
    appreciated.

    John
  • How are you creating the stream?

    On May 19, 2008, at 7:07 PM, John MacMullin wrote:

    > Continuing on my efforts re: my modified Echo Server, the following
    > code is crashing.
    >
    > - (void)startStreamWrite:(NSOutputStream *)ostream
    > {
    > NSAutoreleasePool * pool = [[NSAutoreleasePool alloc]init];
    > while ([ostream hasSpaceAvailable]) {
    > if (remainingToWrite > 0) {
    > actuallyWritten = 0;
    > actuallyWritten = [ostream write:marker maxLength:1];
    > if ((actuallyWritten == -1) || (actuallyWritten == -1)) {
    > NSLog(@"[ostream streamError]:\n%@\n", [ostream streamError]);
    > NSLog(@"remainingToWrite:\n%u\n", remainingToWrite);
    > NSLog(@"ostream:\n%@\n", ostream);
    > } else {
    > remainingToWrite -= actuallyWritten;
    > marker += actuallyWritten;
    > NSLog(@"remainingToWrite:\n%u\n", remainingToWrite);
    > }
    > }
    > }
    > [pool release];
    > }
    >
    > The error returned is: [ostream streamError]:
    > NSError "POSIX error: Broken pipe" Domain=NSPOSIXErrorDomain Code=32
    >
    > I have looked at the stream error docs in NSStream and this error is
    > not even reference.
    >
    > Any ideas on what this means and how I fix it would be greatly
    > appreciated.
  • On May 19, 2008, at 8:07 PM, John MacMullin wrote:
    > The error returned is: [ostream streamError]:
    > NSError "POSIX error: Broken pipe" Domain=NSPOSIXErrorDomain Code=32
    >
    > I have looked at the stream error docs in NSStream and this error
    > is not even reference.
    >
    > Any ideas on what this means and how I fix it would be greatly
    > appreciated.

    One of the BSD/POSIX routines used by the NSOutputStream
    implementation is returning EPIPE.

    It's documented in the man pages for the BSD routine in question
    (write, send, or similar), and also in one central list: http://
    developer.apple.com/documentation/Darwin/Reference/ManPages/man2/
    intro.2.html

    It means that the would-be recipient of the data you're writing has
    closed their end of the connection.  So, you should look at the
    implementation of the client to figure out why it's closing the
    connection.

    Cheers,
    Ken
  • Ah, thank you.

    John
    On May 19, 2008, at 6:32 PM, Ken Thomases wrote:

    > On May 19, 2008, at 8:07 PM, John MacMullin wrote:
    >> The error returned is: [ostream streamError]:
    >> NSError "POSIX error: Broken pipe" Domain=NSPOSIXErrorDomain Code=32
    >>
    >> I have looked at the stream error docs in NSStream and this error
    >> is not even reference.
    >>
    >> Any ideas on what this means and how I fix it would be greatly
    >> appreciated.
    >
    > One of the BSD/POSIX routines used by the NSOutputStream
    > implementation is returning EPIPE.
    >
    > It's documented in the man pages for the BSD routine in question
    > (write, send, or similar), and also in one central list: http://
    > developer.apple.com/documentation/Darwin/Reference/ManPages/man2/
    > intro.2.html
    >
    > It means that the would-be recipient of the data you're writing has
    > closed their end of the connection.  So, you should look at the
    > implementation of the client to figure out why it's closing the
    > connection.
    >
    > Cheers,
    > Ken