Professional Documents
Culture Documents
php/3728576
http://www.developer.com//services/article.php/3728576/Dynamic-Service-Discovery-with-Java.htm
Back to article
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.
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.
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.
...
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.
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;
/* 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);
}
}
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;
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
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);
}
/*
* 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();
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.
7 di 10 04/03/2011 14.20
Dynamic Service Discovery with Java - Developer.com http://www.developer.com/print.php/3728576
ServiceBrowser browser;
Vector<ServiceDescription> descriptors;
TestClient() {
descriptors = new Vector<ServiceDescription>();
System.out.println("\n---TIME SERVERS---");
for (ServiceDescription descriptor : descriptors) {
System.out.println(descriptor.toString());
}
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.
Sitemap | Contact Us
Search:
About Internet.com
Copyright 2011 QuinStreet Inc. All Rights Reserved.
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
10 di 10 04/03/2011 14.20