Jul 1, 1998 1:00 AM PT

Agents on the move

Bolster your client apps by adding agent mobility

Last month we delved into the world of agents. We learned what they are and what problems they solve. We also developed the foundation for a simple agent architecture in Java. I promised that we'd build on that foundation in the coming months, and here I am, making good on that promise.

Recall that agents typically possess one or more of the following characteristics; they can be:

  • Autonomous
  • Adaptive/learning
  • Mobile
  • Persistent
  • Goal oriented
  • Communicative/collaborative
  • Flexible
  • Active/proactive

We've got a lot of ground to cover, so we'd best get started. We're going to start our agent adventure with an up-close look at agent mobility. Agent mobility provides a good starting point because the problems associated with mobile (and potentially untrustworthy) code are well-known, and robust solutions are readily available. In addition, Java (via remote method invocation -- or RMI -- and object serialization) provides prefabricated tools that make the job of moving objects easier.

Location, location, location

To better understand mobile agents and their behavior, we must first look briefly at traditional network architectures. The following figure illustrates the network behavior of a typical client/server application.

The typical client/server application communicates via requests and responses, which require a round trip trek across the network.

A client/server application typically consists of two pieces: a client piece and a server piece. Often, the client and server pieces are on separate machines and they communicate over a common network. When the client needs data or access to resources that the server provides, the client sends a request to the server over the network. The server in turn sends a response to the request. This "handshake" occurs again and again in a traditional client/server architecture. Each request/response requires a complete round trip across the network.

Now compare the client/server architecture I just described to the mobile agent architecture illustrated in the next figure.

In the mobile agent architecture, the client actually migrates to the server to make a request directly, rather than over the network.

Just as in the client/server architecture, there is a client piece and a server piece. The difference lies in how the two communicate. When the client in the mobile agent architecture needs data or access to a resource that the server provides, the client doesn't talk to the server over the network. Instead, the client actually migrates to the server's machine. Once on the server's machine, the client makes its requests of the server directly. When the entire transaction is complete, the mobile agent returns home with the results.

A mobile agent's travel habits

One distinguishing characteristic of a mobile agent architecture is (surprise, surprise) the mobility of the code. However, while this characteristic is necessary, it alone is not sufficient. That's because the idea of moving code and computation to the location of the data and resources is not unique to the mobile agent architecture. In fact, such mobility has been a feature of many commercial databases for some time. In the database world, mobile code goes by the name of a stored procedure. A stored procedure is a piece of client code that executes on the server. In some applications, the client piece of the application can dynamically upload stored procedures to the server. Once there, they can do their work and return the results of their calculations back to the client.

What's the difference then between mobile agents and stored procedures? Stored procedures lack many of the features common to agents such as autonomy and flexibility. A more compelling reason, however, involves the type of mobility. Stored procedures are basically static entities: Once they're uploaded to a server they belong to that server. If a transaction requires data or access to resources spread across several servers, a stored procedure is forced to traverse the network just as the client piece did in the traditional model shown earlier. In other words, a stored procedure cannot migrate from server to server, dragging along the incomplete transaction or calculation with it. A mobile agent can and does.

Client/server architectures vs. mobile agent architectures: Which approach is best and why?

I've provided some interesting background on mobile agents, but nothing (at least yet) that indicates why we might want to move code from the client to the server rather than have the two communicate over a network. As we learned last month, there are three very good reasons.

First, mobile agents solve the client/server network bandwidth problem. By moving a query or transaction from the client to the server, the repetitive request/response handshake is eliminated.

Second, agents reduce design risk. Agents allow decisions about the location of code (client vs. server) to be pushed toward the end of the development effort when more is known about how the application will perform. In fact, the architecture even allows for changes after the system is built and in operation.

Third, agent architectures also solve the problems created by intermittent or unreliable network connections. Agents can be built quite easily that work "off-line" and communicate their results back when the application is "on-line".

Inside a mobile agent

A mobile agent is really a gestalt entity composed of two different pieces. One piece is the code itself, which consists of the instructions that define the behavior of the agent. The second piece is the current state of execution of the agent.

Often, these two pieces are separate. For example, in a typical computer program, the code sits on disk while the executing state sits in RAM. A mobile agent, however, brings the two together. When an agent migrates to a new host, both its code and its state are transferred. Thus, the agent doesn't only remember what to do and how to do it, it remembers what it was doing before as well.

When an agent migrates to a new host, both of these pieces must be captured and packaged. In the discussion that follows and the code examples included with this column, the two pieces are referred to as resource (the code) and data (the state). The resource piece consists of the class files that define the agent. The data piece consists of a snapshot of the agent's state. More technically, it consists of a snapshot of the agent's data structures.

The lifecycle of a mobile agent

Agents have a well-defined lifecycle. The figure below illustrates the four states that make up this lifecycle.

An agent's lifecycle

If the state diagram looks suspiciously similar to the state diagram of an applet, it's because the lifecycles of both applets and agents are similar. In fact, I find that applets are a lot like non-mobile agents. That's a useful consideration to remember.

Here's what happens in each of the four states:

initializePerforms one-time setup activities such as building initial data structures
startStarts its calculations
stopStops its calculations, saves intermediate results, joins all threads, and stops
completePerforms one-time termination activities

The agent on the move

Mobile agents migrate between computing locations called hosts. Agent hosts are responsible for providing the resources agents need to work. Agent hosts are also responsible for handling the mechanics of packaging an agent and moving its resource and data pieces from one host to another. The following figure illustrates how this occurs.

Agent hosts communicate with each other to move the agent along its way

There are two hosts in this figure: the remote host and the local host. The local host is responsible for orchestrating the transfer. The remote host is responsible for initiating the transfer -- possibly at the request of the agent itself. The agent initially resides on the remote host. When the series of communications is complete, the agent will reside on the local host.

The communication goes as follows:

  1. The remote host notifies the local host of its desire to transfer an agent by invoking the remote agent's requestToSend() method.

  2. The local host marks the beginning of the transfer by invoking the remote host's beginTransfer() method. The remote host in turn invokes the agent's stop() method. When the stop() method returns, the agent is ready for transfer.

  3. The local host gets the resource piece of the agent by invoking the remote host's transferResource() method. The remote host returns the resource piece as an array of bytes.

  4. The local host gets the data piece of the agent by invoking the remote host's transferData() method. The remote host returns the data piece as an array of bytes.

  5. The local host marks the end of the transfer by invoking the remote host's endTransfer() method. It then resurrects the agent by calling the agent's start() method.

The code

We've got the basics under our belts, so now we're ready for the code. Let's begin with a look at the Agent interface. The agent interface defines the set of methods all agents must define.

abstract public class Agent { /** * The runtime calls initialize() exactly once -- * after the agent is created. * */

abstract public void initialize();

/** * The runtime calls start() whenever an agent must be * started. * */

abstract public void start();

/** * The runtime calls stop() whenever an agent must be * stopped -- either to transfer it, to store it, or to terminate it. * * An agent whose stop() method is called should * stop all computation, store any intermediate results, join all * active threads, and promptly return. * */

abstract public void stop();

/** * The runtime calls conclude() exactly once -- before * the agent is destroyed. * */

abstract public void conclude(); }

Mobile agents should implement the Agent class and provide definitions for these four methods.

An agent host provides the environment in which agents live. The AgentHost interface defines the methods that such an environment must provide. Among them are five in support of mobility.

import java.rmi.Remote; import java.rmi.RemoteException;

interface AgentHost extends Remote { /** * Requests that an agent transfer occur. * * A remote host calls this method on behalf of an agent and * requests the local host transfer the agent. The local host * responds by either transferring the agent and returning true, or * by returning false. * * It throws IOException if problems occurred while serializing the * data. * * It throws IllegalAccessException if a class or initializer was not * accessible. * * It throws InstantiationException if the agent tried to instantiate * an abstract class or an interface, or if the instantiation failed * for some other reason. * * It throws ClassNotFoundException if a required class could not be * found. * * It throws TransferFailedException if the local host attempted the * transfer but it failed -- either because the specified host * couldn't be found, the specified host rejected the transfer, * or the transfer failed for some other reason. * * It throws AgentNotFoundException if the specified agent does not * exist on the specified host. * * It throws RemoteException if something unexpected happened during * the remote method invocation. * */

public boolean requestToTransfer(AgentIdentity agentidentity, AgentHost agenthost) throws IOException, IllegalAccessException, InstantiationException, ClassNotFoundException, TransferFailedException, AgentNotFoundException, RemoteException;

/** * Begins a transfer. * * It throws IOException if problems occurred while serializing the * data. * * It throws TransferFailedException if the transfer failed -- either * because the remote host rejected the transfer, or because the transfer * failed for some other reason. * * It throws AgentNotFoundException if the specified agent does not * exist on the specified host. * * It throws RemoteException if something unexpected happened during * the remote method invocation. * */

public void beginTransfer(AgentIdentity agentidentity) throws IOException, TransferFailedException, AgentNotFoundException, RemoteException;

/** * Ends a transfer. * * It throws IOException if problems occurred while serializing the * data. * * It throws TransferFailedException if the transfer failed -- either * because the remote host rejected the transfer, or the transfer * failed for some other reason. * * It throws AgentNotFoundException if the specified agent does not * exist on the specified host. * * It throws RemoteException if something unexpected happened during * the remote method invocation. * */

public void endTransfer(AgentIdentity agentidentity) throws IOException TransferFailedException, AgentNotFoundException, RemoteException;

/** * Transfers the agent's resource file. * * The local host calls this method to transfer an agent's resource * file from the remote host to the local host. * * It throws IOException if problems occurred while serializing the * data. * * It throws TransferFailedException if the transfer failed -- either * because the remote host rejected the transfer, or the transfer * failed for some other reason. * * It throws AgentNotFoundException if the specified agent does not * exist on the specified host. * * It throws RemoteException if something unexpected happened during * the remote method invocation. * */

public byte [] transferResourceFile(AgentIdentity agentidentity) throws IOException, TransferFailedException, AgentNotFoundException, RemoteException;

/** * Transfers the agent's datafile. * * The local host calls this method to transfer an agent's datafile * from the remote host to the local host. * * It throws IOException if problems occurred while serializing the * data. * * It throws TransferFailedException if the transfer failed -- either * because the remote host rejected the transfer, or the transfer * failed for some other reason. * * It throws AgentNotFoundException if the specified agent does not * exist on the specified host. * * It throws RemoteException if something unexpected happened during * the remote method invocation. * */

public byte [] transferDataFile(AgentIdentity agentidentity) throws IOException, TransferFailedException, AgentNotFoundException, RemoteException; }

The AgentHost interface extends the Remote interface, making implementations of the agent host interface usable with remote method invocation. This allows agent hosts on one machine to communicate with agent hosts on another machine in a transparent manner.

The AgentHostImplementation class provides a concrete implementation of this interface. It provides definitions for each of the five methods shown above.

import java.io.IOException;

import java.rmi.RemoteException;

import java.rmi.server.UnicastRemoteObject;

import java.util.Hashtable;

class AgentHostImplementation extends UnicastRemoteObject implements AgentHost { /** * Requests that an agent transfer occur. * */

public boolean requestToTransfer(AgentIdentity agentidentity, AgentHost agenthost) throws IOException, IllegalAccessException, InstantiationException, ClassNotFoundException, TransferFailedException, AgentNotFoundException, RemoteException { byte [] rgbResource = null; byte [] rgbData = null;

// In response to a request to transfer an agent, the local // host (this one) begins the transfer, transfers the resource // and data pieces, and ends the transfer.

agenthost.beginTransfer(agentidentity);

rgbResource = agenthost.transferResourceFile(agentidentity);

rgbData = agenthost.transferDataFile(agentidentity);

agenthost.endTransfer(agentidentity);

// The resource data is passed to a specialized class loader which // loads the classes that make up the agent from the resource // piece.

AgentClassLoader agentclassloader = new AgentClassLoader(rgbResource);

// The agent wrapper is loaded. The agent wrapper knows how to // construct an agent from the data piece.

Class c = agentclassloader.loadClass("AgentWrapper");

AgentWrapper agentwrapper = (AgentInterface)c.newInstance();

// The agent is created from the serialized agent stored in // the data piece.

agentwrapper.initialize(rgbData);

// The agent is started.

agentwrapper.startAgent();

// The agent's identity and the agent wrapper are stored in // a hash table for later use.

hashtableAgents.put(agentwrapper.getAgentIdentity(), agentwrapper);

return true; }

/** * Begins a transfer. * */

public void beginTransfer(AgentIdentity agentidentity) throws IOException, TransferFailedException, AgentNotFoundException, RemoteException { // This executes on the remote host.

// The agent wrapper is read from the table.

agentwrapper = (AgentWrapper)hashtableAgents.get(agentidentity);

// The agent is stopped.

agentwrapper.stopAgent(); }

/** * Ends a transfer. * */

public void endTransfer(AgentIdentity agentidentity) throws IOException, TransferFailedException, AgentNotFoundException, RemoteException { // This executes on the remote host.

// The agent is removed from the table.

hashtableAgents.remove(agentidentity); }

/** * Transfers the agent's resource file. * */

public byte [] transferResourceFile(AgentIdentity agentidentity) throws IOException, TransferFailedException, AgentNotFoundException, RemoteException { // This executes on the remote host.

// The agent wrapper is read from the table.

agentwrapper = (AgentWrapper)hashtableAgents.get(agentidentity);

// The resource bytes are returned.

return agentwrapper.getResourceBytes(); }

/** * Transfers the agent's data file. * */

public byte [] transferDataFile(AgentIdentity agentidentity) throws IOException, TransferFailedException, AgentNotFoundException, RemoteException { // This executes on the remote host.

// The agent wrapper is read from the table.

agentwrapper = (AgentWrapper)hashtableAgents.get(agentidentity);

// The data bytes are returned.

return agentwrapper.getDataBytes(); } }

As you can see, the code is very simple. It relies on RMI to provide transparent network communication between agent hosts, and on object serialization to transfer the agent. This saves us from having to write (and debug) complicated socket code.

Conclusion

This column has been about agent mobility. As it turns out, by doing mobility the way we have, we get agent persistence for almost nothing. That's because writing the byte arrays making up the resource and data pieces of an agent to permanent storage is no more difficult than sending them to another host. In fact, it's a bit easier. I leave it to you to do the work.

Next month we add a bit more wood to the pile. In addition to mobility, agents must be able to communicate with each other. Tune in then and I'll show you one way to pull it off.

Todd Sundsted has been writing programs since computers became available in convenient desktop models. Though originally interested in building distributed object applications in C++, Todd moved on to the Java programming language when it became the obvious choice for that sort of thing. In addition to writing, Todd is president of Etcee, which offers Java-centric training, mentoring, consulting, and development.

Learn more about this topic

  • Find out all you can on remote method invocation including a tutorial, the specification, the API reference, tools, and release notes from Sun's RMI Web page http://www.javasoft.com/products/jdk/1.1/docs/guide/rmi/index.html
  • Get complete information on object serialization, including the specification, the API reference, tools, and release notes from Sun's Object Serialization Web page http://www.javasoft.com/products/jdk/1.1/docs/guide/serialization/index.html
  • Cetus Links provides gobs of information on mobile agents http://www.cetus-links.org/oo_mobile_agents.html
  • The Distributed Systems Group (Technical University of Vienna) includes an interesting look at mobile code, agents, and Java http://www.infosys.tuwien.ac.at/Research/Agents/
  • Check out the Centre for Advanced Learning Technologies' Information TechnologiesAgent Technologies page for everything related to agent technology http://www.insead.fr/CALT/Encyclopedia/ComputerSciences/Agents/
  • IBM Aglets SDK (formerly Aglets Workbench) is an environment for programming mobile Internet agents in Java http://www.trl.ibm.co.jp/aglets/
  • Mitsubishi's Concordia is a framework for development and management of network-efficient mobile agent applications http://www.meitca.com/HSL/Projects/Concordia/
  • JATLite is a package of programs written in Java that allow users to quickly create new software agents that communicate robustly over the Internet http://java.stanford.edu/
  • Previous How-To Java articles