RMI-IIOP

IBM Space Conquest Sample

Tom Watson
Senior Software Engineer
IBM Center for Java Technology - Cupertino, CA
7/14/99


Copyright ¨ 1999 International Business Machines Corporation. All Rights Reserved.


 

Overview

If you haven't used RMI-IIOP yet, you should first take a look at the "Hello World!" sample that comes with the RMI-IIOP FCS release. But if you are ready for the next step, read on.

The IBM Space Conquest is a multiuser network game, so it's not your typical CORBA application. But it is a sophisticated example of what you can do with RMI-IIOP. It creates and uses multiple servers on demand, passes large and complex objects by value, and has multiple Swing clients.

This guide will first explain what the sample does. Then it will guide you through building and running the sample. Finally, it will explain the sample, drawing attention to important lessons. Like any large application, the IBM Space Conquest sample uses many Java APIs that are not related to RMI-IIOP, e.g. Swing. We do not take the time to explain these parts, so a basic working knowledge of Java is assumed.

 

Project Organization

Unzip the ibmspace.zip file to your <RMI_IIOP_HOME>\samples directory. This will result in an ibmspace directory that has three subdirectories: common, client and server. The server directory contains classes that implement the two servers used in the game, and the underlying game simulation. The client directory contains classes that implement the Swing client. The common directory contains classes that are used by the server and client. These include interfaces and classes that are used to pass information between the client and server.

 

Setting Up the Environment

Refer to the "Hello World!" documentation that comes with the RMI-IIOP release and set up the RMI-IIOP environment the same way (including the security policy file). To simplify explanations, this document assumes that you are building and running on Windows NT and that RMI-IIOP has been installed into your c:\rmi-iiop directory, but you can also build and run the sample on Windows 95, 98 and Solaris with minor modifications to platform-specific instructions.

 

Building the IBM Space Conquest Sample

After configuring your environment, change to your <RMI_IIOP_HOME> directory, e.g. c:\rmi-iiop, and execute the following commands:

Compile all java files:

javac c:\rmi-iiop\samples\ibmspace\common\*.java
javac c:\rmi-iiop\samples\ibmspace\server\*.java
javac c:\rmi-iiop\samples\ibmspace\client\*.java

Compile the stubs with rmic:

· For JRMP:

rmic -d samples ibmspace.server.GameViewServer
rmic -d samples ibmspace.server.SpaceConquestServer

· For IIOP:

rmic -iiop -d samples ibmspace.server.GameViewServer
rmic -iiop -d samples ibmspace.server.SpaceConquestServer

 

Moving Files to Set Up the Server and Client Systems

When you run the IBM Space Conquest sample, it is easiest to have all sample files on all client and server systems. But this does not represent the typical deployment of a distributed application and also will not exercise the RMI-IIOP code downloading feature. It is more typical to deploy some "client" files to client systems and other "server" files to server systems.

There is also a third category with RMI-IIOP applications. These are classes that the client will need at run-time, but that will be downloaded from the server on demand rather than being deployed to client systems. RMI-IIOP supports passing objects by value, polymorphically. This means that a server interface can be declared to pass a specified type from the server to the client, but at run-time the actual type can be a subtype of the declared type. The declared type must be deployed to the client system, but the subtype will be downloaded via HTTP provided that the server is running an HTTP server and that the associated class file is in the server's code base.

Setting Up the Server

The server should have all server and common class files.

Setting Up the Client

The client systems should have all client and common class files. When you build for IIOP, the common package will include the following stubs. required by the client

When you build for JRMP, the stubs will be in the server package, so you will also need to create a server package on the client systems with only the following stub class files in it.

Setting Up the HTTP Server Code Base

Any files that are not on the client systems, but that will be need to be downloaded to clients, must be accessible in the HTTP server code base. The files that will be downloaded include a handful of classes in the server package, but these classes use or extend other server and common classes so you should put all class files in the common and server packages in the code base. You can eliminate some classes that are not required (while there is no harm of them being there). These include the server implementations and the tie (IIOP) and skel (JRMP) classes.

 

Running the IBM Space Conquest Sample

The following assumes that the server is run on a host named aslan.narnia.com, and that the server code base is a "java" directory under the server's root. [This implies having an ibmspace\common subdirectory and an ibmspace\server subdirectory under the java directory.] Also, you must set-up the RMI-IIOP environment in each shell (e.g. MS-DOS), and on each system where you launch a name server, server or client. Finally, substitute the variable players with the number of players/servers you will launch in step 4, and substitute the variable name with each player's name when launching a client.

1. Start an HTTP server.

2. Run the name server.

· For JRMP:

Note: Run your naming service from an environment (shell) where your CLASSPATH does not include the samples directory. See the RMI documentation for an explanation.

rmiregistry

· For IIOP:

tnameserv

3. Run the IBM Space Conquest server:

· For JRMP:

java -Djava.rmi.server.codebase=http://aslan.narnia.com/java/ -Djava.security.policy=policy -Djava.naming.factory.initial=com.sun.jndi.rmi.registry.RegistryContextFactory ibmspace.server.SpaceConquestServer players

· For IIOP:

java -Djava.rmi.server.codebase=http://aslan.narnia.com/java/ -Djava.security.policy=policy -Djava.naming.factory.initial=com.sun.jndi.cosnaming.CNCtxFactory ibmspace.server.SpaceConquestServer players

4. Run the IBM Space Conquest client (for each client):

· For JRMP:

java -Djava.rmi.server.codebase=http://aslan.narnia.com/java/ -Djava.security.policy=policy -Djava.naming.factory.initial=com.sun.jndi.rmi.registry.RegistryContextFactory -Djava.naming.provider.url=rmi://aslan.narnia.com ibmspace.client.SpaceConquestUI name

· For IIOP:

java -Djava.rmi.server.codebase=http://aslan.narnia.com/java/ -Djava.security.policy=policy -Djava.naming.factory.initial=com.sun.jndi.cosnaming.CNCtxFactory -Djava.naming.provider.url=iiop://aslan.narnia.com ibmspace.client.SpaceConquestUI name

 

Playing the Game

Now that the IBM Space Conquest sample is up and running, it will help to know how the game works. What follows is a whirlwind tour!

The Object of the Game

The game takes place in a small galaxy of 16 planets. Each player is initially given one home world with ideal temperature and gravity (72° F and 1g) and a thriving settlement. Home worlds are determined randomly and no player knows where the other players are. The object of the game is to conquer the galaxy. To do this you must build ships and send these throughout the galaxy to find and defeat your opponents.

This is not as easy as it sounds. Building ships costs money and consumes metal. You will run out of these quickly if you don’t settle new planets. Also, ships are built with your current technology levels. You must invest strategically in improving your technology levels, and you must continually scrap old ships and build new ships with improved technology to stay ahead of your opponents.

The Game Simulation and Turns

IBM Space Conquest is a turn based, simulation game. Each player can take as long as he/she wants to adjust various settings between turns … but your opponents will probably frown upon taking too long! Once you have made all or your choices and are ready to for the game simulation to proceed, press the "Take Turn" button. The simulation will not be run until all players have pressed their "Take Turn" buttons. If you have pressed it and other players have not, your button will change to a grayed (disabled) button labeled "Waiting for other players." Once all players have taken their turns and the simulation has been run, all players’ buttons will be restored to "Take Turn." And so the game goes until it ends.

Income and Investments

Each time you take a turn, your income is tallied from all of your settled planets and is invested according to your choices. You can adjust the Main Budget Controls (a set of horizontal bar controls) to divide your investment between ship savings, technology and each of your settled planets. You can adjust the Technology Budget Controls (a set of vertical bar controls) to further divide your technology investment between the different technologies – range, speed, weapons, shields, and miniaturization. Finally, when you select one of your settled planets in the Galaxy View, you get a corresponding Planet Budget Control (a pie control) that you can adjust to divide that planet’s spending between mining (blue), which extracts metal from the planet, and terraforming (green), which improves the planet’s temperature for more productive settlements and more income. Adjust these bar and pie controls at anytime between taking turns to change your spending.

Ships and Journeys

You build ships and send them on journeys to explore and settle other worlds and to find and defeat your opponents. Ships that you have built and sent on journeys move when you take turns. To build a ship you must have enough ship savings and metal. (Unfortunately, knowing how much is required is not obvious, but your request will be rejected if you don't!)

To build a ship, select a planet in the galaxy view at which you have a settlement and then choose the type of ship to build from the "Build" menu. A message box will show you the cost (metal and money) and you will be given a chance to cancel the construction. Once built, the ship will appear in the Ship List View. You can build four types of ships. Scouts are inexpensive and small and used to probe new planets for possible new settlements. Colony Ships are used to settle new planets. Fighters have slightly better offensive strength than other ships. Satellites are small and inexpensive and have slightly better defensive strength than other ships, but cannot be moved.

To send a ship on a journey, select a settled planet in the galaxy view in order to view the ships stationed there in the ship list view. Then select the ship you want to move in the ship list view. Finally, use your mouse to drag in the galaxy view from the settled planet to the planet you want to send the ship to. An arrow will be drawn as you drag. A green arrow represents a valid journey. You can release the mouse to commit the ship on a valid journey. A red arrow represents in invalid journey, but can be useful to determine the distance between two planets. As you take turns after committing a ship on a journey, the tail of the journey arrow will be shortened to indicate progress of the ship on the journey.

Battles and Exploring and Settling Planets

When ships arrive at planets, they can do any of three things. If then arrive unopposed then they land. In this case a colony ship will (1) result in the creation of a new settlement. Any other ship will (2) explore the planet without establishing a settlement. Finally, when ships meet up with ships of opponents, (3) there is a battle in which one player remains the total winner. That is, losing player loses all ships in the battle and winning player loses none.

When you select a settled or explored planet in the galaxy view, you get current planet statistics in the Planet Stats View. These statistics include the planets temperature, gravity, metal, population and income. You are not permitted to see statistics for unexplored planets or planets owned by other players.

Scrapping Ships and Abandoning Planets

As the game develops, you may decide that a ship or even a planet is not serving you well. You can scrap a ship (select it and choose "Scrap Ship" from the "Do" menu) to reclaim some of its metal for building new ships. You will also typically abandon a planet (select it and choose "Abandon Planet" from the "Do" menu) after you have mined all of its metal and believe that it will not become profitable in a reasonable amount of time.

Lots More to Discover

There are many details that have not been explained here. For these, the code is the ultimate documentation. You are also encouraged to improve the game. Our goal was to develop a complex RMI-IIOP application, but we did not have the time to really get carried away with the game!

 

Inside IBM Space Conquest - Distributed Programming Basics

The beauty of distributed programming through RMI-IIOP is that most of the details are handle transparently. You write your code very much the same way as if you were writing a typical, non-distributed Java program. But there are a few things you need to know. In this last section we walk through a few important lessons using code from the IBM Space Conquest sample to illustrate them.

Some Terminology and Concepts

Before we get started, it will help to describe the nature of a distributed application and some associated terms.

We develop a distributed application because we need to run some code on one machine and some on another. As our program is written in Java, our code is in the form of objects. Any object that has an interface that needs to be accessible to code on another machine is called a server. Any object that calls this interface is called a client. Note that an object can be both a client and a server.

There is a distinction between a remote interface and a server that implements it. A server may implement more than one remote interface and may also implement non-remote interfaces and otherwise have methods that are not remotely accessible.

Whenever a client calls a server, it seems as if it were calling the server locally. This illusion is pulled off by stubs. A stub is an object that implements the same remote interface that the server implements, but that implements it to call the server remotely. Clients and servers never know about stubs, and you need not write them. But it is helpful to know that stubs are being used and that, for any given remote interface there are two implementations, the server (which you implement) and the stub (which rmic implements for you).

Lesson 1 – How to Find a Server

So you have a client and you have a server. You start each of these up. Now, how does your client locate and begin calling your server? This introduction is coordinated through a name server. The role of the name server is to map names like "SpaceConquest" into corresponding server stubs. As we have already seen, once a client has a stub it can call the corresponding server.

RMI-IIOP applications talk to the name server through the class InitialContext. The server uses InitialContext to associate itself with a name, as is illustrated in the following code from ibmspace.server.SpaceConquestServer.main.

SpaceConquestServer obj = new SpaceConquestServer (numPlayers);
InitialContext context = new InitialContext ();
context.rebind ("SpaceConquest", obj);

The client uses InitialContext to get a stub for the server from the name, as is illustrated in the following code from ibmspace.client.GameSurrogate.lookupServer.

InitialContext context = new InitialContext ();
Object o = context.lookup ("SpaceConquest");
Server = (SpaceConquest)PortableRemoteObject.narrow (o, SpaceConquest.class);

There are a few things we should point out. First, the server is created just like any other Java object. Second, the stub is not explicitly created. An instance of the actual server is passed to InitialContext.rebind and an object is returned from InitialContext.lookup and downcasted to the remote interface of the server. At no point does either side explicitly create a stub or know that it is dealing with a stub, even though this is what is really happening. Finally, notice that the downcast on the client side must be done through PortableRemoteObject.narrow.

Lesson 2 – How to Choose a Protocol

RMI-IIOP supports the IIOP (CORBA/IIOP) and JRMP (classic Java RMI) protocols. Each protocol has its own name server. When using the IIOP protocol, the Transient Name Server (tnameserv.exe) must be used. When using JRMP, the RMI Registry (rmiregistry.exe) must be used. How does the client and server code know which of these to use, and how does it know which underlying distributed protocol, wire encoding, etc., to use?

The decision is made in the way you create InitialContext. You can pass properties to its constructor to do this (not illustrated in the IBM Space Conquest sample), but we recommend that you instead rely on environment properties set using –D on the java command line. This allows you to write and compile code that can be run using either protocol. You also use a –D property to specify the machine and port of the name server (if you omit the port, a default port is used).

To use the Transient Name Server and the corresponding RMI-IIOP protocol, pass the following –D settings:

-Djava.naming.provider.url=iiop://hostname:port
-Djava.naming.factory.initial=com.sun.jndi.cosnaming.CNCtxFatory

To use the RMI Registry and the corresponding RMI-JRMP protocol, pass the following –D settings:

-Djava.naming.provider.url=rmi://hostname:port
-Djava.naming.factory.initial= com.sun.jndi.rmi.registry.RegistryContextFactory

Lesson 3 – Remote Interfaces and Servers

We touched on the concepts of remote interfaces and servers earlier. Now we show you how to create them. A remote interface is an interface that extends java.rmi.Remote. The IBM Space Conquest sample has two remote interfaces, ibmspace.common.SpaceConquest and ibmspace.common.RemoteGameView.

A server is an object that implements a remote interface. The IBM Space Conquest sample also has two server classes, ibmspace.server.SpaceConquestServer and ibmspace.server.GameViewServer. It creates one SpaceConquestServer instance that all players use to join the game. Then it creates a GameViewServer for each player.

For a server to be accessible for remote invocation, it must be exported. Once the server is no longer needed it should be unexported. To export a server, call PortableRemoteObject.exportObject. To unexport a server, call PortableRemoteObject.unexportObject. If a server is written to extend PortableRemoteObject, then each of these is taken care of for you. The server class ibmspace.server.SpaceConquestServer illustrates this. But a server can also call these methods directly. The server class ibmspace.server.GameViewServer illustrates this.

Lesson 4 – Passing Parameters by Value

When you pass an object by value, the bits of the object are serialized, sent from one computer to another (if the client and server are on different systems which is likely but not essential), and then an equivalent object is reconstituted on the other end. In order to pass an object by value, you must ensure that the object you pass at run-time extends java.io.Serializable. The IBM Space Conquest sample passes several objects by value. For example, GameView.getMainBudget returns a BudgetSummary object by value.

Value objects can be passed to and from servers. For example, a client can pass an object to the server, then the server can modify the object and return it to the client which will see the modifications. This appears similar to passing object references in a local program, and conceptually it is the same, but the object is in fact being serialized and recreated on the other end and this is done in both directions of the call and the return.

Lesson 5 – Passing Parameters by Reference

Server objects are passed by reference. When you pass an object by reference, you do not move the object. Instead, you provide the receiving side with a stub which looks like the object, but which delegates all calls back to the server.

The IBM Space Conquest sample has two server objects, SpaceConquestServer and GameViewServer. A SpaceConquestServer stub is obtained through InitialContext.lookup, which we saw in the first lesson. SpaceConquestServer.joinGame creates a new GameViewServer and returns a reference to it. This is the only place in the IBM Space Conquest sample where a server object is passed by reference. Notice how the implementation of SpaceConquestServer.joinGame simply creates the GameViewServer object with new and returns it … nothing out of the ordinary to create a referenced. RMI-IIOP knows to return a reference instead of a value because the object returned implements java.rmi.Remote.

Lesson 6 – Deciding How to Pass Parameters at Run-Time

How a parameter is passed through return interfaces is completely determined by its run-time type. If the object implements java.io.Serializable and not java.rmi.Remote then it is passed by value. If it implements Remote then it is passed by reference. Note that a server can also be Serializable (this can be useful if you want to unexport a server, save its state to disk or move it to another system, and then export it again later on). In this case, Remote always has precedence over Serializable, and the object will still be passed by reference.

Normally, parameters in a remote interface are passed consistently, either by value or reference. But it can sometimes be useful to bind this decision at run-time. This can be useful for load balancing between the clients and servers. When a value is returned to a client and the client then calls the value, this code is executed on the client. But when a server reference is returned to the client and then called, this code is executed on the server (mostly … except for the stub code).

The IBM Space Conquest sample illustrates this with the set of classes which implement the GameView interface. GameView is an interface that represents a given player’s view of the game. Each player is returned a GameView after calling SpaceConquest.joinGame. What is interesting about this call is that it can return either a GameViewImpl (a value) or a GameViewServer (a server). SpaceConquestServer makes this decision based on the number of players expected to join the game. If the game is created for one player, then a GameViewImpl is returned by value because no server coordination is required for a single-player game. Thereafter, the entire game is run on the client system. If the game is created for more than one player, a GameViewServer is returned by reference so that the game can be coordinated on the server. Note that the game is not currently very interesting for a single player, but it could conceivably have a computer-generated opponent in this case. This is left as an exercise for the reader!!!