0

Ok so I have been at this bug all day, and I think I've got it narrowed down to the fundamental problem.

Background:

I am working on an app that has required me to write my own versions of NSNetService and NSNetServiceBrowser to allow for Bonjour over Bluetooth in iOS 5. It has been a great adventure, as I knew nothing of network programming before I started this project. I have learned a lot from various example projects and from the classic Unix Network Programming textbook. My implementation is based largely on Apple's DNSSDObjects sample project. I have added code to actually make the connection between devices once a service has been resolved. An NSInputStream and an NSOutputStream are attained with CFStreamCreatePairWithSocketToHost( ... ).

Problem:

I am trying to send some data over this connection. The data consists of an integer, a few NSStrings and an NSData object archived with NSKeyedArchiver. The size of the NSData is around 150kb so the size of the whole message is around 160kb. After sending the data over the connection I am getting the following exception when I try to unarchive...

Terminating app due to uncaught exception 'NSInvalidArgumentException', 
reason: '*** -[NSKeyedUnarchiver initForReadingWithData:]: incomprehensible archive 

After further exploration I have noticed that the received data is only about 2kb.. The message is being truncated, thus rendering the archive "incomprehensible."

Potentially relevant code:

The method that sends the data to all connected devices

- (void) sendMessageToPeers:(MyMessage *)msg
{
  NSEnumerator *e = [self.peers objectEnumerator];

  //MyMessage conforms to NSCoding, messageAsData getter calls encodeWithCoder:
  NSData *data = msg.messageAsData; 

  Peer *peer;
  while (peer = [e nextObject]) {
    if (![peer sendData:data]) {
      NSLog(@"Could not send data to peer..");
    }
  }
}

The method in the Peer class that actually writes data to the NSOutputStream

- (BOOL) sendData:(NSData *)data
{
  if (self.outputStream.hasSpaceAvailable) {
    [self.outputStream write:data.bytes maxLength:data.length];
    return YES;
  }
  else {
    NSLog(@"PEER DIDN'T HAVE SPACE!!!");
    return NO;
  }
}

NSStreamDelegate method for handling stream events ("receiving" the data)

The buffer size in this code is 32768 b/c that's what was in whatever example code I learned from.. Is it arbitrary? I tried changing it to 200000, thinking that the problem was just that the buffer was too small, but it didn't change anything.. I don't think I fully understand what's happening.

- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
  switch (eventCode) {

    case NSStreamEventHasBytesAvailable: {
      NSInteger       bytesRead;
      uint8_t         buffer[32768];      // is this the issue?
//    uint8_t         buffer[200000];   //this didn't change anything

      bytesRead = [self.inputStream read:buffer maxLength:sizeof(buffer)];
      if (bytesRead == -1) ...;
      else if (bytesRead == 0) ...;
      else {
        NSData  *data = [NSData dataWithBytes:buffer length:bytesRead];
        [self didReceiveData:data];
      }
    } break;

    /*omitted code for other events*/
  }
}
RyanM
  • 4,474
  • 4
  • 37
  • 44
  • Add logic to actually count the number of bytes you send and receive. I suspect that you're only receiving the first buffer. Your `didReceiveData` method should be accumulating the data from multiple buffers. – Hot Licks Jul 11 '12 at 01:19

2 Answers2

7

NSStream over a network like that will be using a TCP connection. It can vary, but the maximum packet size is often around 2k. As the message you’re sending is actually 160k, it will be split up into multiple packets.

TCP abstracts this away to just be a stream of data, so you can be sure all these packets will receive in the correct order.

However, the stream:handleEvent: delegate method is probably being called when only the first 2k of data has arrived – there’s no way for it to know that there’s more coming until it does.

Note the method read:maxLength: doesn’t gauruntee you’ll always get that max length – in this case it seems to be only giving you up to 2k.

You should count up the actual bytesReceived, and concatenate all the data together until you receive the total amount you’re waiting for.

How does the receiver know how much data it wants? – you might want to design your protocol so before sending data, you send an integer of defined size indicating the length of the coming data. Alternatively, if you’re only ever sending one message over the socket, you could simply close it when finished, and have the receiver only unarchive after the socket is closed.

DouglasHeriot
  • 1,674
  • 2
  • 13
  • 19
1

You seem to be reading from self.inputStream but the stream passed into your stream:handleEvent: method is called aStream. Are they referencing the same object somehow? Otherwise I'm not sure you're reading the stream that actually has bytes available

Tim Dean
  • 8,253
  • 2
  • 32
  • 59
  • Good point.. I'm not sure why I wrote it like that. They do reference the same object though, and the class is only the NSStreamDelegate for one NSInputStream – RyanM Jul 11 '12 at 05:52