Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

JVM Languages

How Do I Use Java Remote Method Invocation From An Applet?


Dr. Dobb's Journal March 1997: Java Q&A

Cliff, vice president of technology of Digital Focus, can be contacted at [email protected]. To submit questions, check out the Java Developer FAQ web site at http://www.digitalfocus.com/faq/.


Java is inherently a network-capable language. The platform independence of a Java program, combined with the automatic on-demand deployment of a Java applet provided by the Web, makes it possible to write programs that run automatically anywhere within a wide-area network or the Internet. Now that programs can be sent anywhere automatically and run anywhere, programmers want to access data on legacy systems and provide users with new host-based services -- all via a downloadable applet. Some services we would like to implement are peer-to-peer as well, requiring inter-applet cooperation across a network. The Java Remote Method Invocation (RMI) API provides a system that facilitates this. Java RMI provides CORBA-like functionality, but is lighter weight.

How Does It Work?

Using Java RMI consist of defining method calls in Java and calling the same methods that you compiled. As with CORBA, the RMI code generator (rmic) creates a stub and a skeleton. When making a remote method call, however, you are not actually calling the method that you coded; instead, you are calling a method of a generated class that implements your interface. An RMI stub is a client class that implements your interface; a skeleton is a server class that unmarshals (converts from a stream) incoming data, then calls your method implementations. When you first connect to an RMI server program and call Naming.lookup(), it returns what appears to be an instance of your remote object class (because it has the same behavior, although it is remote), but is actually a different class type that also implements the remote interface you have defined and incorporates the additional behavior needed by the RMI protocol. From then on, all remote object references you receive are the result of remote calls, and so you are always dealing with a proxy (stub) object. This is why your client code would not normally call "new" for a remote object, because if you were to do this, it would return an instance of the object type that you defined, rather than a stub object. It is the stub that knows how to pass calls remotely.

On the server side, the skeleton interprets the incoming stream, to determine which of your methods to call. It then makes the call, supplying parameters it obtained from the stream. In other words, the skeleton calls you, you don't call it. Once in your implementation code, you are dealing strictly with your own types (unless you are making remote callbacks), and all your calls are local.

For an object to accept remote requests, it must implement an interface that extends java.rmi.Remote, and it must be exported (that is, by calling java.rmi.server.UnicastRemoteObject.exportObject()). In practice, you will probably want to have all your remote objects extend the class java.rmi.server.UnicastRemoteObject, since this class automatically exports instances of itself (in its constructor), and also implements a lot of behavior that is important for a remote object. You can provide this behavior on your own, but is is far easier to just extend the class provided by Sun.

Remote programs are multithreaded programs. A server program creates an instance of an object to service remote requests. When a request arrives, it runs in (usually) a new thread. This means that you must design your methods to be re-entrant, and to guard against simultaneous access to state objects. A simple solution is to make all of your remotely exported methods synchronized; that way each incoming request will have to wait for any prior request to complete. If you expect a high rate of requests, however, you may want to use a finer granularity in what is synchronized and what is not.

Passing Objects by Reference

In Java, all objects are passed and returned by reference. This means that a pointer or handle is actually passed, in all cases. This holds true with RMI, since it relies on Java to provide its functionality. However, in the context of RMI, passing by reference and by value have some additional meaning, which needs clarification.

The RMI system provides two ways of passing objects in a remote call. When a remote reference to an object is transferred from the host to the client or vice versa, we say that the object was passed by reference. In actuality, a lightweight reference object is passed (the stub object), which contains enough information for the recipient to identify the instance on the host where the actual object resides.

For an object to be passed by remote reference, it normally (in the current release of RMI) should extend java.rmi.UnicastRemoteObject, which provides the behavior needed for the object to support remote referencing.

Passing Objects by Value

Nonremote objects (those that do not implement java.rmi.Remote) are passed from origin to destination by value. Objects that are passed by value in a remote method call are actually transferred in their entirety. This should be done with care, as the entire object is traversed, including all references within the object to other objects. The recipient then ends up with a complete copy of the object; the Java reference that is returned is a handle to this local copy. To give you control over what is passed and what is not, you use the keyword "transient" to mark references that are volatile or unserializable and that should not be traversed when copying an object for transfer.

For an object to be passed by value, it must be a serializable object. Objects that are serializable must implement java.io.Serializable directly in the class. (I tried to extend Serializable with our interfaces, and had no luck in the pre-beta version of RMI.) In JDK 1.1 many standard Java types have been made serializable. Your own classes can be made serializable, by implementing Serializable, and ensuring that each nontransient (and nonstatic) object in the class is also serializable. If you make a remote call and attempt to pass an object by value that contains other nonserializable objects, you will get an obscure error message, saying that the RMI interface was expecting data, and did not get any. This is what causes most "marshaling" errors. The Serializable type is just a flag to the system that a type is serializable; it contains no methods.

An RMI Example: A Multiplayer Game

To illustrate RMI, I wrote a multiplayer game in which players shoot at each other and rack up scores. (Alas, it is a violent game.) The game is simple and demonstrates RMI with a minimum of unrelated features and code. The program consists of a server program and an applet. The server coordinates the actions of the players, and maintains a score. The applet behaves as a slave to the server, sending the server user actions and displaying update commands sent from the server in the form of remote callbacks.

The remote interface (in GameServer.java) exported by the server contains the methods in Example 1(a). The client applet calls these methods to add itself (addPlayer()), remove itself from play (remPlayer()), and to fire its weapon at another player. GamePlayer is a remote interface, exported by the client, and the server makes callbacks to GamePlayer objects when it wants to send instructions to a client. The client applet creates an instance of a GamePlayer, which is a remote interface that exports the methods in Example 1(b).

(a)
public void addPlayer(GamePlayer gp, int x, int y) 
       throws java.rmi.RemoteException;
public void remPlayer(GamePlayer gp) throws java.rmi.RemoteException;
public void fire(GamePlayer gp) throws java.rmi.RemoteException;


(b)
public void drawPlayer(int x, int y, boolean self, boolean fire, double angle)
       throws java.rmi.RemoteException;
public void clear() throws java.rmi.RemoteException;
public void updateScore(int score) throws java.rmi.RemoteException;
Example 1: (a) Methods contained in the remote interface exported by the server; (b) methods exported by the remote interface.

These are the methods that the server calls. Thus, the server does not call the applet directly, but instead calls a remote object created by the applet. The reason for doing this is because the applet must extend the java.applet.Applet class, and so it cannot also extend the remote object class java.rmi.UnicastRemoteObject, which contains the protocol behavior we want for our remote object. So, we create a separate little server object in the applet, with the GamePlayer interface. Our applet is called GameClient. The GamePlayer object merely fields remote calls from the server, and forwards them to identical calls in the applet.

When our applet initializes itself, it creates an instance of a GamePlayer object:

gamePlayer = new GamePlayerImpl(this);

The class GamePlayerImpl implements GamePlayer, and extends java.rmi.server.UnicastRemoteObject. The constructor for this latter class calls the static method exportObject() to export itself, thereby making the object callable remotely.

The applet also has a method called connect(), which is called in the init() method, and which performs the lookup of the server object on the network:

gameServer = (GameServer)
                    (java.rmi.Naming.lookup
                      ("///GameServer"));

In the statement, the URL is coded without a host name, because I am running the applet on the same host as the server. If the server is on a different host, the URL would be of the form: rmi://hostname/GameServer, where the hostname is the name (or IP address) of the host, "GameServer" is the name our server program has assigned for itself, and "rmi:" is the protocol, which is optional since it is assumed to be "rmi:".

When the server program starts, its main method first creates an instance of the server class (which is of type UnicastRemoteObject), and then calls java.rmi.Naming.rebind("GameServer", gameServer); which registers the server object with the specified name. The main program could then terminate, since the UnicastRemoteObject it created runs in its own thread. In this case, however, you want the main program to perform the function of incrementing client weapon firing angles at regular time intervals. This has the effect of making the guns rotate.

When users press the Fire button on their applet window, the applets make remote calls to the server's fire() method, passing it the remote reference of the gamePlayer object owned by the applet. The server needs to determine which client fired its gun. To do this, it runs through its list of clients, comparing each client reference to the one passed as a parameter. There is a subtlety here, however. Since the object passed is really the stub object, a comparison using the "==" operator will fail. Instead, you use the equals() method, which compares the value of one remote reference (stub) with another. It is guaranteed to return True if two references refer to the same remote object.

After the server determines who Fired, it has to compute the effect of this event, and broadcast to all clients instructions for any resulting changes. The fact that clients are completely server-driven obviates the need for precise synchronization between the client and server, which would be impractical with today's Internet protocols.

Another simplification is that user events occur immediately. A Fire is interpreted to occur as soon as the server receives it, so the server does not need to schedule (or unschedule) future events: It processes each event as it occurs.

Figure 1 shows two game applets. Each applet displays itself on the playing field as a green box, and opponents with a red box. When the user fires, a green raybeam emanates from the player box. Opponent rays are drawn in red. The player's score is at the top of the applet; the user can modify the GamePlayer to show opponent scores as well.

At the time of this writing, the popular browsers (Netscape Navigator and Internet Explorer) do not yet incorporate RMI. However, they likely soon will, when they add support for the JDK 1.1 API. In the meantime, you can run this applet by using the appletviewer.

The complete code for this game is available electronically from DDJ, (see "Availability," page 3) or at the Digital Focus web site (http://www.digitalfocus.com/ddj/code/). A Windows .BAT script file for building the system and starting the server is also included.

Conclusion

RMI provides an extremely powerful facility for creating truly distributed programs. The current release of the RMI system is preliminary, and many new features are in development, including persistent references (in which making a remote call automatically revives the associated remote object as needed), and multicasting (for protocol-supported broadcasting of calls to multiple object instances of the same type). The ability to make callbacks to applet objects also makes real-time and notification applications possible without polling. Finally, while it is possible to bypass RMI and use sockets to implement any client/server interaction, the RMI protocol encapsulates all the mechanics of passing data as objects, and simplifies client/server programming. Designing a client/server application in Java is now merely a matter of specifying a call interface API.

DDJ


Copyright © 1997, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.