Bonjour with MonoTouch

Someone was asking about Bonjour examples in MonoTouch in irc yesterday so I threw this quick and dirty sample together. If you’re not familiar, Bonjour is Apple’s zeroconf networking implementation. It is open-source and even includes a version for Windows. Bonjour allows you to publish and discover services without needing pre-configured information. It’s all about the discovery of services. The actual implementation of the server and client networking code is up to you (I haven’t included that in this example however).

To use Bonjour with MonoTouch, you deal primarily with two classes, NSNetService and NSNetServiceBrowser. NSNetService is used to publish and resolve services. NSNetServiceBrowser, as the name suggests, allows you to browse for services. Once you have discovered a service via a browse, you resolve it in a separate operation, after which you can implement whatever networking code you like to communicate with the service.

In this simple example I publish a service when the application finishes launching and browse from a view controller that includes a table and a text view. The table displays each service as it is discovered. Selecting the row in the table associated with a particular service calls resolve on the service. Adding, removing and resolution of services is logged in the text view.

Here’s the relevant code that does the service publication:

        NetDelegate _netDel;
        NSNetService _ns;

        void InitNetService ()
        {
            _ns = new NSNetService ("", "_testservice._tcp", UIDevice.CurrentDevice.Name, 9999);
            _netDel = new NetDelegate ();
            _ns.Delegate = _netDel;
            _ns.Publish ();
        }

        class NetDelegate : NSNetServiceDelegate
        {
            public override void Published (NSNetService sender)
            {
                Console.WriteLine ("published {0}", sender.Name);
            }

            public override void PublishFailure (NSNetService sender, NSDictionary errors)
            {
                Console.WriteLine ("publish failure {0}," + sender.Name);
            }
        }

The browsing is done by calling the NSNetServiceBrowser’s SearchForServices method. I keep a list of NSNetServices as they are discovered in the FoundService callback and register for the AddressResolved event. Similarly, I remove them from the list via ServiceRemoved. Each service is registered to an AddressResolved handler as it is found. The resolve call, something you would do when you want perform actual networking calls with the service, is made here in the UITableViewSource’s RowSelected implementation. When the callback to ServiceAddressResolved happens, you’ll have an NSNetService complete with information that you can use to perform networking as you see fit.

Here’s the code for the controller with the service browsing and resolution:

    public partial class TestViewController : UIViewController
    {
        List _serviceList;
        NSNetServiceBrowser _netBrowser;
        ServicesTableSource _source;

        // constructors omitted for brevity …

        public override void ViewDidLoad ()
        {
            base.ViewDidLoad ();
            
            InitNetBrowser ();
        }

        internal void InitNetBrowser ()
        {
            _serviceList = new List ();
            _netBrowser = new NSNetServiceBrowser ();
            
            _source = new ServicesTableSource (this);
            servicesTable.Source = _source;
            
            _netBrowser.SearchForServices ("_testservice._tcp", "");
            
            _netBrowser.FoundService += delegate(object sender, NSNetServiceEventArgs e) {
                
                servicesTextView.Text += "\r\n" + e.Service.Name + " added";
                
                _serviceList.Add (e.Service);                
                e.Service.AddressResolved += ServiceAddressResolved;
                
                //NOTE: could also insert and remove rows in a more fine grained fashion here as well
                servicesTable.ReloadData ();
            };
            
            _netBrowser.ServiceRemoved += delegate(object sender, NSNetServiceEventArgs e) {
                
                servicesTextView.Text += "\r\n" + e.Service.Name + " removed";
                
                var nsService = _serviceList.Single (s => s.Name.Equals (e.Service.Name));
                _serviceList.Remove (nsService);
                servicesTable.ReloadData ();
            };
        }

        void ServiceAddressResolved (object sender, EventArgs e)
        {
            NSNetService ns = sender as NSNetService;
            
            if(ns != null)
                servicesTextView.Text += "\r\n" + ns.Name + " resolved";
            
            // at this point you could use any networking code you like to communicate with the service
        }

        class ServicesTableSource : UITableViewSource
        {
            TestViewController _controller;
            const string SERVICE_CELL_ID = "servicecell";

            public ServicesTableSource (TestViewController controller)
            {
                _controller = controller;
            }

            public override int RowsInSection (UITableView tableview, int section)
            {
                return _controller._serviceList.Count;
            }

            public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
            {
                var serviceCell = tableView.DequeueReusableCell (SERVICE_CELL_ID) ?? new UITableViewCell (UITableViewCellStyle.Value1, SERVICE_CELL_ID);
                
                NSNetService ns = _controller._serviceList[indexPath.Row];
                
                serviceCell.TextLabel.Text = ns.Name;
                
                return serviceCell;
            }
            
            public override void RowSelected (UITableView tableView, NSIndexPath indexPath)
            {
               NSNetService ns = _controller._serviceList[indexPath.Row];
               ns.Resolve(60);
            }
        }
    }

Here’s the app running on the device while another instance is running in the simulator:

You can download the sample project here.

One thought on “Bonjour with MonoTouch

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s