Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

Littleodie914

macrumors 68000
Original poster
Jun 9, 2004
1,813
8
Rochester, NY
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
 

Littleodie914

macrumors 68000
Original poster
Jun 9, 2004
1,813
8
Rochester, NY
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?
 

Littleodie914

macrumors 68000
Original poster
Jun 9, 2004
1,813
8
Rochester, NY
*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. :)
 

chem

macrumors regular
Jun 9, 2007
184
0
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!
 

Taum

macrumors member
Jul 28, 2008
56
0
Hi,

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

Have you checked the WiTap sample code ? It seems to me that it does just what you're asking for.
 

Littleodie914

macrumors 68000
Original poster
Jun 9, 2004
1,813
8
Rochester, NY
Hi,

Have you checked the WiTap sample code ? It seems to me that it does just what you're asking for.
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:
 

Taum

macrumors member
Jul 28, 2008
56
0
[_outStream write:[sendingData bytes] maxLength:sizeof([sendingData bytes])]
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).
 

Littleodie914

macrumors 68000
Original poster
Jun 9, 2004
1,813
8
Rochester, NY
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).
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:
 

Littleodie914

macrumors 68000
Original poster
Jun 9, 2004
1,813
8
Rochester, NY
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;
		}
 

Littleodie914

macrumors 68000
Original poster
Jun 9, 2004
1,813
8
Rochester, NY
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:
 

Littleodie914

macrumors 68000
Original poster
Jun 9, 2004
1,813
8
Rochester, NY
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:
 

Taum

macrumors member
Jul 28, 2008
56
0
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.
 

peeInMyPantz

macrumors member
Mar 9, 2006
65
0
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.
 

Littleodie914

macrumors 68000
Original poster
Jun 9, 2004
1,813
8
Rochester, NY
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.
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. :)
 

peeInMyPantz

macrumors member
Mar 9, 2006
65
0
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. :)

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
 

peeInMyPantz

macrumors member
Mar 9, 2006
65
0
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
 

Littleodie914

macrumors 68000
Original poster
Jun 9, 2004
1,813
8
Rochester, NY
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. :)
 

peeInMyPantz

macrumors member
Mar 9, 2006
65
0
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. :)

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?
 

peeInMyPantz

macrumors member
Mar 9, 2006
65
0
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.
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.