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 :)