PrEV
Thoughts from a NeXTStep Guy on Cocoa Development

Bonjour Network Server for iPhone

Mar 10, 2009 by Bill Dudney

While helping my son build his first shipping iPhone app I was forced to dig into Bonjour. It has been quite a while since I did any Cocoa networking (distributed objects way back in the day) so I was nearly starting from scratch.

I started with the iPhone sample code for WiTap (search Xcode docs for it, there is not public URL AFAIK). A fine example as far as it goes, but I found it to be confusing because of the lack of commentary in the code. So I decided to start from scratch using WiTap as an example to look at but not copy the code from. I ended up copying a bit but more or less wrote a new server from scratch.

It was a ton of fun getting back into this. As with so many things on Cocoa once you peal back the skin you see so much more. The thing I really liked about this particular learning experience was getting deep into the delegation model that helps me not have to think too deeply about threads. It is really very nice and makes your life much easier. If you have not had the chance to look into this you should (this example or the URL loading code is a great place to start).

I have uploaded the example code here. Its licensed under the apache 2 license so do with it what you will. I will try to get it into a google code project at some point. I'll be discussing the code in this post so feel free to grab it and follow along.

The server we needed for BounceIt had to be simple to use both the API and the user scenarios it supported. To make it easy to use we choose Bonjour (no brainer) to handle the discovery and configuration. Once connected the peers send small packets back and forth so we also needed a way to limit the TCP buffer so it would send more often than a page full of data. To make the API easy to use we created a delegation protocol that gives the program the info in needs to support several different scenarios but remains simple to implement. We made all the methods required more from not being sure than out of necessity. Over time if its obvious that some of the methods are not really required they should be made optional.

The server has a relatively simple API. You create a new server using either initWithProtocol: or initWithDomainName:protocol:name:. Since we often ignore the name and domain, preferring to let Bonjour pick those for us, the first method is typically preferred. For the cases where you need to specify the domain or name use the second method. Keep in mind that the name is advisory and Bonjour treats this as a 'first come first served' request. In other words, if you ask for you server to be called "Bob's your Uncle" and there is another service already out there with that name you will be renamed by Bonjour to something it deems appropriate (most likely "Bob's your Uncle 2").

If the your app needs to know the actual name the service was published under you can get it from the server after it finishes starting.

After you create and init the server you set the delegate. The delegate of the server will get call backs when data arrives or when connections are made and lost.

Now that the server is setup and ready its time to send it the start: method. This is where the magic begins. If you have never messed with Bonjour it is really cool! Basically you register a service under the protocol string and the clients can look for that service using the same string. The client side does not need to know anything about the port numbers or underlying network protocol (udp, tcp etc). All we have to do from the client side is look up the protocol name using Bonjour and it pops out objects that can connect you to the server. Under the hood it is using dynamic DNS but you really don't need to know anything about that part of it (part of the beauty of Bonjour). The server takes care of looking for other services for you though so you don't have to mess with service discovery. The server sends it's delegate the serviceAdded:moreComing: method as services are discovered and serviceRemoved:moreComing: when services go away.

Before launching the Bonjour service though, the application needs a port. I know I just said 'hey you don't have to know the port number' but that was for the client side of the equation. The server must have a port to send data out and to receive info back. We just ask the OS for an open port and then use that to start up our service, the client configuration will be taken care of by Bonjour.

On to the start: method. First thing it does is create a CFSocket and configure it. The port number is set to zero which is how we ask the OS to choose a port for us. During the binding of the CFSocket to the native socket, the OS picks a port and assigns it to the native socket. We then ask the CFSocket for its address and from there we get the port. Kind of a pain in the neck because of the amount of code we have to write but fairly straightforward once you get your head wrapped around it. This stuff is easier on the Mac but the higher level NSPort class is not available on iPhone OS X.

Now that we have the port from the OS we can launch our Bonjour service. This is where the beauty of the Bonjour API's become evident. To launch the service we just create an instance of NSNetService, add it to the run loop, publish it and then set ourselves as the delegate. Four simple steps and we have a server started! Behind the scenes Bonjour is doing a ton of work with the dynamic DNS stuff. It lets us know what is happening by calling the delegate methods. The cool part is that all the behind the scenes stuff is going on in one or more threads and we did not have to do anything to manage them. The NSNetService and Bonjour takes care of all that for us and lets us know on the main thread through our delegate method implementations. It really is amazing when you let is sink in how easy it is to get network ability into your app. The server implements all the necessary Bonjour delegation API's and converts them into the server delegate method calls.

Now that the server is started we wait for notification via the server delegate methods. In the sample app each new server discovered is added to the list of available servers and displayed in a table view. When the user clicks on one of the entries the connection is made to the underlying socket. Now that the two services are connected the Bonjour service is stopped and the socket is used for further communication between the two services.

I tried to comment all the places in the code that were tricky to me but I'm sure there are places where I either did not document the code well or I got something wrong. Please let me know if you find something that does not make sense.

Happy networking :)



Comments:

Hey Bill,

I don't know if this is the right place to put this but I could not find a place just to e-mail you. I just finished watching your Coding in Objective-C screen cast (great job with those, helped a lot). I am new to objective-C and would like to create games for the iphone and I was wondering if you were going to create any screen cast for making games for the iphone. Nothing big just small games like connect four. I know you have the screen cast of "Frist app on the iphone", and your book on "Core Animation". I was also wondering if you can kind of point me in a direction that will help me learn to make games more then applications.

Posted by Casey on April 10, 2009 at 01:44 PM MDT #

Hi Casey,

I'd love to do more screen casts in that direction.

Unfortunately I don't know of any particular 'developing games for iPhone' resources.

Good luck!

Posted by Bill Dudney on April 11, 2009 at 02:35 AM MDT #

Hey Bill,

I would just like to tell you that I have been working my butt off trying to figure out bonjour, but your Server.h class just took all the stress away, I wanted to tell you how relieved I am, and that I have added you to my rss feeds, so I will be reading your blog very often.

Thanks,
-Chris

Posted by Chris Alvares on April 20, 2009 at 02:40 AM MDT #

Hey Bill,

It was really helpful for me. But we can send data to the other device is limited
but I need to send the data approximately 10000 bytes.
it gives following error when data increases after the limit.

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSKeyedUnarchiver initForReadingWithData:]: incomprehensible archive (0x62, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x30, 0x30)'

I think the data is cut down and while converting it back to required format it gives the above exception.
so how can we increase the limit of the data or any best suggestion about the problem.

thanks

Posted by gnc on October 18, 2010 at 11:12 AM MDT #

That was good application to start with wifi. thanks for post. but one thing

As we know data can be sent 1500 bytes maximum at a time. what to do with it if we need to send data more than that.

I have tried to send data in packets. but when data reaches on destination device it becomes different from that which was sent.
I am discussing my problem here

http://www.cocos2d-iphone.org/forum/topic/10161

wait for help

Posted by gnc on October 18, 2010 at 11:12 AM MDT #

Dear sir,

May I use your code for cross platform network application. I want to send text message from iphone to windows pc... I am .net developer. Can u guide me how can I achieve it.

Thanks... please needful...

Posted by Prakash Rathod on March 20, 2011 at 06:31 PM MDT #

This code is working!!! Thnx for such a nince tutorial.......

Posted by Sneha Patel on March 20, 2011 at 06:31 PM MDT #

I am new to iphone programming.I just wanted to know can this application can be modified in such a manner that it connects to more than two users,I have no knowledge of socket programming.So please guide me..
Thanks.

Posted by Leena on March 20, 2011 at 06:31 PM MDT #

Hey, there!

First of all - thanks Bill for great stuff!
You save a lot of my time!!! =))

Second - I had a problem with this code - very high latency.
I even wanted to change this server from TCP to UDP, but I'm so nub in networking and it takes me maybe months... =(((

So, I had googled 2 days and found solution!!!! =)))))
If you want everything works ok - just stop bonjour browser after connection completed ok. To do this simply add: [server stopBrowser]; at the end of serverRemoteConnectionComplete! =)

Good luck!

Posted by Evgeniy Fedoseev on June 20, 2011 at 09:06 AM MDT #

Hey Bill, thanks for this code!

I'm sorry if I shouldn't ask this here.
I'm trying to build a peer to peer connection between several ipods, not using bluetooth and without internet around.
I suppose that means ipod need to be operated as an access point, same idea as wifi-direct, is it possible in iOS SDK?

Posted by Jing on September 02, 2011 at 01:09 PM MDT #

So many comments of people who made it work. For some reason I can't .
When it starts, it looks like it creates the service, but tableview is empty, number of rows in the table view returns 0. But, I know for sure that service gets created (I have a little client app that shows all connections). Don't know what the problem might be. Was going to play around with this example trying to understand how it all works.

Would appreciate if the author or somebody who made it work replies.

Posted by Irina on November 14, 2011 at 07:17 PM MST #

Is there a way to send a large amounts of nsdata over the network with out it crashing??

Posted by John O'Sullivan on December 14, 2011 at 08:39 AM MST #

Hi,
Is there any way to make your code working with more than 2 devices : 1 device would be the server and the others the receiver, so when we send a text, all other devices will display it ?
Thanks a lot :)

Posted by CutMaster on December 14, 2011 at 08:39 AM MST #

Hi Bill,

I know it's not the right place for this but I couldn't find anything on the internet for direction.

I'm trying to building a opengl that just about opens an image. And an iPhone app that shows the same display as the desktop. I would like to communication with the desktop application from the iPhone app e.g. Zooming/panning. Please could you advise on steps/milestones that I should take to achieve this?

Thank you,
Haroon

Posted by Haroon on February 21, 2012 at 11:37 AM MST #

Hi Thanks for your valuable coding but i got 2012-03-13 13:59:46.264 MacNetworkingTesting[1266:903] *** -[NSCFArray objectAtIndex:]: index (0) beyond bounds (0)
error while press the button.and can you please guide how to implement for 2 iPhone devices insted of mac,through wi-fi.

thank you in advance,

Posted by Satish on March 13, 2012 at 05:04 AM MDT #

Hi Bill,
Thanks to make this open source. But in ios 5, when iphone goes to screen lock mode, the connection between all devices gets disconnected and we pop to the first page. Any solution for keeping the connections alive ? It would be very helpful for me.
Thanks in advance

Posted by Ayon Das on April 03, 2012 at 07:29 AM MDT #

Bill,

I see so many comments here and I just wanted to, unlike the rest of the folk here, JUST thank you for your hard work and putting this up. Really helped me out.

Regards from Scotland,
Ken

Posted by Ken Reid on January 08, 2013 at 01:22 PM MST #

thanks Bill - massive help - thankyou so much for sharing.
in return I'll share my solution to a problem I nearly didn't crack (hence my visit to this page)

I was trying to send data once the serverRemoteConnectionComplete delegate method was called - to get initial values between the two ends but kept getting errors from the server about not haveing space on the outputStream.

the solution was to detach a new thread from within that delegate method and do your data sending from the new thread.

I have no idea if this is the expected behaviour but it seems to be the case.

Posted by justin webster on January 16, 2013 at 10:07 AM MST #

Post a Comment:
  • HTML Syntax: Allowed