Remoting

You don’t have to do all distributed programming via HTTP. Sometimes you just want to make it look like you can just call a function that happens to be on another machine.

Overview

Remoting is a fancy word describing technologies that allow code in one process invokes a service (calls a remote procedure, or invokes a method on a remote object) in another process.

There are several flavors of remoting:

Remoting should be distinguished from traditional socket-based programming, since with remoting one calls a service without knowledge of any network details at all. To the client, the remote service or object appears to be in the same system. All the client needs to know is the name (or “location”) of the remote service or object.

RPC

Remote Procedure Call has been around a long time. The protocol is that a client wants to call

   p(x, y)

where p is a procedure (or a function) in another process. When the client is compiled, a proxy for the call is created that marshals the arguments and passes them to the stub on the server side. The whole call is synchronous: the caller waits for the whole thing.

rpc.png

(Well, I suppose the actual flow is really a few more levels down. The RPC library sort of sits on top of TCP....)

RMI

RMI (Remote Method Invocation) is RPC for Java — a client essentially calls a method on an object in a different (remote) JVM (which could be on a different machine). The client doesn’t have to know anything about ports or sockets.

Here's an example. First a server:

AccountServer.java
import java.rmi.Naming;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

/**
 * The interface to the remote account object. All RMI servers must implement an
 * interface which extends Remote that defines the remote methods. All the
 * remote methods must declare that they throw RemoteException. Clients only
 * interact with the server through the interface.
 */
interface Account extends Remote {
    public void deposit(int amount) throws RemoteException;

    public void withdraw(int amount) throws RemoteException;

    public int getBalance() throws RemoteException;
}

/**
 * An account that is accessed via RMI. In this case it does point-to-point
 * object referencing so it extends UnicastRemoteObject. And of course it has to
 * implement an interface that clients will use. <strong>THIS IS NOT AN EXAMPLE
 * OF A PRODUCTION-READY SERVER. IT STORES THE BALANCE IN MAIN MEMORY INSTEAD OF
 * PERSISTING IT.</strong>
 */
public class AccountServer extends UnicastRemoteObject implements Account {

    private static final long serialVersionUID = 446895079020690009L;

    private int balance;

    /**
     * Constructor required because the superclass UnicastRemoteObject has a
     * constructor which may throw a RemoteException.
     */
    public AccountServer() throws RemoteException {
        super();
    }

    // Now we have the trivial interface method implementations.

    public void deposit(int amount) {
        balance += amount;
    }

    public void withdraw(int amount) {
        balance -= amount;
    }

    public int getBalance() {
        return balance;
    }

    /**
     * Creates a server and registers it with the name Joes_Account.
     */
    public static void main(String[] args) throws Exception {
        AccountServer server = new AccountServer();
        Naming.rebind("Joes_Account", server);
        System.out.println("Account RMI server running");
    }
}

and a client:

AccountClient.java
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.rmi.Naming;
import java.rmi.RemoteException;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

/**
 * A client program for an RMI Bank called "Joes_Account".
 */
public class AccountClient extends JPanel {

    private static final long serialVersionUID = 3255779371281665000L;

    /**
     * The remote account object.
     */
    private Account account;

    private JTextField amountField = new JTextField(30);
    private JTextArea balanceArea = new JTextArea(4, 20);
    private JPanel buttonPanel = new JPanel();

    /**
     * Constructs the client by laying out the GUI and registering listeners for the
     * components.
     */
    public AccountClient() {
        setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
        setLayout(new BorderLayout(10, 10));
        add(amountField, BorderLayout.NORTH);
        new Operation("Deposit") {
            protected void execute() throws RemoteException {
                int amount = Integer.parseInt(amountField.getText());
                account.deposit(amount);
            }
        };
        new Operation("Withdraw") {
            protected void execute() throws RemoteException {
                int amount = Integer.parseInt(amountField.getText());
                account.withdraw(amount);
            }
        };
        add(new JScrollPane(balanceArea), BorderLayout.CENTER);
        add(buttonPanel, BorderLayout.SOUTH);
    }

    private void showBalance() {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                try {
                    balanceArea.setText("Balance: " + account.getBalance());
                } catch (RemoteException e) {
                    balanceArea.setText(e.toString());
                }
            }
        });
    }

    /**
     * Bundles buttons for the dialog box together with their actions. The way our
     * client is set up is that we have a bunch of buttons, each of which calls a
     * method and displays the balance after the call. The Operation itself is
     * registered as a listener for the button; the response is to call the specific
     * execute method and then write the balance. It is the responsibility of each
     * Operation subclass to override execute().
     */
    private abstract class Operation implements ActionListener {
        public Operation(String label) {
            JButton button = new JButton(label);
            buttonPanel.add(button);
            button.addActionListener(this);
        }

        public void actionPerformed(ActionEvent event) {
            try {
                execute();
                showBalance();
            } catch (Exception e) {
                balanceArea.setText(e.toString());
            }
        }

        protected abstract void execute() throws RemoteException;
    }

    /**
     * Runs the AccountClient as an application.
     */
    public static void main(String[] args) throws Exception {
        AccountClient client = new AccountClient();
        JFrame frame = new JFrame("Joe's Bank ATM");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        String serverAddress = JOptionPane.showInputDialog(null,
                "Welcome to the Remote Account ATM\n" + "Enter IP Address of the Server:");
        client.account = (Account) Naming.lookup("rmi://" + serverAddress + "/Joes_Account");
        frame.getContentPane().add(client, BorderLayout.CENTER);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
        client.showBalance();
    }
}

To run this application:

  1. Run the program rmiregistry. That's a service that runs on port 1099 by default. The server will register its "remote objects" with the registry under given names. In our example, we export only one object, under the name "Joes_Account".
  2. Build and run the server.
  3. Build and run the client. If running on two separate machines, get a copy of Account.class, generated from building the server, and place it with the client.
  4. Use the client to make deposits and withdrawls.

Note: Prior to Java 5, there was another step: running rmic to generate the stub class AccountStub.class, which had to be accessible to both the client and server. Prior to Java 2, there were stubs and skeletons. Life is better now.

.NET Remoting

.NET Remoting provides RPC-type services on the .NET platform. There's a nice article at developer.com on .NET remoting, with a fairly small C# distributed app you can build and play with.

Web Services

The much-maligned W3C's Web Services and its much-maligned SOAP are, probably more complex and more inherently inefficient than they need to be. Or are they? They are language and platform and object model independent, and so require complex interface descriptions written in WSDL, and so on and so on. They are built on top of HTTP, which solves potential firewall problems, and use XML for human-readability.

Links will go here - TODO (Wikipedia, W3C page, XML-RPC, SOAP, JAX-RPC, WSDL, UDDI....

The term web services can also be used to describe any kind of remoting technology that sits on HTTP. In that case, there are many far simpler alternatives to the (capital-W, capital-S) Web Services, many of which are not language, platform, or object-model independent.

Hessian and Burlap

Hessian is Caucho's super-lightweight web services framework. It uses a binary protocol. That means fast and attachment-less! There are implementations in Java, Python, C#, Ruby, PHP, and C++.

Burlap is practically identical to Hessian, except that it uses an XML-based protocol. Yet it's super simple and does not required external definition languages like IDL and WSDL!

Spring HTTP Invoker

Spring's HTTP Invoker is a very lightweight web service framework that uses Java serialization to transmit data. It's kind of like combining RMI with Hessian. Of course, you can only use this for Java apps running under Spring.

TODO - links

Ajax

A web application using Ajax (some speak of ajaxian applications - ugh) does the web service thing with asynchronous requests made with JavaScript as the client language. Responses can be any HTTP response: no protocol is enforced, though usually these apps use XML or JSON.

DWR

DWR is Direct Web Remoting. The authors say "DWR is easy Ajax for Java. It makes it simple to call Java code directly from Javascript. It gets rid of almost all the boiler plate code between the web browser and your Java code."

REST

REST (Representational State Transfer) describes a way to do web-oriented remoting. It's often contrasted with RPC because REST focuses on resources, not the services or operations.

Others

ICE (Internet Communications Engine)

CORBA

DCOM

EJBs