Help!! Sending Two-Way Communications via Bonjour

Discussion in 'iOS Programming' started by Littleodie914, Aug 14, 2008.

  1. Littleodie914 macrumors 68000

    Littleodie914

    Joined:
    Jun 9, 2004
    Location:
    Rochester, NY
    #1
    Howdy all. :)

    Spic and span: I publish an NSNetService on one device, and it's picked up on another. Both devices need to act as a client and a server, so that iPhone A can connect to iPhone B, and vice-versa, but in this scenario, lets say:

    iPhone A is the "server," or the one whose NSNetService instance is being resolved.

    iPhone B is the "client," or the one who resolves the NSNetService.

    Now, my question is, what's the flow of communication? I've looked through all of Apple's bonjour examples for the Mac and for the iPhone, and here's what I know:

    Step 1: Sockets. You need to create a socket for your applications to communicate "through." I did this like the following:
    Code:
    NSSocketPort *mySocketPort = [[NSSocketPort alloc] initWithTCPPort:12345];
    Step 2: File Handles. This is where I start getting confused. Should each application (since it needs to act as both client and server, depending on the scenario) have two file handles, one that is configured to send data once it has been connected to (NSFileHandleConnectionAcceptedNotification) and one that is configured to read data once it receives it (NSFileHandleReadCompletionNotification)?

    Step 3: Catching All the Data. Here's where it gets even worse. I know that when you add the observers to the sockets for the above notifications, you have to point the selectors to methods, so lets say I have:

    Code:
    - (void)connectionReceived:(NSNotification *)aNotification;
    - (void)fileHandleDataRead:(NSNotification *)aNotification;
    Those methods, once I test the code in the simulator, are each run once. I don't know which device (server vs. client) is running the code, or which File Handles are being passed to which methods.

    Here's what does work in my program so far.

    When the client connects to the server, the connectionReceived: method is sent to the observer of the socket. (My application delegate.) Then, in that connection received method, I do this:

    Code:
    - (void)connectionReceived:(NSNotification *)aNotification {
    	NSLog(@"Connection received");
        NSFileHandle * incomingFileHandle = [[aNotification userInfo] objectForKey:NSFileHandleNotificationFileHandleItem];
    	
        NSData * representationToSend = [[[NSNumber numberWithInt:13] stringValue] dataUsingEncoding:NSUnicodeStringEncoding];
        [[aNotification object] acceptConnectionInBackgroundAndNotify];
        [incomingFileHandle writeData:representationToSend];
    	NSLog(@"Writing number to file handle.");
    	[incomingFileHandle closeFile];
    }
    That writes the data for that NSNumber object to the file handle, correct?

    But then, things get even more confusing, when streams enter the picture! The input and output streams for the net service have their delegates pointed to my application delegate as well. (I'm trying to consolidate all my methods into one class right now, just until I understand it a bit better.)

    So once the data is written to the above File Handle, it's "caught" or read in the following delegate method. (From NSStream)

    Code:
    - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)event;
    So I guess my question is:

    How much of this is necessary, and what's the flow of communication between two devices? Once you have a Net Service, what's the easiest way to open a two-way communication link to it? I can send data from one device using the file handle and pick it up in another using that stream's delegate method, but then, how do I send data back?

    That NSStream delegate method was used when only a single, writable File Handle was provided by the server. Since there's also a readable one now, that responds to the fileHandleDataRead: method, do I still need to work with streams? Or, if it's that easy to get the input and output streams of an NSNetService instance, why do we need File Handles at all?

    Thanks for making it through that, I've never done any networking programming before, but it's pretty exciting stuff. :D
     
  2. Littleodie914 thread starter macrumors 68000

    Littleodie914

    Joined:
    Jun 9, 2004
    Location:
    Rochester, NY
    #2
    The more I look at it, the more I'm leaning towards ditching NSFileHandle completely. It doesn't seem to do anything different from NSOutputStream, since you can use writeData: (NSData *) in the file handle, and write: (bunchaBytes) in the output stream.

    Can any network guru confirm this?
     
  3. Littleodie914 thread starter macrumors 68000

    Littleodie914

    Joined:
    Jun 9, 2004
    Location:
    Rochester, NY
    #3
    *sigh* Or maybe the NSFileHandle is what's used by the server to send information, and the NSStream is what's used by the client to pick it up?

    Where's Apple's modern documentation on this? :confused:

    Edit: Looking at Apple's example with the Picture Sharing Browser and Server, located in Developer/Examples/Foundation, if anyone could explain how one would (once a picture is received by the browser) send an NSString back to the server with the text "Howdy", then I think that would wrap things up for me. :)
     
  4. chem macrumors regular

    Joined:
    Jun 9, 2007
    #4
    I can't help you, but I can wish you luck! Bonjour with iphones seems like it will open up a lot of interesting possiblities for apps. If you finish up your code/app and can post a reduced example of what you ended up doing, I'd love to check it out.

    Have fun!
     
  5. Taum macrumors member

    Joined:
    Jul 28, 2008
    #5
    Hi,

    Have you checked the WiTap sample code ? It seems to me that it does just what you're asking for.
     
  6. Littleodie914 thread starter macrumors 68000

    Littleodie914

    Joined:
    Jun 9, 2004
    Location:
    Rochester, NY
    #6
    That was it. :) I had looked at the WiTap code before, but at the time (before I knew what was really going on) it didn't look like an effective way to send larger chunks of data, only ints.

    Now, I'm running into another issue. The server/client part works, and I'm trying to send an NSData object from the client to the server. I'm using the following code to send the data:

    Code:
    NSMutableArray *dataArray = [[NSMutableArray alloc] init];
    [dataArray insertObject:[inputTextField text] atIndex:0];
    NSData *sendingData = [NSKeyedArchiver archivedDataWithRootObject:dataArray];
    if (_outStream && [_outStream hasSpaceAvailable])
    		if([_outStream write:[sendingData bytes] maxLength:sizeof([sendingData bytes])] == -1)
    
    Then, on the receiving side of things, I have the following code:

    Code:
    case NSStreamEventHasBytesAvailable:
    		{
    			if (!currentDownload) {
    				currentDownload = [NSMutableData dataWithCapacity:1];
    			}
    			if (stream == _inStream) {
    				uint8_t b;
    				unsigned int len = 0;
    				len = [_inStream read:&b maxLength:sizeof(uint8_t)];
    				if(!len) {
    					if ([stream streamStatus] != NSStreamStatusAtEnd)
    						NSLog(@"Failed reading data from peer");
    				} else {
    					NSLog(@"Received bytes: %d", b);
    					[currentDownload appendBytes:&b length:sizeof(b)];
    					if ([stream streamStatus] == NSStreamStatusAtEnd) { // SPIFFY LINE
    						NSMutableArray *receivedArray = [NSKeyedUnarchiver unarchiveObjectWithData:currentDownload];
    						NSLog(@"Received array: %@", receivedArray);
    						NSLog(@"String in array: %@", [receivedArray objectAtIndex:0]);
    						[currentDownload release];
    						currentDownload = nil;
    						
    						if ([_outStream hasSpaceAvailable]) {
    							uint8_t success = 13;
    							[_outStream write:(const uint8_t *)&success maxLength:sizeof(13)];
    						}
    					}
    				}
    			}
    			break;
    		}
    So that grabs the bytes, builds a NSMutableData object, and once it's finished grabbing all the bytes, it gets the array from it. (Or so it's supposed to.) Here's what happens in the console, however:

    Code:
    2008-08-16 08:38:07.608 iProcrastinate Mobile[4964:20b] Found an existing database
    2008-08-16 08:38:07.645 iProcrastinate Mobile[4964:20b] Setup
    2008-08-16 08:38:07.658 iProcrastinate Mobile[4964:20b] Advertising Server
    2008-08-16 08:38:08.409 iProcrastinate Mobile[4964:20b] serverDidEnableBonjour:withName:
    2008-08-16 08:38:13.846 iProcrastinate Mobile[4964:20b] Open Streams
    2008-08-16 08:38:22.387 iProcrastinate Mobile[4964:20b] Received bytes: 98
    2008-08-16 08:38:22.388 iProcrastinate Mobile[4964:20b] Received bytes: 112
    2008-08-16 08:38:22.389 iProcrastinate Mobile[4964:20b] Received bytes: 108
    2008-08-16 08:38:22.389 iProcrastinate Mobile[4964:20b] Received bytes: 105
    Then the app just sits there. So it receives a buncha bytes, but how do you realize once you've gotten all of them? I've also changed the line that looks for the end of the data (the one that says "SPIFFY LINE" in comments) to:

    Code:
    if (![((NSInputStream *)stream) hasBytesAvailable]) { // SPIFFY LINE]
    And then I get an error about not having the right bytes:

    Code:
    [Session started at 2008-08-16 08:47:10 -0400.]
    2008-08-16 08:47:11.700 iProcrastinate Mobile[5025:20b] Found an existing database
    2008-08-16 08:47:11.715 iProcrastinate Mobile[5025:20b] Setup
    2008-08-16 08:47:11.721 iProcrastinate Mobile[5025:20b] Advertising Server
    2008-08-16 08:47:12.471 iProcrastinate Mobile[5025:20b] serverDidEnableBonjour:withName:
    2008-08-16 08:47:44.972 iProcrastinate Mobile[5025:20b] Open Streams
    2008-08-16 08:47:54.460 iProcrastinate Mobile[5025:20b] Received bytes: 98
    2008-08-16 08:47:54.460 iProcrastinate Mobile[5025:20b] Received bytes: 112
    2008-08-16 08:47:54.461 iProcrastinate Mobile[5025:20b] Received bytes: 108
    2008-08-16 08:47:54.461 iProcrastinate Mobile[5025:20b] Received bytes: 105
    
    [Session started at 2008-08-16 08:47:54 -0400.]
    2008-08-16 08:47:54.464 iProcrastinate Mobile[5025:20b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSKeyedUnarchiver initForReadingWithData:]: incomprehensible archive (0x62, 0x70, 0x6c, 0x69, 0x0, 0x0, 0x0, 0x0)'
    So the bytes are either getting there or are they aren't, but I definitely don't know exactly how to determine when I've got them all! :confused:

    Edit: I should also add that when I just try to send an NSData built straight from an NSString, only the first letter appears on the receiving end. :confused:
     
  7. Taum macrumors member

    Joined:
    Jul 28, 2008
    #7
    No no no no no.

    To get the number of bytes in an NSData object, use -length.

    You might want to check the exact definition of sizeof() in C but it's not like PHP where you can use it to get the number of elements in an array. Actually, I believe sizeof() gets resolved at compile-time.


    When you read from the stream, you'll want to allocate some buffer and then read a chunk of data with -read:maxLength: (maxLength being the length of your buffer) repeateadly until -hasBytesAvailable becomes NO (I didn't try it but I guess it's the way it's meant to be done if you have large chunks to transfer).
     
  8. Littleodie914 thread starter macrumors 68000

    Littleodie914

    Joined:
    Jun 9, 2004
    Location:
    Rochester, NY
    #8
    Ah, thanks for pointing that out. :) Not sure why I didn't see to change that.

    But now, I'm just getting *lots* of "Received bytes" messages, over 100 for a single send, (but I am sending an array, so it makes sense?) when I use:

    Code:
    [stream streamStatus] == NSStreamStatusAtEnd
    To find the end of the stream, but that condition is never met, so I never actually convert the data back into an array.

    And then if I use this instead:

    Code:
    [((NSInputStream *)stream) hasBytesAvailable]
    Then I get the following error:

    Code:
    [Session started at 2008-08-16 13:28:17 -0400.]
    2008-08-16 13:28:19.431 iProcrastinate Mobile[5553:20b] Found an existing database
    2008-08-16 13:28:19.455 iProcrastinate Mobile[5553:20b] Setup
    2008-08-16 13:28:19.463 iProcrastinate Mobile[5553:20b] Advertising Server
    2008-08-16 13:28:20.214 iProcrastinate Mobile[5553:20b] serverDidEnableBonjour:withName:
    2008-08-16 13:28:27.395 iProcrastinate Mobile[5553:20b] Open Streams
    2008-08-16 13:28:32.860 iProcrastinate Mobile[5553:20b] Received bytes: 98
    
    [Session started at 2008-08-16 13:28:32 -0400.]
    2008-08-16 13:28:32.862 iProcrastinate Mobile[5553:20b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSKeyedUnarchiver initForReadingWithData:]: incomprehensible archive (0x62, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0)'
    Thanks for the help so far! Any ideas from here? :eek:
     
  9. Littleodie914 thread starter macrumors 68000

    Littleodie914

    Joined:
    Jun 9, 2004
    Location:
    Rochester, NY
    #9
    Ah, I think I got some of my variables and their purposes confused.

    Here's the final chunk that is working for me! Thanks for all the help!

    Code:
    case NSStreamEventHasBytesAvailable:
    		{
    			if (!currentDownload) {
    				currentDownload = [[NSMutableData alloc] initWithCapacity:409600];
    			}
    			if (stream == _inStream) {
    				uint8_t readBuffer[409600];
    				int amountRead = 0;
    				NSInputStream * is = (NSInputStream *)stream;
    				amountRead = [is read:readBuffer maxLength:409600];
    				[currentDownload appendBytes:readBuffer length:amountRead];
    				if(!amountRead) {
    					if ([stream streamStatus] != NSStreamStatusAtEnd)
    						NSLog(@"Failed reading data from peer");
    				} else {
    					NSLog(@"Amount read: %d", amountRead);
    					NSLog(@"Bytes: %d", readBuffer);
    					if (![((NSInputStream *)stream) hasBytesAvailable]) {
    						NSMutableArray *receivedArray = [NSKeyedUnarchiver unarchiveObjectWithData:currentDownload];
    						NSLog(@"Received array: %@", receivedArray);
    						NSLog(@"String in array: %@", [receivedArray objectAtIndex:0]);
    						[currentDownload release];
    						currentDownload = nil;
    						
    						if ([_outStream hasSpaceAvailable]) {
    							uint8_t success = 13;
    							[_outStream write:(const uint8_t *)&success maxLength:sizeof(13)];
    						}
    					}
    				}
    			}
    			break;
    		}
     
  10. Littleodie914 thread starter macrumors 68000

    Littleodie914

    Joined:
    Jun 9, 2004
    Location:
    Rochester, NY
    #10
    Back again... :(

    Anyone know why writing/reading with streams in the simulator would work fine, and then crash on a device?

    I get the following traces when I run it on my iPod.

    Console:

    Code:
    Tue Aug 19 10:27:54 unknown iProcrastinate Mobile[509] <Warning>: serverDidEnableBonjour:withName:
    Tue Aug 19 10:27:59 unknown iProcrastinate Mobile[509] <Warning>: Callback entered
    Tue Aug 19 10:27:59 unknown iProcrastinate Mobile[509] <Warning>: Open Streams
    Tue Aug 19 10:27:59 unknown iProcrastinate Mobile[509] <Warning>: End of open streams
    Tue Aug 19 10:27:59 unknown iProcrastinate Mobile[509] <Warning>: Callback exited
    Tue Aug 19 10:28:06 unknown ReportCrash[510] <Notice>: Formulating crash report for process iProcrastinate Mobile[509]
    Tue Aug 19 10:28:06 unknown com.apple.launchd[1] <Warning>: Exited abnormally: Segmentation fault
    Tue Aug 19 10:28:06 unknown SpringBoard[58] <Warning>: Application <SBApplication: 0x440e450> A4GG7J4JH3.com.craigotis.iProcrastinateMobile activate:  deactivate:  exited abnormally with status
    Actual Crash Log:

    Code:
    Identifier:      iProcrastinate Mobile
    Version:         ??? (???)
    Code Type:       ARM (Native)
    Parent Process:  launchd [1]
    
    Date/Time:       2008-08-19 10:28:04.918 -0400
    OS Version:      iPhone OS 2.0.2 (5C1)
    Report Version:  103
    
    Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
    Exception Codes: KERN_INVALID_ADDRESS at 0x2fc17478
    Crashed Thread:  0
    
    Thread 0 Crashed:
    0   iProcrastinate Mobile         	0x00004790 0x1000 + 14224
    1   Foundation                    	0x30710668 _inputStreamCallbackFunc + 44
    2   CoreFoundation                	0x302858e0 _CFStreamSignalEventSynch + 84
    3   CoreFoundation                	0x3025bd50 CFRunLoopRunSpecific + 1974
    4   CoreFoundation                	0x3025b584 CFRunLoopRunInMode + 44
    5   GraphicsServices              	0x316998e4 GSEventRunModal + 268
    6   UIKit                         	0x30a5e308 -[UIApplication _run] + 404
    7   UIKit                         	0x30a671dc UIApplicationMain + 1064
    8   iProcrastinate Mobile         	0x000020b6 0x1000 + 4278
    9   iProcrastinate Mobile         	0x0000202c 0x1000 + 4140
    Any clues? I have no idea why it would run differently depending on the environment. :confused:
     
  11. Littleodie914 thread starter macrumors 68000

    Littleodie914

    Joined:
    Jun 9, 2004
    Location:
    Rochester, NY
    #11
    I've also realized that the crash occurs on the device side regardless of whether it's acting as a client or a server. Eg., whether it's the service being resolved, or the app doing the resolving. :confused:
     
  12. Taum macrumors member

    Joined:
    Jul 28, 2008
    #12
    Use the debugger connected with your device to find out where the problem is.

    A segmentation fault means you are trying to access memory at a location where you don't have any allocated. It can be hard to track down, but most of the time either you free your memory too early or you forget to initialize a pointer.
     
  13. peeInMyPantz macrumors member

    peeInMyPantz

    Joined:
    Mar 9, 2006
    #13
    HI,
    how's the progress with the app so far?
    I'm trying to do some networking stuff with bonjour as well.. but i have no idea where to start...

    can you nudge me in the right direction? what samples to look into and what books are suitable? I am looking into making an app for iphone and computer, to allow data transfer.
     
  14. Littleodie914 thread starter macrumors 68000

    Littleodie914

    Joined:
    Jun 9, 2004
    Location:
    Rochester, NY
    #14
    Check out the Picture Sharing and Picture Sharing Browser sample code. It's somewhere like Examples > Foundation in your Developer folder. Sorry I can't be more specific, I'm not at my dev. Mac. :)
     
  15. peeInMyPantz macrumors member

    peeInMyPantz

    Joined:
    Mar 9, 2006
    #15
    Hi,
    thanks, I took a look at the file and it's quite useful for sending files.
    but is it possible to use that sample and modify it to send/receive objects such as NSString, NSMutableArrray, NSMutableDictionary?

    the 2 samples uses NSFileHandle, so it's more specific to file sending. However if I am sending multiple files, I have a problem of getting the original file names of each file being sent, so maybe being able to send objects such as array of filenames will be useful.

    is it possible to do that?

    looks like the WiTap sample is good for passing button pressed , while picture sharing is good for file transfer. Been trying to merge them unsuccessfully so that I can trigger file transfer from any of the both device. Also have been unsuccessful with sending/received arrays/strings
     
  16. peeInMyPantz macrumors member

    peeInMyPantz

    Joined:
    Mar 9, 2006
    #16
    ok..looks like I can just easily pass dictionaries using TXTRecordData..
    but if I need to transfer more than 1 file, how do modify the existing Picture Sharing and Picture Sharing Browser sample?

    in the sample, the toggleSharing function in Picture Sharing will start the bonjour service and upon connection, immediately sends out the file using NSFileHandle

    I tried to loop the file sending using an Array of file paths, but it will always fail for the second file, with an error that the file cannot be found. I am not familiar with NSFileHandle, but I think it is because NSFileHandle is mean for one file, so if I need to transfer a second file, I will have to make a new NSFileHandle. The problem is everything is tied together in the toggleSharing function, and I can't figure out what I have to do to recreate a new NSFilehandle so that can make multiple fill transfer by looping an array of filepaths
     
  17. Littleodie914 thread starter macrumors 68000

    Littleodie914

    Joined:
    Jun 9, 2004
    Location:
    Rochester, NY
    #17
    Why not just send all the files (or NSData objects that you loaded the files into) in an NSArray? That's how I've been sending multiple objects over the same stream. :)
     
  18. peeInMyPantz macrumors member

    peeInMyPantz

    Joined:
    Mar 9, 2006
    #18
    Hi,
    I have tried that actually, with only 1 file in the array. I printed the array on the original device just to make sure that it is populated properly, but I received an empty array on the other device. How big is the file that you tried to send over? The file I tried was a sql db file, about 300kb in size.

    Maybe I need to check that the array has been completely sent?
     
  19. peeInMyPantz macrumors member

    peeInMyPantz

    Joined:
    Mar 9, 2006
    #19
    i just checked,
    if I just add a few small variables in the NSDictionary and send it over, it gets sent successfully, but if I add a NSData (of a file) to the existing NSDictionary, it doesn't.

    So I tried to print out the NSDictionary object on the sender device, and it shows no problem.
    The NSDictionary is converted to a TXTRecordData before sending using
    [NSNetService dataFromTXTRecordDictionary:dataDict];

    if I evaluate the line above by printing it,
    I get a non-null value without the NSData of the file, but I get a null value if I add the file. That explains why I am not receiving anything on the receiver device. But I don't know why it's giving me null value.
     

Share This Page