Check out the new USENIX Web site.
Using JavaUSENIX

  October, 1997

slattery by Terry Slattery
<tcs@ccci.com>

Terry has been working with UNIX and networking since 1981. He founded Chesapeake Computer Consultants in 1990 and has been discovering how business really works in a hands-on environment.

TTCP (Test TCP) is a network-testing program that I've been involved with since the early 1980s. The UNIX version of TTCP started out as a TCP throughput-testing program. It makes a TCP connection, pumps data through the connection, then reports statistics on the throughput. Many individuals in the UNIX and networking communities have enhanced it over the years. Today, the UNIX version handles both UDP and TCP tests and includes a lot of statistics reporting on various components of the transfer.

I wanted to learn Java and so decided that the combination of features required to implement TTCP would be a good exercise. A graphic interface would be useful for window-based systems, threads would be needed to handle the user interface in parallel with the data transfer component, and the networking component is absolutely required. To top it off, I decided to do the development with one of the integrated development environments to learn how they work.

The resulting Java version has both a GUI for use on window systems and a command line interface for use on remote systems where you may not have a windowing system available. It currently handles only TCP connections and, unlike the UNIX version, cannot handle data piped through it. Both the UNIX source and the Java executable are available from the Chesapeake Web site, <//www.ccci.com>. The Java source can be found at on the USENIX Web server.

Development Environments

I come from a UNIX background where my favorite development environment is emacs. However, our company issues a PC to everyone who travels (as I do). So one of the things I wanted to learn was whether the Java Integrated Development Environments were usable. I selected the Symantec Cafe development product and JDK 1.0.2 version of their compiler.

One thing I've found about PCs is the lack of real documentation. It used to be that UNIX always had the bad reputation on documentation, but these days, when you purchase a PC product, you often have documentation that gives you 25-50% of the knowledge you need. For Cafe, I had to visit the Symantec Web site and download a tutorial document that walked me though the use of the product.

Once I had the documentation, I was able to be productive with their development environment in about three hours. During this time, I made three attempts at building the TTCP graphical interface I wanted. By the third version, I knew enough about how the system worked that I was able to build the GUI (resulting in three pages of generated code). I consider this to be a very short learning curve! In the future, I expect to be able to create prototype GUI interfaces very quickly.

The other advantage to the Cafe development system is that it compiled the code in under five seconds on a desktop Dell PC with 24MB of RAM and a 90Mhz Pentium processor. Compiling the same code on a Sparc20 running Solaris and JDK 1.1.1 took tens of seconds. The development environments tend to preload the entire environment, and many provide incremental compilation, which helps increase their speeds. Even with this startup load, I didn't find that starting the application took much time.

One downside to a development environment is that you will have to follow the environment's rules for modifying the resulting code. I made the mistake of editing some sections of the code that the development environment was using and found that Cafe refused to load the result. Another "gotcha" is that development environments do not always use the best features of the underlying programming language. For example, the Cafe version I used positions the graphical items using explicit locations. This prevents TTCP from appropriately handling resizing events that would be better handled by one of the Java AWT layout managers.

This isn't enough of a detriment to prevent me from using a development environment for rapid prototyping and is likely to be fixed in future versions. To give you an idea of the development time, I started with a GUI design on paper and was able to produce the GUI and resulting Java code (see below) in under 20 minutes. Development environments, although they have some limitations, are very useful for rapid prototyping and "proof of concept" applications. They don't replace good software design techniques ­ I had already designed the GUI layout and knew exactly what I wanted when I started.
T.java

Threads

Native support of threads within Java is nice! There are even some good books available about the topic (e.g., Concurrent Java Programming by Doug Lea is a good text). The JDK documentation doesn't go into enough depth on the subject, so I wound up experimenting to determine the operation of some things. For example, in an object-oriented world, one would like to construct a thread object, use accessor methods to set a number of variables, then run the thread whenever it is needed. I found that once a thread stops, it cannot be restarted. So I created a separate class to hold all the variables needed by a thread and had the thread access them when it started.

It also took me a while to discover how to implement interprocess communication between the GUI and the worker thread (which is named TransThread). Chesapeake TTCP operates in the following manner: The GUI runs and allows the user to modify the startup parameters. Then, when the START button is pressed, a new TransThread is started. Simultaneously with starting the new thread, the GUI modifies the appearance of the buttons so that only the STOP and QUIT buttons are enabled, giving a visual indication that a test is running. When the STOP button is pressed, the GUI performs a stop operation on TransThread within the following code fragment:

public void clickedButtonStop() {
  System.err.println("Stop");
  if (Trans != null) {
    if (Trans.isAlive()) {
      Trans.stop();
      Trans = null;
    }
  }
  ButtonState(Params.STOP);
  Status.appendText("Transfer interrupted.\n");
}

This stops TransThread, resets all button states to the START state, and displays a status message in the Results section of the GUI. But what happens when TransThread reaches the end of the transfer? It needs to inform the GUI of its completion so the GUI can reset the button states.

The method that I came up with, which I later found described by Doug Lea in Concurrent Java Programming, is to have TransThread accept the GUI object as a parameter. When TransThread finishes a transfer, it calls the transferComplete() method within the GUI to reset the button appearance.

TransThread(ParamsClass p, ttcpControls c){
  Controls = c;
  Params = p;
...
 private void savestats(int i) {
  Params.setBufCount(i);
  Params.setStart(start);
  Params.setEnd(end);
  if (Controls != null) {
    Controls.transferComplete();
  }
 }
...
}

The method within the GUI that resets the button states looks like this:

public void transferComplete() {
  ButtonState(Params.STOP);
  Status.appendText(Params.getStats());
}

Networking

Once the GUI was working, it was time to add the networking code. There are several good examples available on the net, and I used them to help point me in the right direction. The Java networking model is certainly easier than the C model, but what you give up in ease of use also sacrifices in low-level access. For example, Java currently doesn't have access to ICMP, so it is not possible to build a true Java-based Ping or a Java-based Traceroute (it is possible to build a native method to access ICMP, but I discount this approach because it isn't portable).

Creating the socket is greatly simplified by using the Java Socket class, which includes the ServerSocket class for servers (receivers) and the Socket class for clients (transmitters). Once the socket exists, I created a DataInputStream or DataOutputStream from the socket to provide a high-level read/write stream interface.

There are a number of read/write methods available for these streams, but I used the DataOutputStream.write() method to write data on the sender and DataInputStream.readFully() to read data on the receiver. The readFully() method blocks until it has read the requested amount of data. To make the application robust, you have to include a lot of exception catching and handling. It's not efficient to try/catch each individual Java exception, so I include a whole block of statements within one try block and use several catch clauses to handle the important exceptions. The finally clause cleans up and saves the statistics of the transfer. The receiver code is shown below. The transmitter code is nearly the same:

if (TransmitReceive == Params.RECEIVE) {
 try {
  // Create receive server socket
  if (Server == null) {
   Server = new ServerSocket(Port);
  }
  report("Receive: buflen= " + BufSize +
   " nbuf= " + NumBuf + " port= " + Port);
  Sock = Server.accept();
  report("Receive connection from:\n " +
   Sock.toString() + ".");
  in = new DataInputStream(Sock.getInputStream());
  starttime();
  for (i = 0; i < NumBuf; i++){
   in.readFully(buf, 0, BufSize);
  }
 } catch (EOFException e) {
  report("Receive: EOF");
 } catch (Exception e) {
  report("Receive: socket or IO error: " + e);
 } finally {
  endtime();
  savestats(i);
  try {
   if (in != null) in.close();
   if (Sock != null) Sock.close();
   if (Server != null) Server.close();
 } catch (Exception e) {
   report("Receive: socket close error: " + e);
 }
 in = null;
 Sock = null;
 Server = null;
}
...

One thing I found interesting is that sockets have to be explicitly closed if you intend to use them again. My initial interpretation of how Java worked was that terminating the thread would close the socket, allowing me to reuse it. This isn't the case, and Java reported a socket already in use exception when I tried repeating a test using the same socket.

Other Observations

I enjoyed building the Java version of TTCP (you can download the Java executable from our Web site <https://www.ccci.com>) and learned quite a lot about object-oriented programming in the process. One of the things that I found surprising is the lack of a getargs() argument-parsing class. My Web searches found only an example of processing command arguments using in-line code. The differences in compilation speed between the Sparc and Cafe compilers was surprising, especially considering the higher speed of the Sparc 20 CPU. The development environment is useful for rapid prototyping, but has some drawbacks for production use, which I believe will be fixed as the product matures.

 

?Need help? Use our Contacts page.
First posted: 21st November 1997 efc
Last changed: Nov 21, 1997 pc
Java index
Publications index
USENIX home