You are on page 1of 10

Dynamic Service Discovery with Java - Developer.com http://www.developer.com/print.

php/3728576

IT Professionals Developers Solutions eBook Library Webopedia

http://www.developer.com//services/article.php/3728576/Dynamic-Service-Discovery-with-Java.htm

Back to article

Dynamic Service Discovery with Java

February 19, 2008

Overview
Writing a network service application in Java is usually a straightforward matter: Open a ServerSocket on some specified
port on the machine, listen for requests, and process them when they arrive. Likewise, the client side is fairly
straightforward: Open a Socket connection to a specified host and port, then start communicating with the service on the
other end. But sometimes, the need to specify a port number to run the service on, and the need to know a valid hostname
(or IP address) and port on the client, adds a bit more "required knowledge" to the utility than you want end users to worry
about (or that you want to support later). If you were to search for a way to eliminate the technical mumbo jumbo from the
end user's point of view and also eliminate the need to fix the hostname (or IP address) and port number ahead of time,
you would end up in the land of dynamic service discovery.

When all the mystery is stripped away, dynamic service discovery is basically a technique where a client application makes
an open query on the entire local network, saying "I'm looking for service XYZ ... anyone here provide that? If so, where
are you?" The service itself listens for such questions and responds with something like "I provide service XYZ, and I am
running at 10.1.2.3 on port 9999." Now, if you have already done a little research in this area, you may be aware of Apple's
work in this area. Their Bonjour stack basically provides a system-wide discovery service that, among many other things,
allows two people both running the iTunes application to immediately see and play from each other's music shares just by
firing up the application. Adding a simple version of this magic to your own Java-based services is really rather simple.

About this Article


I will first talk, perhaps at excessive length, about the conceptual underpinnings of a good service discovery layer. If you are
sure you are completely familiar with how this works, you might skip the concept section and go straight to the details
section. I assume the reader is comfortable enough with basic Java networking. As such, I will not call out each line of code
or even explain the differences between a TCP packet compared to a UDP packet. I also tend to use lots of Java5-isms in
my code (generics, enhanced-for, and so forth), so do be warned if you are still relegated to an older compiler.

A Real Life Example


I work in a small software shop. I once needed to provide fellow developers with a little service utility to make their (and
my) life easier. The client side of involved a little Swing app that ran on their desktop and interacted with the a network
service. However, given the rather dynamic nature of our working environment, I didn't want to have to distribute an update
for the client Swing app just because I had to move the service to a different host machine. And, although the connections
settings could be passed in on the command line, this needlessly burdens the end users to remember the argument list
format when they already have enough to keep track of. I also knew we might eventually need to run multiple such services,
and I did not want to personally manage a list of machines and port numbers just to keep everyone updated as to what the
current connection options were. So, this was all handled via service discovery: When the end user started the Swing app
client, he was given a list of all currently available options (with descriptive human words, not geeky connection detail lingo)
to choose from. It ended up being so seamless that I doubt many of them even appreciated the effort. (And that was fine
with me.)

Concepts
By way of a crude sequence diagram, service discovery works like this:

1 di 10 04/03/2011 14.20
Dynamic Service Discovery with Java - Developer.com http://www.developer.com/print.php/3728576

As I stated earlier, the gimmick is that the clients ask the machines on the local network to reply whether they provide some
service the client is looking for. This generally takes the rather simple form of a multicast datagram packet. (A multicast
datagram packet, rather than going to just one specific host address, instead goes to all machines on the local network that
are listening on the same channel address. It is somewhat analogous to the concept of television or radio.) The exact
"body" of that query packet is largely up to you to devise, but to be effective, it must include that it is (1) a request for (2)
some named (or otherwise identifiable) service. This does introduce a condition that a "service name" needs to be
introduced. This can take any practical form: webserver, echoservice, MySlickUtility, and so on. The person who writes the
service side will pick something appropriate. The person who writes the client side (who may or may not the same person)
just needs to know what service name to ask for in their query packet.

The server receives multicast datagram packets from some particular channel address, analyzes the body of those packets
to see whether they should reply to it, and then replies back (if appropriate) with its specific connection information. The
body of the reply packet is again ultimately up to you, but should include that it is (1) a reply for (2) the named service in
question, and (3) include any connection details the client needs, such as hostname and port number. You also may want to
include a friendly, human label for the service instance itself in the event the human user needs to be presented with a less
technical label for the service.

Back on the client, after making the query on the network, it then waits a little while (a second or two is probably sufficient)
to get any reply packets back. It should check the reply packets to make sure they are for the service name it asked for.
Then, the connection details for the services that replied can be collected. In the case of multiple replies, you either can
select the first one, select one at random, or select based on some information in the reply packets (such as a load
balancing metric or priority number). Perhaps the best option is to give the user a list and let them decide which one to use.

A further refinement of the preceding discussion attempts to address the "unreliable" part of UDP. During the window in
which a client is waiting and collecting replies for its query, it might issue the query a few more times. Although this can be
a good idea on congested networks (from the standpoint of your application, albeit not from the standpoint of the network
itself) it is also a good idea to add "known answer suppression" into the mix: If a client already has received a reply from
one server, but is sending a followup query, it includes a list of servers it has already processed. This allows the server to
silently ignore the followup request, which would otherwise just add more (and needless, in this case) traffic. The code I am
providing with this article does not implement a known answer suppression technique. However, it is not a technically
difficult thing to add in, should the reader wish to do so.

To be clear, there are two separate labels (not counting the optional, friendly, human description). First is the "service
name" being something fairly generic, such as "iTunes". Second is the "service instance name" being something absolutely
unique to the local network, such as "Rob's Music." Only the background code needs to work with the service name when it
makes a request or sends back a reply. The service name is like a generic identifier for what kind or category of service is
provided or searched for. But, if the user needs to be informed or involved in any way, the service instance name (or, as
mentioned, a possibly better description) should be used. Likewise, if you log anything, you'll want to use the service
instance name to identify which exact instance of that service is being used. It is a subtle point, but understanding the
distinction is vital.

Something for Nothing?


It is rightly said you cannot get something for nothing. Those of you who have worked with multicast network services know
that there isn't just one magic channel that all the multicast traffic goes out on—you still have to co-ordinate the server and
client to use the same multicast address and port. Because this still must be kept in sync, it is indeed worth asking whether
you have gained anything or merely moved the mountain (slightly). Here's where the idea pays off: Even though different
service applications will be running on different ports, the service discovery code only needs to run on one agreed-upon

2 di 10 04/03/2011 14.20
Dynamic Service Discovery with Java - Developer.com http://www.developer.com/print.php/3728576

multicast channel. In other words, Although you have shifted the mountain slightly, you only really need to climb up it one
time. (Furthermore, the same service cannot run on the same port inside the same machine... however, any number of
programs can join the same multicast channel. This lets each service instance run on any port available, again without
needing to coordinate what those port numbers actually are.)

To be fair, maximum pay-off only comes if there is a system-wide service running in the machine that all applications
(server- and client-side) can interact with. Each server-side application talks to this service and registers itself when it
starts up, handing over its instance name and connection parameters (and unregistering when it stops running). The
requests that come in over the network are actually responded to via this system-global service. (This is what Bonjour and
various work-alikes are.) Your own project might not be of sufficient importance to install and run your own operating
system service. (Then again, maybe it is, but my own project wasn't.) However, if you trade down a little bit, each service
instance can easily run its own query responder on an agreed-upon multicast channel. This still allows for the actual service
itself to run on whichever dynamic port is available (or whichever one suits the whims of whomever installs and runs it) while
allowing for clients to find the service without any advanced knowledge. You really do seem to get something for nothing
out of the deal.

As is usually the case when dealing with connectionless datagram packets in general, the devil is in the (implementation)
details. If a service runs its own responder, that will definitely need to exist in its own thread. Likewise, the clients need a
little co-ordination to "wait a second or so" when gathering replies (if any) and possibly add a little prompt to involve the
user if multiple replies do come back. I will not be discussing how to handle the user interface side of the client. (However,
you might want to refer to another article of mine that discusses coordination of tasks in a Swing application.)

Details
By way of a reminder, this "automagic" service discovery technique employs the use of a multicast address. The address
range 224.0.0.0 through 239.255.255.255 (or alternately, 224.0.0.0/4) is a special range of IP addresses set aside for
multicast use. They cannot be assigned to a specific host. (This being said, these addresses still have ports.) Furthermore,
a handful of addresses in the 224.0.0.x address space are already reserved for specific use. You can get some good
information and links by reading this wikipedia entry. You should also refer to the Java API documentation for the
MulticastSocket class. (The Socket and ServerSocket classes are probably more familiar, but those are not used for
multicast traffic.) Also, you might want to refer to the Java API documentation for the DatagramPacket class, which forms
the transmission container for UDP data.

Just to give a brief example, multicast networking code looks as follows:

...
MulticastSocket socket = new MulticastSocket(9999);
InetAddress address = InetAddress.getByName(230.0.0.1);
socket.joinGroup(address);
...
DatagramPacket inboundPacket, outboundPacket;
...
socket.receive(inboundPacket);
...
socket.send(outboundPacket);
...

The sample code I will be working with uses the 230.0.0.1 address and the 4321 port. This is a rather arbitrary choice, and
can easily be changed as needed. Also, instead of giving a step-by-step explanation of my particular implementation for the
server-side responder code and client-side browser code, I will rather be showing a simple "time server" application and
client, and then the steps needed to connect both sides to the provided implementation. It is left as an exercise to the
reader to poke around in source code, but the biggest pieces involve rather vanilla networking and threading code.

Time Server Example: Starting Point


Just to keep the demo simple, consider you want to write a TCP-based network service that, when connected to,
immediately emits the machine's current time to the client and then disconnects. This is rather elementary networking code
on both sides. First, the server:

3 di 10 04/03/2011 14.20
Dynamic Service Discovery with Java - Developer.com http://www.developer.com/print.php/3728576

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;

public class TimeServer {


public static void main(String[] args) {

ServerSocket serverSocket = null;

/* section one */
try {
serverSocket = new ServerSocket();
serverSocket.bind(
new InetSocketAddress(
InetAddress.getLocalHost(),
9999));
}
catch (IOException ioe) {
System.err.println(
"Could not bind a server socket to port 9999: "+ioe);
System.exit(1);
}

/* section two */
System.out.println("Server is now taking connections...");
while (true) {
try {
Socket socket = serverSocket.accept();
System.out.println("Connection from: "+
socket.getInetAddress());
OutputStreamWriter writer = new OutputStreamWriter(
socket.getOutputStream());
writer.write(new Date().toString()+"\r\n");
writer.flush();
socket.close();
}
catch (IOException ie) {
System.err.println("Exception: "+ie);
}
}

And now the client:

4 di 10 04/03/2011 14.20
Dynamic Service Discovery with Java - Developer.com http://www.developer.com/print.php/3728576

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.Socket;

public class TimeClient {


public static void main(String[] args) {
try {
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
InputStreamReader reader =
new InputStreamReader(socket.getInputStream());
BufferedReader bufferedReader = new BufferedReader(reader);
String line = bufferedReader.readLine();
System.out.println(line);
socket.close();
}
catch (IOException ie) {
System.err.println("Exception: "+ie);
System.exit(1);
}

System.out.println("\nThat's all, folks.");


System.exit(0);
}
}

This is a completely straightforward, no-frills TCP network server and client application. The important point to note is the
hard-coded port, 9999, in both the server and the client, and the hard-coded server address (that for the localhost, in this
case) in the client. Although these values can be put into property or configuration files, they are still effectively static, fixed
values. If the server, running on one machine, changes port numbers, but the client isn't aware of this, it will just look like the
server is unavailable.

But, with just a little addition using a few classes (that you can download and experiment with on your own), making things
be completely dynamic is fairly simple. First is the new server code:

5 di 10 04/03/2011 14.20
Dynamic Service Discovery with Java - Developer.com http://www.developer.com/print.php/3728576

public static void main(String[] args) {

ServerSocket serverSocket = null;


String SERVICE_NAME = "timeDemo";
String INSTANCE_NAME = "Time_Server_1";

try {
serverSocket = new ServerSocket();
serverSocket.bind(
new InetSocketAddress(InetAddress.getLocalHost(),
0)); /*bind to any available port number*/
}
catch (IOException ioe) {
System.err.println(
"Could not bind a server socket to a free port: "+ioe);
System.exit(1);
}

/* Create a descriptor for the service you are providing. */


ServiceDescription descriptor = new ServiceDescription();
descriptor.setAddress(serverSocket.getInetAddress());
descriptor.setPort(serverSocket.getLocalPort());
descriptor.setInstanceName(INSTANCE_NAME);
System.out.println("Service details: "+descriptor);

/* Read special note for code you should add here */

/*
* Set up a responder and give it the descriptor (above)
* we want to publish. Start the responder, which
* works in its own thread.
*/
ServiceResponder responder =
new ServiceResponder(SERVICE_NAME);
responder.setDescriptor(descriptor);
responder.startResponder();

/* Back to the usual routine of servicing requests */


System.out.println(
"Responder listening. Now taking connections...");
while (true) {
try {
Socket socket = serverSocket.accept();
System.out.println(
"Connection from: "+socket.getInetAddress());
OutputStreamWriter writer =
new OutputStreamWriter(socket.getOutputStream());
writer.write(new Date().toString()+"\r\n");
writer.flush();
socket.close();
}
catch (IOException ie) {
System.err.println("Exception: "+ie);
}
}

The important aspects of this change are that, once the service knows the address and port it is running on, a "descriptor"
object is created. (This object is just a plain data container.) Then, a "responder" is created and passed the descriptor
object for the service. This responder forms the server-side half of the service discovery code. Running in its own thread, it
binds to an internally configured multicast channel, listens for lookup queries, and sends back a reply containing the
information in this descriptor. That's about it. The rest of the Time Server runs exactly as it did before: listening for
connections on the port it is running on, and pushing across the current time.

The comment in the modified server code mentions a special note. At this location, you should really arrange for the server

6 di 10 04/03/2011 14.20
Dynamic Service Discovery with Java - Developer.com http://www.developer.com/print.php/3728576

to make its own lookup query (you'll see how in a moment) to check that the instance name it wants to use has not yet
been claimed. (If it has, the instance name should be modified, and the check repeated as needed.) I've omitted this partly
for clarity, and partly to not duplicate what you will see in the client code.

Now, you have the code modifications for the client.

7 di 10 04/03/2011 14.20
Dynamic Service Discovery with Java - Developer.com http://www.developer.com/print.php/3728576

public class TestClient implements ServiceBrowserListener {

public static final String SERVICE_NAME = "timeDemo";

public static void main(String[] args) {


new TestClient();
}

ServiceBrowser browser;
Vector<ServiceDescription> descriptors;

TestClient() {
descriptors = new Vector<ServiceDescription>();

/* first browse for any 'timeDemo' instance */


browser = new ServiceBrowser();
browser.addServiceBrowserListener(this);
browser.setServiceName(SERVICE_NAME);
browser.startListener();
browser.startLookup();
System.out.println(
"Browser started. Will search for 2 secs.");
try {
Thread.sleep(2000);
}
catch (InterruptedException ie) {
// ignore
}
browser.stopLookup();
browser.stopListener();

/* now if the browser found any matches, we'll


* print out the complete list, but only connect
* to the first one.
*/
if (descriptors.size()>0) {

System.out.println("\n---TIME SERVERS---");
for (ServiceDescription descriptor : descriptors) {
System.out.println(descriptor.toString());
}

System.out.println("\n---FIRST SERVER'S TIME IS---");


ServiceDescription descriptor = descriptors.get(0);
try {
Socket socket = new Socket(descriptor.getAddress(),
descriptor.getPort());
InputStreamReader reader =
new InputStreamReader(socket.getInputStream());
BufferedReader bufferedReader =
new BufferedReader(reader);
String line = bufferedReader.readLine();
System.out.println(line);
socket.close();
}
catch (IOException ie) {
System.err.println("Exception: "+ie);
System.exit(1);
}
}
else {
System.out.println("\n---NO TIME SERVERS FOUND---");
}

System.out.println("\nThat's all folks.");


System.exit(0);
}

8 di 10 04/03/2011 14.20
Dynamic Service Discovery with Java - Developer.com http://www.developer.com/print.php/3728576

The structure is a little bit different this time just to satisfy needing an instance method to implement the
ServiceBrowserListener interface. (However, you could write an inner/anonymous implementation and keep everything
entirely in the static 'main' method, if you want.) Before connecting, the client first needs to run the discovery browser to
poll the network for available services. After letting the browser run for a little while, the browser is stopped, and the vector
of responses is displayed. (The vector stores the same descriptor objects that the server-side code uses.) The first
response in the vector is used to make a direct connection, just as before.

And, there you have it. You can run as many Time Server instances on the network as you want (just use different instance
names for each). You can run them on any port you want, and you can bring the servers up and down as desired.
Whenever this client runs, it will look for any instance that responds to the browse query.

Conclusion
Adding dynamic service discovery to an application actually can be a fairly simple matter. The browser/responder code only
needs to be written once, and then it can be packaged along with your client and server application components time after
time. (And I've already given you a good, basic starting point for this code.)

Other Directions
If you skipped the Concept section, you should note there are a few extra things that the browser/responder can
incorporate to add a little more robustness and efficiency. One of these is "known answer suppression." This requires a
little variation in the packets the browser sends out to include a list of those instances it already knows about, because the
browse period is likely (and, for practical purposes, should be) long enough to send out a few browse requests. It also
requires a corresponding variation in the responder code to look for you "known answer" tokens in the browse requests so
it can ignore them. Another nice touch is to have the server send out an "available/not-available" announcement when it
starts up or when it shuts down. (Catching a control-C signal can be arranged using the Runtime.addShutdownHook
method.) Clients that watch for these extra announcements can maintain an even more up-to-date list of available servers.
(In a chat application, you would see someone immediately join or leave, for example.) Finally, if you start making a lot of
use of discovery code, and you have a lot of server and client nodes, you might consider a known-answer caching
mechanism (with some extra intelligence to allow these caches to gradually expire, and the like). You even can have client
browsers running in a passive mode to cache responses that were triggered by other browse requests in more active
clients. I'll leave implementation of these ideas to the reader.

Download the Code


You can download the code that accompanies this article here.

About the Author


Rob Lybarger is a software guy (and a degreed aerospace engineer) in the Houston, TX area who has been using Java
for nearly ten years. He has used various versions of Windows, various distributions of Linux, but loves Mac OS X. He likes
exploring new techniques and new approaches to organizing and wiring applications, and loves to let the computer do as
much work for him as possible.

Sitemap | Contact Us

The Network for Technology Professionals

Search:

About Internet.com
Copyright 2011 QuinStreet Inc. All Rights Reserved.

Legal Notices, Licensing, Permissions, Privacy Policy.


Advertise | Newsletters | E-mail Offers

9 di 10 04/03/2011 14.20
Dynamic Service Discovery with Java - Developer.com http://www.developer.com/print.php/3728576

Solutions
Whitepapers and eBooks
MSDN Spotlight for Developers Helpful Cloud Computing Resources
Internet.com Cloud Computing Showcase MORE WHITEPAPERS, EBOOKS, AND ARTICLES
Microsoft TechNet Spotlight

Webcasts
MORE WEBCASTS, PODCASTS, AND VIDEOS

Downloads and eKits


MORE DOWNLOADS, EKITS, AND FREE TRIALS

Tutorials and Demos


Demo: Google Site Search New Security Solutions Using Intel(R) vPro(TM) Technology
Virtual Event: Master Essential Techniques for Leveraging the Cloud All About Botnets
Article: Explore Application Lifecycle Management Tools in Visual Studio 2010 MORE TUTORIALS, DEMOS AND STEP-BY-STEP GUIDES
Internet.com Hot List: Get the Inside Scoop on IT and Developer Products

10 di 10 04/03/2011 14.20

You might also like