Project 1: Project Source Setup


Overview

All of the project code you write must operate inside the infrastructure we're distributing as the project 1 base code. This page gives an overview of that infrastructure. Armed with that overview, the code you see, which inevitably contains a lot of details, should be manageable.

The two key points that provide context for the code you'll see are:

  1. The project source is all portable to Android, and the Eclipse project you're seeing is a subset of a larger project that includes Android projects.

  2. The project infrastructure uses a dynamic loading scheme to assemble the set of classes that will loaded together into a JVM for execution.
The impact of the first point is that, in some cases, the Eclipse project setup might seem unnaturally partitioned to you. You're right, if the only target is the desktop, as it is in this project. The impact on you should be minimal, though - there is very little about the structure that reveals it's Android-ready.

In contrast, the dynamic loading scheme employed by the software is evident everywhere, and so it's useful to understand the basics of what it does. The figure immediately below gives a static view of this process.

Execution starts in the main() of ConsoleStart. ConsoleStart statically references NetBase, so initially these two classes are loaded in the JVM. ConsoleStart provides the mechanism required for the user to pick a configuration file. Configuration files contain arguments to programs that run in the JVM. For instance, the configuration file may list an IP address that should be used as the default for the remote machine to connect to when applications are run. The entries in the configuration file is documented here.

ConsoleStart hands the chosen configuration file to NetBase. Among the information in the configuration file are the names of Java classes for which we want an object created in the JVM. NetBase reads these names and creates a single instance of each. Each of these objects registers an "app name" with the infrastructure. For instance, the object created from class EchoRPC might register itself with the name echorpc. (This separates choosing good class names from choosing names that you might have to type a lot.) Once all objects are created, ConsoleStart transfers control to a specially designated "shell application." Here's the picture of the JVM at this point.

The shell application prints a prompt, and optionally lists the app names of all loaded applications. The user enters the name of an application to run, and the shell transfers control to the run() method of the chosen application.

In this infrastructure, the ping and dataxfer clients you write are applications. To be loaded as applications, they must implement the NetLoadableConsoleAppInterface interface. That requires implementing a constructor that takes no arguments, a run() method that is invoked when the user requests execution of your application, and a shutdown() method called when the entire JVM is coming down.

In contrast, the dataxfer service you write is a service. It must implement the NetLoadableServiceInterface. During construction it creates UDP and TCP sockets. For each socket, it creates a thread. The thread sits in a loop waiting for a packet to arrive (UDP) or a connection to be made (TCP). When that happens, the thread sends the required amount of data back to the client, then waits for another incoming packet or connection. A timeout must be set so that the thread will occasionally wake up even if there is no incoming packet connection. That allows the thread to check whether the entire application is trying to terminate. (If so, your service's shutdown() method will have been called, and you can have set a flag in the object that the threads can check.)

During execution, any component can access the configuration file selected when the infrastructure was brought up, and extract from it parameter values that affect that components behavior. The configuration file is represented in memory by a ConfigManager object (found in Eclipse project util). A reference to that object is available as NetBase.theNetbase().config().

Coding: Java Interfaces

In many cases, the infrastructure uses Java interfaces as a form of documentation. The idea is to have a short file that shows the public interfaces of a class - something much shorter than the full source file, which is cluttered with implementation and comments. Interface files are easy to spot: we followed a convention of having the string Interface in the file name.

An interface file for a fully implemented component of the infrastructure shows the methods we think you should be interested in, and doesn't show ones we think you shouldn't be interested in. (Method attributes related to Java's protection scheme (public, private, and the like) are unreliable indicators of what we expect you to be able to use. A method may be public to ease implementation of the infrastructure itself without that being a sign that we are inviting you to invoke it.)

Interface files for classes you implement are intended to make clear which methods we may rely on in our testing code. You must provide those interfaces.

Coding: Taking Measurements

The infrastructure implements a class intended to make it easy to run repeated experiments and aggregate sampling information from them. The implementation is in file SampledStatistic.java in Eclipse package util. Because the classes it uses are static, there is no interface file for it, but here is a brief description of how to use it.

The main classes you deal with are ElapsedTime and TransferRate. Each manages a dynamically created set of counters. Counters are identified by string names you make up. A new counter is created when a new name is seen. ElapsedTime counters measure time intervals. TransferRate counters measure transfer rates, which also involves measuring time intervals. Counters have start and stop methods that demarcate the time interval.

Here's an overview of how to use ElapsedTime. TransferRate is nearly identical, except that you have to provide the amount of data transferred on the stop event.


  ElapsedTime.clear();  // destroy all existing ElapsedTime counters
  for (...) {
    ElapsedTime.start("foo");  // create a foo counter, if necessary
    ...
    ElapsedTime.start("bar");  // create the bar counter, if necessary
    ...
    ElapsedTime.stop("bar");   // take a new sample - the time since start("bar")
    ..
    ElapsedTime.stop("foo");   // new foo sample
  }
  System.out.println(ElapsedTime.get("bar").mean());  // print mean of bar counter

Each start of a counter must be terminated with either a stop or an abort call before another start can be performed on the same counter. (abort records that a failed experiment occurred, but doesn't use the sample in estimating, for instance, the mean.) Other information is available besides just the mean; look at the SampleSet class. There is no requirement that start and stop of distinct counters must nest; the two stop calls above could be reversed, for instance.

Running: The Config File

The config file contains fields whose values you must you (e.g., net.timeout.socket). See the config file documentation page.

Running: Determining Ports

You must run a client and a server. To contact the server, the client must know what the IP address of the server, and the port of the application or service it wants to contact on that server.

The default server IP is given by field net.server.ip in the config file. You can modify your applications to prompt the user for an IP, though, if you prefer.

Most services use "ephemeral ports," meaning that the port they listen on is chosen at run time, and so there are no config file entries specifying ports. Instead, on the server run the dumpservicestate application. It iterates over all loaded services, asking each for its status string, which is then printd. A server's status string will contain its port number.

Running: Debug Printing

Eclipse provides an interactive debugger that works well for both console and Android execution (including setting breakpoints and the like in code running on a physical phone). Despite that, you might find it useful to print some debugging/logging information.

The infrastructure supports this via the Log class, which provides methods with names like e(), w(), and d() to print messages at error, warning, and debug levels, respectively. (For instance, Log.e(TAG, "some error occurred"); prints a message at the error level.)

The config file contains two properties that control Log printing. Setting debug.enable to 0 turns off printing entirely, while setting it to 1 (the default) allows printing. Property debug.level is set to a value between 2 (meaning verbose level) and 6 (meaning error level). Log messages at levels lower than debug.level are suppressed.

Running: Where?

You'll need to run at least two instances of your code, one as the client and one as the server. Because config files contain things like server IP addresses, your choice of where to run and the contents of your config files are related.

We distribute two config files, client.config.ini and server.config.ini. They assume you will run both the client and the server instance on the machine you're sitting at, localhost. This turns out to be very handy for debugging. It also avoids a lot of things that can go wrong trying to communicate in a symmetric fashion between two arbitrarily located machines. (We'll get to these things in lecture a little later in the course.) So, our advice is to run both instances on the same machine. If you do decide to run distributed, that's great, but remember that you'll almost certainly have to edit the config files.

Running: Moving Code to a Remote Machine

If you want to run over the network, you'll need to move your code to some remote machine. The easiest way to do this is to export the code as a jar. The easiest way to do that is to right-click on the ConsoleApps project, then Export..., expand the Java entry and select Runnable JAR file, then Next. For Launch Configuration, choose ConsoleStart. Choose some directory into which the jar should be written for the Export destination, make the name of the file ConsoleApps.jar, and hit Finish.

Now copy ConsoleApps.jar to the remote machine. Put it in a directory that also contains a single configuration file (whose name ends with config.ini), and invoke it with:

java -jar ConsoleApps.jar -d .
(The -H switch will cause a help message to be printed, listing other command line options.)

It's possible that you might get errors printed indicating Java can find some required library. That library is in the Lib directory of Eclipse project. You should move a copy to the remote machine. (Note that all libraries we use have some flavor of open source licensing. You should still make sure that you follow the license when making copies.)