Bonjour Gatekeeper: How to implement Bonjour service in an iOS or Android app

Gatekeeper is Eventbrite’s onsite entry management technology that delivers the high-speed secure scanning solution for organizers to use on the day of their events.

Ideal for events with more than 5,000 attendees, Gatekeeper consists of Linux-based gate servers and multiple scan devices at each gate. We use iPhone and Android phones with barcode scanner add-ons as the scan terminal, which not only improves the check-in speed but also makes the system easier to set up and use.

Smartphone as Gatekeeper scan terminal

Technically, to connect the scan terminal to the Gatekeeper server, staff members must enter the server’s IP address in the scanner—potentially adding complexity for event organizers. We opted to eliminate this step so staff members could simply turn on the scanner and launch the app, which then automatically finds the nearby Gatekeeper servers.

How, you ask? By leveraging Bonjour, Apple’s implementation of zero configuration networking, which establishes a connection for the scanner to the server by locating nearby services on a local network using mDNS.

Bonjour involves two parts: service registration and service discovery. When Eventbrite’s Gatekeeper launches, it broadcasts its availability by registering a service type in the local network. Let’s say the service type we use is called “_gatekeeper._tcp”. When the scanner app launches, it uses Bonjour to find this service type “_gatekeeper._tcp”. Once Bonjour locates the service, the mobile app automatically finds its IP address and port number, all without user interference.

Bonjour has been built into the iOS system since iOS 4.0. The following is a bare-bone minimal code sample showing how to discover http Bonjour services in the local network and list them in a table view. Once the user taps on the table cell, the browser will load the http page.

#import "EVBViewController.h"
#include <arpa/inet.h>

@interface EVBViewController ()
@property(nonatomic, retain) NSNetServiceBrowser *serviceBrowser;
@property(nonatomic, retain) NSNetService *serviceResolver;
@property(nonatomic, retain) NSMutableArray* services;
@end

@implementation EVBViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.services = [[NSMutableArray alloc] init];
    self.serviceBrowser = [[NSNetServiceBrowser alloc] init];
    self.serviceBrowser.delegate = self;

    [self searchForBonjourServices];

}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (void)searchForBonjourServices
{
    [self.serviceBrowser searchForServicesOfType:@"_http._tcp" inDomain:@"local"];
}

#pragma mark UITableViewDataSourceDelegate
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    int count = [self.services count];
    if (count == 0) {
        return 1;
    } else {
        return count;
    }
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *simpleTableIdentifier = @"SimpleTableItem";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];

    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
    }

    int count = [self.services count];
    NSString* displayString;

    if (count == 0) {
        displayString = @"Searching...";
    } else {
        NSNetService *service = [self.services objectAtIndex:indexPath.row];
        displayString = [service name];
    }

    cell.textLabel.text = displayString;
    return cell;
}

#pragma mark UITableViewDelegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{

    if (self.serviceResolver) {
        [self.serviceResolver stop];
    }

    int count = [self.services count];
    if (count != 0) {
        self.serviceResolver = [self.services objectAtIndex:indexPath.row];
        self.serviceResolver.delegate = self;
        [self.serviceResolver resolveWithTimeout:0.0];
    }
}

#pragma mark NSNetServiceDelegate
- (void)netServiceDidResolveAddress:(NSNetService *)service {
    [self.serviceResolver stop];
    NSLog(@"%ld", (long)service.port);

    for (NSData* data in [service addresses]) {
        char addressBuffer[100];
        struct sockaddr_in* socketAddress = (struct sockaddr_in*) [data bytes];
        int sockFamily = socketAddress->sin_family;
        if (sockFamily == AF_INET) {
            const char* addressStr = inet_ntop(sockFamily,
                                               &(socketAddress->sin_addr), addressBuffer,
                                               sizeof(addressBuffer));

            int port = ntohs(socketAddress->sin_port);
            if (addressStr && port) {
                NSLog(@"Found service at %s:%d", addressStr, port);
                NSString *urlString = [NSString stringWithFormat:@"http://%s:%d", addressStr, port];
                NSURL *url = [ [ NSURL alloc ] initWithString: urlString];
                [[UIApplication sharedApplication] openURL:url];
            }
        }
    }

}

- (void)netService:(NSNetService *)sender didNotResolve:(NSDictionary *)errorDict {
    [self.serviceResolver stop];
}

#pragma mark NSNetserviceBrowserDelegate
- (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didFindService:(NSNetService *)aNetService moreComing:(BOOL)moreComing
{
    [self.services addObject:aNetService];

    if (!moreComing) {
        [self.tableView reloadData];
    }
}

- (void)netServiceBrowser:(NSNetServiceBrowser *)netServiceBrowser didRemoveService:(NSNetService *)aNetService moreComing:(BOOL)moreServicesComing
{
    if (self.serviceResolver && [aNetService isEqual:self.serviceResolver]) {
        [self.serviceResolver stop];
    }

    [self.services removeObject:aNetService];
    if (!moreServicesComing) {
        [self.tableView reloadData];
    }
}

@end

For more information about using Bonjour in iOS, please refer to this page.

Android doesn’t support Bonjour natively prior to version Jelly Bean 4.1. Fortunately, JmDNS open source project provides a pure java implementation of Bonjour, which works well with Android.

In order to use JmDNS library, Android project must obtain these two permissions:

  • android.permission.CHANGE_WIFI_MULTICAST_STATE
  • android.permission.INTERNET

The following is a bare-bone minimal code snippet showing how to discover and resolve a certain Bonjour service type using JmDNS.

private void startBonjour() {
   WifiManager wifi = (WifiManager) getSystemService(android.content.Context.WIFI_SERVICE);
   lock = wifi.createMulticastLock("lock");
   lock.setReferenceCounted(true);
   lock.acquire();
   try {
      jmdns = JmDNS.create();
      jmdns.addServiceListener(SERVICE_TYPE,
            listener = new ServiceListener() {
               @Override
               public void serviceResolved(ServiceEvent ev) {
                  listService("\nService resolved: "
                        + ev.getInfo().getQualifiedName()
                        + "\nip: "
                        + ev.getInfo().getInet4Addresses()[0]
                              .getHostAddress().toString() + "\nport: "
                        + ev.getInfo().getPort());

               }

               @Override
               public void serviceRemoved(ServiceEvent ev) {
                  listService("\nService removed: " + ev.getName());
               }

               @Override
               public void serviceAdded(ServiceEvent event) {
                  jmdns.requestServiceInfo(event.getType(), event.getName(),
                        1);
               }
            });
} catch (IOException e) {
      e.printStackTrace();
      return;
   }
}

private void listService(final String msg) {
   handler.postDelayed(new Runnable() {
      @Override
      public void run() {
         TextView t = (TextView) findViewById(R.id.textview_main_item);
         t.setText(msg + "\n============== " + t.getText());
      }
   }, 10);
}

On Jelly Bean 4.1, Network Service Discovery (the name Android chooses to call Bonjour) is built into the system. There is no need to use any third-party library for this purpose. For more information about Android Network Service Discovery, please refer to this article. There is also a code sample showing how to use it.

As always, you’re welcome to download the code samples (iOS and Android) used in this article if you want to delve deeper into this topic.