ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.


AddThis Social Bookmark Button

Zero Configuration Networking: Using the Java APIs, Part 1
Pages: 1, 2, 3, 4, 5

Using the APIs

In this section, you will see quick examples of performing specific tasks using the Java APIs. You will begin by registering a service and verifying that it is being advertised correctly by using the dns-sd command-line tool. Next, you will browse using Java code to discover the service you just registered, and resolve the service to get the target host and port number. The final example in this section revisits registering a service, but this time with attached attributes, stored in the service's TXT record.

Registering a Service

There are two steps you must take to register a service:

  1. Call DNSSD.register( ) using one of the two available signatures.

  2. Provide a class that implements the RegisterListener interface.

The DNSSD.register( ) call

The first step can be as simple as a single line of code:

DNSSDRegistration r = DNSSD.register("Moët & Chandon", "_example._tcp", 9099, this);

This advertises a service of type _example._tcp, which is listening on port 9099, with the instance name Moët & Chandon. Remember that instance names are not restricted like conventional DNS hostnames. Service instance names can contain uppercase, lowercase, spaces, punctuation, accented characters, and even non-roman characters like Kanji.

The return value from calling DNSSD.register( ) is a DNSSDRegistration object. DNSSDRegistration extends DNSSDService, so you can use the stop( ) method when it is time to stop advertising the service.

You can also add additional records to a registered service, and you can get a reference to the service's primary TXT record if you need to update that record to contain new data:

DNSRecord addRecord(int flags, int rrType, byte[] rData, int ttl)
DNSRecord getTXTRecord(  )

Most applications don't need to use the calls, but one well-known example that does is iChat, which adds the user's image icon as an additional record and updates the service's TXT record each time the user's status message changes.

The register( ) method might throw a DNSSDException, which must be caught. In our example program, the only reason you'd get an exception would be if you had an illegal parameter because of a typing mistake, say, "_txp" where it should say "_tcp." Using printStackTrace( ) in your exception handler can help you track down and debug this kind of mistake.

Before you compile this code, you first need to make sure that the calling object (this) implements the required RegisterListener interface.

The RegisterListener

To fulfill the requirement for a RegisterListener, you could create a whole new class especially for this purpose, but usually that is not necessary. Usually, the object responsible for registering a service is also the natural place to handle events pertaining to that registration, so all you have to do is add serviceRegistered( ) and operationFailed( ) methods to that class and declare that it now implements the RegisterListener interface.

In this current example, name conflicts should be handled automatically for us because we don't use NO_AUTO_RENAME, so we don't expect the operationFailed( ) method to be called at all. If it is called, it just prints a message to the standard error output to help us debug the code and find out what went wrong.

The serviceRegistered( ) method in our example will print a message to standard output displaying the advertised service's name, type, and domain. Note that the service's name may not be the name we asked for, if that name is already in use. Indeed, many programs don't specify a name at all, just passing in an empty string for the name and letting DNS-SD automatically use the system-wide default name, handling name conflicts as necessary.

public void serviceRegistered(DNSSDRegistration registration, int flags,
  String serviceName, String regType, String domain)
  System.out.println("Registered Name  : " + serviceName);
  System.out.println("           Type  : " + regType);
  System.out.println("           Domain: " + domain);

It's important to understand that in the dynamic world of networking, success at one moment in time is not a guarantee of continued future success in perpetuity. The serviceRegistered callback indicates that, at this moment, the service is being advertised on the network under the indicated name. Over the lifetime of a long-running program, the program should expect that it is quite possible that the name may change, and the serviceRegistered( ) method may be called again with new data. After the initial probing and announcement of the chosen unique service name, that name may subsequently change as a result of both internal and external factors.

The internal factor is explicit user action. If you registered your service using an empty string for the name so that your service uses the system-wide default name, and the user subsequently decides to change the system-wide default name, then she doesn't need to quit and relaunch your server for it to get the new name. The name is updated live, and you will get a new serviceRegistered( ) callback telling you the new name.

The external factor that may cause your service name to change is connecting to a new network. If the user starts your server on his laptop when it's not connected to any network, and then (perhaps hours or days later) connects to a network where your chosen name is already in use, one of two things will happen. If you specified NO_AUTO_RENAME, then your operationFailed method will be called. If you did not specify NO_AUTO_RENAME, then Multicast DNS will automatically select a new unique name for you and notify you with a new serviceRegistered callback.

The importance of the instance name provided in the serviceRegistered( ) callback depends on what kind of program you're writing. For a background process like an FTP server with no user interface, the program itself may not care at all what name is being advertised, as long as users can find it and connect. For a server program with a user interface, it may want to find out its advertised name simply for cosmetic reasons, to display it in a status window.

The kind of program for which the reported instance name is most interesting is the kind that's both a client and a server. For example, iChat advertises its presence on the network using Bonjour and, at the same time, browses to find other iChat instances on the network. One of the instances it discovers will be itself, but naturally iChat wants its Bonjour window to show only other users on the network, not itself. By comparing discovered instances against its own name as reported in the serviceRegistered( ) callback, iChat can tell when it has discovered itself on the network and filter that particular discovered entity from the list displayed in its Bonjour window.

Some developers have asked why DNS-SD doesn't do this filtering automatically. The problem is that the definition of self is slippery. Does self mean the same machine? Same user ID? Same process? Same thread? Automatically preventing discovery of all services on the same machine would be wrong. Some background processes use a web-based configuration interface, which they advertise with DNS-SD. If DNS-SD couldn't discover services on the same machine, these local background processes wouldn't show up in Safari on that machine. This would create the nonsensical situation where you could configure the process from any machine on the network except the one where the process is actually running! Another problem scenario is multiuser Unix machines, which can have more than one user logged on at a time. If DNS-SD couldn't discover services on the same machine, two users logged onto the same Unix machine from different X Window terminals would be effectively invisible to each other. Preventing discovery of services that happen to be running with the same user ID causes a similar set of inadvertent problems.

Automatically filtering discovery of services advertised from the same Unix process ID also doesn't necessarily give the results you might want. Sometimes the entity doing the browsing and the entity doing the advertising aren't the same process, even though they are conceptually related. For example, in Mac OS X Printer Sharing, the UI code showing the list of network printers doesn't want to show local printers that are being shared on the network by this machine, but the code displaying the print dialog user interface is not the same Unix process as the background process advertising those printers. In this case, automatic filtering based on Unix process IDs would fail to provide the desired result.

Ultimately, the only way to meet the needs of all applications is to report the names of advertised services in the serviceRegistered( ) callback and let applications that require some kind of self-filtering implement that filtering, in the way that makes sense for that particular application.

For the most part, though, most applications don't need any kind of self-filtering. If you find yourself thinking that you don't want to discover entities on the same machine, the question to ask is, "Why?" Usually, the answer will be that there's a different way to discover and communicate with entities on the same machine. If that's the case, the question to ask is, "Why?" Why have two different ways of doing the same thing, one for local entities and a different one for remote entities? Sometimes there are valid performance arguments for making local entities a special case, but in most cases, it is just a historical design accident. In most cases, instead of having two different mechanisms for doing roughly the same thing, each with their own bugs, features, and idiosyncrasies, it is smarter to have one mechanism—built on IP—and concentrate on making that IP-based mechanism fully featured, reliable, and efficient.

Pages: 1, 2, 3, 4, 5

Next Pagearrow