Lookup and Discovery


Jini services are found and resolved by a lookup service (LUS). Every Jini community must have at least one lookup service available. The lookup service is the central bootstrapping mechanism for the system, and provides the major point of contact between the system and users of the system. A lookup service maps Java language-defined interfaces that indicate they possess the functionality provided by a service to sets of objects implementing the service.

In contrast to the RMI registry, which uses name-to-object mapping, the Jini lookup service uses interface-to-object mapping, as illustrated in Figure 14.4. This is a powerful concept, because interfaces are inherently extensible.

Figure 14.4. Jini uses interface-to-object lookup as opposed to the name-to-object method provided through RMI.

graphics/14fig04.gif

Jini lookup services are organized into groups. You can create groups with any name that you desire. There is also a default group called the public group. When you start a lookup service, you specify the groups that the lookup service will support. In addition, when you search for a lookup service, you specify the groups you require the lookup service to support.

Groups can be organized along departmental boundaries, such as marketing or advertising departments, or along other dimensions, such as sports services, vacation services, and so on. The organization of lookup services is really system- and application-dependent. However, security considerations often influence if not dictate the configuration.

A service is added to a lookup service, and therefore to a group, by a pair of protocols called discovery and join.

Understanding Discovery Management

The discovery and join protocols enable services to discover, become part of, and advertise supplied services to other members of the Jini community.

The discovery protocol is used to:

  • Announce the presence of a lookup service on a local area network (LAN multicast)

  • Discover one or more lookup services on a local area network (LAN multicast)

  • Establish communication with a specific lookup service over a wide area network (WAN unicast)

The discovery protocol requires support for multicast or restricted-scope broadcast (such as UDP), along with support for reliable unicast delivery in the transport layer (such as TCP). The discovery protocol makes use of the Java platform's object serialization capabilities to exchange information in a platform-independent manner.

The join protocol makes use of the discovery protocol to provide a standard sequence of steps that services should perform when they are starting up and registering themselves with a LUS. The join protocol is used to register a service and advertise its functionality in all lookup services of interest.

The discovery protocol supports multicast and unicast messaging.

Multicast and Unicast Messaging

Jini uses both multicast and unicast messaging to support the discovery protocols. Unicast messaging is used when the location of the lookup service is known. For instance, the TCP/IP address of the lookup service is known to the querying entity. Multicast messaging is used when the lookup service must be found or discovered. It is more dynamic and typical of P2P discovery.

Multicast Discovery Protocol

Multicast addresses are in the 224 239 range. Jini has reserved the following two multicast addresses for its use:

  • 224.0.1.84 jini-announcement

  • 224.0.1.85 jini-request

How do lookup services bootstrap to the community and use these multicast addresses?

Jini uses the jini-announcement address (224.0.1.84) and the multicast announcement protocol for lookup services to advertise their existence. When a new lookup service is started, it announces its availability to potential clients. Also, if a network failure occurs, this protocol can be used by the lookup service to make clients aware that it is available after the network has been restored.

The multicast announcement protocol follows these steps:

  1. Interested entities on the network listen for multicast announcements from lookup services. If an announcement of interest arrives at such an entity, the entity uses the unicast discovery protocol to contact the specific lookup service.

  2. Lookup services take part in the unicast discovery protocol (see Figure 14.5) by sending multicast announcements of their existence at regular intervals.

    Figure 14.5. The multicast announcement protocol provides a heartbeat broadcast for lookup services to advertise their presence.

    graphics/14fig05.gif

So, the lookup service can broadcast its existence like a "heartbeat" on the network, and interested clients listen for heartbeats.

It is assumed that lookup services are relatively stable in the community. In other words, lookup services should not be entering and leaving the community as frequently as other services or clients.

Let's consider how other services that become active by broadcasting their existence.

Multicast request datagrams are encoded as a sequence of bytes, using the data and object serialization facilities of the Java programming language. A multicast discovery request packet must be 512 bytes in size or less to fit into a single UDP datagram.

Table 14.1 illustrates the contents of a multicast request packet body.

Table 14.1. Multicast Request Datagrams
Multicast Request Packet Description Count (Occurrences) Serialized Type
Protocol version 1 Int
The port to respond to 1 Int
The count of lookups 1 Int
An array of lookups that have already responded Variable ServiceID
The count of groups 1 Int
An array of groups of interest Variable String

In Figure 14.6, a service becomes active and broadcasts a request to find nearby LUSs of interest: for example, groups that it wants to join. Each LUS discovered will return a MarshalledObject that implements the net.jini.core.lookup.ServiceRegistrar interface. This will enable the service to register with the LUS, and in effect, join and advertise its functionality to the Jini community.

Figure 14.6. When a service becomes active, it multicasts a request to find nearby LUSs.

graphics/14fig06.gif

The multicast protocol is a key to enabling the dynamics of a Jini community. It permits the community to grow and form dynamically based on network "closeness," versus traditional programming lookups or network IP configurations. The using entity does not have prior knowledge of the request recipient.

Unicast Discovery Protocol

Unlike the multicast protocol, the unicast discovery protocol is used to communicate with a specific service or LUS. This is useful for dealing with nonlocal communities, and for using services in specific communities over a long period of time. This protocol is able to bypass the constraints of the multicast radius.

The unicast discovery protocol works as follows:

  1. The LUS listens for incoming connections, and when a client makes a connection, responds with a MarshalledObject that implements the net.jini.core.lookup.ServiceRegistrar interface. This is also what occurred as a result of the preceding multicast request.

  2. An entity that wants to contact a particular LUS uses known host and port information to establish a connection. The client-entity sends a unicast discovery request and listens for a MarshalledObject in response.

The LUS is crucial to the establishment of a Jini community. The discovery protocol provides both a dynamic multicast and a directed unicast approach to finding LUSs.

Many of the requirements of P2P discovery can be satisfied by Jini discovery and join protocols. Of course, the major constraint is that the systems have to publish a Java interface. Systems that are dependent on language-neutral protocols such as XML will probably bridge to Jini services through a Jini gateway.

The Jini Lookup Service

The Jini reference implementation provides a LUS named reggie. reggie is an activatable RMI service. When reggie is started, it will register with rmid and multicast its announcement packets on the network.

Be sure that rmid is running when you start reggie, or an exception will be thrown.

The following script starts reggie on Windows:

 @echo off  set HTTP_ADDRESS=172.16.1.3:8080 set JINI_HOME=c:\files\jini1_2 set HTTP_HOME=c:\services\logs set GROUPS=public echo P2P Unleashed echo  - echo Jini install directory          %JINI_HOME% echo Web server                      %HTTP_ADDRESS% echo Default group                   %GROUPS% echo  - java -jar -Djava.security.policy=%JINI_HOME%\policy\policy.all %JINI_HOME%\lib\reggie.jar  graphics/ccc.gifhttp://%HTTP_ADDRESS%/reggie-dl.jar %JINI_HOME%\policy\policy.all %HTTP_HOME%\services\ graphics/ccc.giflogs\reggie_log %GROUPS% 

The following properties are set:

  • JINI_HOME\lib Executable JAR file path to the Jini reggie.jar

  • JINI_HOME\policy Security policy file path to the policy.all file

  • HTTP_ADDRESS Codebase path to the downloadable reggie-dl JAR file

  • HTTP_HOME\services\logs Log directory path to reggie persistent state

  • Groups Groups this LUS will support

reggie will create a log file under the directory specified by the log directory property. You might want to verify the existence of that file after you have started the service.

After you have started reggie, you should be able to start the lookup browser, which permits you to browse the services that are currently running in your Jini community. At this point, the only service running will be the LUS itself.

The following script can be used to start the service lookup browser on Windows:

 @echo off  set HTTP_ADDRESS=172.16.1.3:8080 set JINI_HOME=c:\files\jini1_2 set HTTP_HOME=c:\services\logs set GROUPS=public echo P2P Unleashed echo  - echo Jini install directory     %JINI_HOME% echo Web server                 %HTTP_ADDRESS% echo Default group              %GROUPS% echo  - echo Starting the Service Browser java -cp %JINI_HOME%\lib\jini-examples.jar -Djava.security.policy=%JINI_HOME%\example\ graphics/ccc.gifbrowser\policy -Djava.rmi.server.codebase=http://%HTTP_ADDRESS%/jini-examples-dl.jar com. graphics/ccc.gifsun.jini.example.browser.Browser  admin 

The lookup browser can be used to view your LUS environment. You can display the services that are currently registered at each LUS that is active. You can use the browser as an aid in determining what services are active, how many LUSs are running, attributes of a service, and so on.

The administration of services is possible by highlighting the service and right-clicking on the service item. Selecting Service Admin displays the administration. You can change groups, locators, and remove the service from the LUS and rmid.

At this point, reggie should be sending announcement packets out on the network. By default, reggie sends an announcement packet every two minutes.

Jini Finding and Binding

Now that you have the LUS in place and have been able to verify the heartbeat of reggie, let's discuss the join and lookup process. These are the processes that implement the find and bind semantics of service-oriented architectures. These processes enable you to provide and access services in the Jini community.

As mentioned, the join protocol makes use of the discovery protocols to provide a standard sequence of steps that services should perform when they are starting up and registering themselves with a LUS.

The discovery protocol is used to discover LUSs, and for LUSs to return a proxy object that implements the ServiceRegistrar interface, shown in Listing 14.2.

Listing 14.2 ServiceRegistrar Interface
 public interface ServiceRegistrar {    ServiceRegistration register(ServiceItem item, long leaseDuration)         throws RemoteException;    ServiceMatches lookup(ServiceTemplate tmpl, int maxMatches)         throws RemoteException;    int TRANSITION_MATCH_NOMATCH = 1 << 0;    int TRANSITION_NOMATCH_MATCH = 1 << 1;    int TRANSITION_MATCH_MATCH = 1 << 2;    EventRegistration notify(ServiceTemplate tmpl, int transitions,                             RemoteEventListener listener,                             MarshalledObject handback,                             long leaseDuration) throws RemoteException;    Class[] getEntryClasses(ServiceTemplate tmpl) throws RemoteException;    Object[] getFieldValues(ServiceTemplate tmpl, int setIndex, String field)         throws NoSuchFieldException, RemoteException;    Class[] getServiceTypes(ServiceTemplate tmpl, String prefix)         throws RemoteException;    ServiceID getServiceID();    LookupLocator getLocator() throws RemoteException;    String[] getGroups() throws RemoteException; } 
Service Registration

After the LUS has been discovered and returns the ServiceRegistrar proxy, you invoke the register method to register your service. You will do this with all LUSs that are of interest to you (that is, LUSs that support specific groups):

 ServiceRegistration register(ServiceItem item, long leaseDuration)     throws RemoteException; 

The register method takes two parameters:

  • A ServiceItem

  • A lease duration

The ServiceItem includes a service ID that must be globally unique and is initially generated by the LUS. In addition, a ServiceItem contains a set of attributes that are used to augment the service definition, and a proxy object that's used to communicate with the service.

The lease parameter is used to indicate a time-based duration to keep the service active. We will discuss leasing in general later in this chapter. For now, just realize that every service has a lease associated with it that controls how long the service will remain active in the LUS. The lease duration is expressed in milliseconds.

The register method of the ServiceRegistrar interface returns an object that implements the ServiceRegistration interface (see Listing 14.3). This interface enables the service to retrieve its service ID and lookup registration and to manage its service attributes. You will need the service ID to register with other LUSs to ensure that your identification is consistent and unique across the network.

Each implementation of the LUS exports proxy objects that implement the ServiceRegistration interface.

Listing 14.3 ServiceRegistration Interface
 public interface ServiceRegistration {    ServiceID getServiceID();    Lease getLease();    void addAttributes(Entry[] attrSets)         throws UnknownLeaseException, RemoteException;    void modifyAttributes(Entry[] attrSetTemplates, Entry[] attrSets)         throws UnknownLeaseException, RemoteException;    void setAttributes(Entry[] attrSets)         throws UnknownLeaseException, RemoteException; } 

After a service has registered with the LUS, it has joined the community. Clients can now find the service by performing a lookup on any LUS with which the service has registered.

The JoinManager Class

Services will often use the net.jini.lookup.JoinManager class to discover and join a Jini community. This helper class implements the discovery and join protocols, making it easier for services to manage the "good Jini citizen" process requirements.

There are five parameters required to construct a JoinManager:

  • java.lang.Object is the object/service to be registered. This is the proxy object that implements the service interface. For example, the Jini LUS registers an object that implements the ServiceRegistrar interface.

  • net.jini.core.entry.Entry[] is an array of attributes associated with the service. Entry objects are used to augment the service definition, so users can perform specific searches for services. For example, an Entry might be used to specify a location of a service, or a specific vendor's implementation. In that case, only services matching that criteria would be returned from lookup requests.

  • net.jini.lookup.ServiceIDListener or net.jini.core.lookup.ServiceID is the unique service identifier assigned to the service. If the service has already received the identifier from the LUS, it uses the ID as a parameter; otherwise it supplies an ID listener to receive the ID. The ID listener stores the ID for subsequent registration(s). Where and how the ID is stored depends on the service. Typically, the service will log this information to safe-storage upon receipt, and retrieve the ID during subsequent starts from a known location.

  • net.jini.discovery.DiscoveryManagement is an interface that defines the discovery operations as outlined in the previous section. The LookupDiscovery class implements this interface. In addition, the LookupLocatorDiscovery class implements this interface using the unicast, rather than the multicast, discovery protocol.

  • net.jini.lease.LeaseRenewalManager is the lease renewal manager responsible for renewing your lease with the LUS. This is how resources are managed across the Jini network, based on time-allocation management. Leasing is often referred to as providing the "self-healing" properties of a Jini network.

The AgentService Example

Let's convert the RMI AgentService into a Jini service to demonstrate the changes required (see Listing 14.4). This will serve to reinforce our discussion and highlight the extensions Jini provides on top of RMI.

Listing 14.4 JiniAgentService Class
 // These classes form the basis of the RMI support import java.rmi.RemoteException; import java.rmi.RMISecurityManager; import java.rmi.server.UnicastRemoteObject; import java.io.IOException; // We will require a number of Jini classes to support our Jini service import net.jini.core.discovery.LookupLocator; import net.jini.core.lookup.ServiceID; import net.jini.discovery.DiscoveryGroupManagement; import net.jini.discovery.LookupDiscoveryManager; import net.jini.lookup.JoinManager; import net.jini.lookup.ServiceIDListener; // // Extend the UnicastRemoteObject to create a non-activatable Jini service. // public class JiniAgentService extends UnicastRemoteObject    implements Agent {    // The LookupDiscoveryManager you are using to find (request) LUSs    private LookupDiscoveryManager lookupDiscMgr;    // The lookup locator array that contains specific LUSs    private LookupLocator locators[];    // The groups array that contains specific groups, no groups, or all groups    private String groups[] = DiscoveryGroupManagement.ALL_GROUPS;    // The manager for joining LUSs    private JoinManager joiner = null;    // ServiceID returned from the lookup registration process    private ServiceID serviceID = null; // // Add an init method to your constructor to do initialization specific to Jini. //    public JiniAgentService() throws IOException {    // call the UnicastRemoteObject to export the object        super();    // Initialize the Jini service        init();    } // // // Create a discovery manager by passing parameters // to discover all LUSs within the multicast radius. // Do this by specifying ALL_GROUPS and null parameters. // //    private void init() throws IOException {       try {         lookupDiscMgr = new LookupDiscoveryManager(groups, locators, null);       } catch (IOException e) {         System.err.println("Problem starting discovery");         e.printStackTrace();              throw new IOException("Problem starting discovery:" +                   e.getLocalizedMessage());        }        /* Register this service with any configured LUSs */        if (serviceID == null) {        // First instance ... need service id           joiner = new JoinManager(           this,                  // service object           null,                  // service attributes   none at this time           new SrvcIDListener(),  // ServiceIDListener  - internal helper class           lookupDiscMgr,         // DiscoveryManagement - default           null);                 // LeaseRenewalManager - default        } else {                  // Rejoin with (recovered) state information           joiner = new JoinManager(           this,                  // service object           null,                  // service attributes   none at this time           serviceID,             // Service ID   already have an ID           lookupDiscMgr,         // DiscoveryManagement - default           null);                 // LeaseRenewalManager - default        }    }     // implementation of the Agent interface    public String talk() {         return "P2P is very cool";    } // // // The main method does not differ significantly from prior examples // however, you no longer need to bind to the RMIregistry because you // now will use the LUS (ServiceRegistrar) to return a reference to the // remote object. // //    public static void main(String[] args) {    // set the security manager        if (System.getSecurityManager() == null) {            System.setSecurityManager(new RMISecurityManager());        }        try {             Agent agent = new JiniAgentService();             System.out.println("JiniAgentService bound");             Thread.currentThread().join();        } catch (Exception e) {             System.err.println("JiniAgentService exception: " +  e.getMessage());             e.printStackTrace();        }    }    // Utility method for setting the service's ID obtained from    // the lookup registration process.    private void setServiceID(ServiceID id) {       serviceID = id;    } // // // The SrvcIDListener inner class handles the callback of the service ID // assignment from the JoinManager. // private class SrvcIDListener implements ServiceIDListener    {       public SrvcIDListener() {           super();       }       /**       * The JoinManager will invoke this method when it receives a       * valid ServiceID from a LUS.       */         public void serviceIDNotify(ServiceID id) {            // Set the ID            setServiceID(id);            System.out.println("Received service id");        }    } } 

There are a number of important aspects of being a Jini service that have been omitted. The preceding service only demonstrates the core requirements to registration. For example, we did not persist the service ID, so restarts of the service would pass a listener, and thus the LUS would generate a new unique ID instead of reusing the existing ID. Any client who had saved the original ID would not be able to find the service again.

A Simple Jini-P2P Client

Let's now build a simple client to test the service, shown in Listing 14.5.

Listing 14.5 JiniUserAgent Class
 import java.rmi.*; import java.io.IOException; import java.util.Vector; // Jini classes used to find and bind to a Jini service import net.jini.core.lookup.ServiceItem; import net.jini.core.lookup.ServiceTemplate; import net.jini.discovery.DiscoveryEvent; import net.jini.discovery.DiscoveryListener; import net.jini.discovery.DiscoveryManagement; import net.jini.discovery.LookupDiscovery; import net.jini.core.lookup.ServiceMatches; import net.jini.core.lookup.ServiceRegistrar; public class JiniUserAgent  {    public JiniUserAgent() {}    public static void main(String args[]) {         // set security manager if not set         if (System.getSecurityManager() == null) {             System.setSecurityManager(new RMISecurityManager());         }         try {             JiniUserAgent userAgent = new JiniUserAgent();             // create a service template to represent the required service             ServiceTemplate template;             // We pass a class that identifies an interface rather than a name             Class[] types = {  Class.forName("Agent")  };             // We do not constrain our service search other than interface type             // null indicates match anything on service ID and attributes             template = new ServiceTemplate(null, types, null);             // call the lookup passing our template             Agent agent = (Agent)userAgent.lookup(template);             // Now call the JiniAgentService             System.out.println(agent.talk());         } catch (Exception e) {             System.err.println("Agent exception: " +                  e.getMessage());                  e.printStackTrace();         }    }    // method to find a service given a service template    private Object lookup(ServiceTemplate template) throws IOException    {        // Your internal class ServiceListener does the actual work        ServiceListener serviceListener = new ServiceListener(template);        return serviceListener.lookup();    } } private class ServiceListener implements DiscoveryListener {    // Our discovery management support    private DiscoveryManagement mgt;    // A vector to hold all services discovered    private Vector services = new Vector();    // A template used to indicate the service requested by the client    private ServiceTemplate template;    public ServiceListener(ServiceTemplate template) throws IOException  {        super();        this.template = template;        // sequence to ensure all lookups are heard        mgt = new LookupDiscovery(LookupDiscovery.NO_GROUPS);        // we implement the listener interface        mgt.addDiscoveryListener(this);        // now set all groups        ((LookupDiscovery)mgt).setGroups(LookupDiscovery.ALL_GROUPS);    }    // client will wait until matching service is discovered    public synchronized Object lookup() {         while(services.size() == 0) {            try {               wait();            } catch (InterruptedException ex) {}         }          // just return the first match found           return ((ServiceItem)services.elementAt(0)).service;    }    public synchronized void discovered(DiscoveryEvent de) {        System.out.println("Discovered LUS: ");        ServiceRegistrar[] registrars = de.getRegistrars();        for(int i=0; i < registrars.length; i++) {           try {             System.out.println("URL:    " + registrars[i].getLocator(). toString());             System.out.println("ID:     " + registrars[i].getServiceID());             String groups[] = registrars[i].getGroups();             // simply display the first group returned             System.out.println("GROUPS: " + groups[0]);             ServiceMatches sm = registrars[i].lookup(template, Integer.MAX_VALUE);             System.out.println("Matching services found  : " + sm.totalMatches);             System.out.println("");             for(int j=0; j < sm.items.length; j++) {                // Process each ServiceItem                if(sm.items[j].service != null) {                   services.addElement(sm.items[j]);                } else {                   System.out.println("Service item null" + sm.items[j].service);                }             }             // notify the client a matching service has been found             System.out.println("Notifying...");             notifyAll();          } catch(Exception e) { e.printStackTrace(); }       }    }    // we ignore discarded LUS objects in this example    public void discarded(DiscoveryEvent de) {} } 

You create a ServiceTemplate to indicate the service you want the discovery process to find (Agent). A ServiceTemplate is used to match service items that are registered in the LUS. The template contains the service ID (if known), an array of java.lang.Class objects of supported interfaces, and the attributes to limit the returned services. Null values are supplied for the service ID and attribute parameters, which instructs the LUS to match anything. You ask for any object that implements the Agent interface by setting the java.lang.Class array parameter to the Agent class name.

The ServiceListener implements the DiscoveryListener interface. The internal class ServiceListener is a helper class for the client, and it is passed the template that the client wants to find.

The DiscoveryListener interface defines two methods, discovered and discarded:

 public interface DiscoveryListener extends java.util.EventListener     // Called when one or more LUS registrars has been discarded.    void discarded(DiscoveryEvent e);    // Called when one or more LUS registrars has been    // discovered.    void discovered(DiscoveryEvent e); } 

The discovered method receives a DiscoveryEvent, which is passed by the LookupDiscovery object when it discovers one or more LUSs. The getRegistrars method of the DiscoveryEvent returns an array containing the ServiceRegistrar for each newly discovered LUS.

The lookup method of the ServiceRegistrar is used to return an array of ServiceMatches objects. The service template containing the service Agent interface is used as a parameter to request only services implementing the Agent interface be returned.

The ServiceMatches object returned by the LUS will contain an array of ServiceItem objects and a count of the number returned. The ServiceItem is added to the Vector of services, which in this simple example is used to trigger the notification to the suspended client that an object implementing the Agent interface has been found.



JavaT P2P Unleashed
JavaT P2P Unleashed
ISBN: N/A
EAN: N/A
Year: 2002
Pages: 209

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net