MindView Inc.
[ Viewing Hints ] [ 2nd Edition ] [ Free Newsletter ]
[ Seminars ] [ Seminars on CD ROM ] [ Consulting ]

Thinking in Java, 1st edition

©1998 by Bruce Eckel

[ Previous Chapter ] [ Short TOC ] [ Table of Contents ] [ Index ] [ Next Chapter ]

15: Network programming

Historically, network programming has been error-prone, difficult, and complex.

The programmer had to know many details about the network and sometimes even the hardware. You usually needed to understand the various “layers” of the networking protocol, and there were a lot of different functions in each different networking library concerned with connecting, packing, and unpacking blocks of information; shipping those blocks back and forth; and handshaking. It was a daunting task.

However, the concept of networking is not so difficult. You want to get some information from that machine over there and move it to this machine here, or vice versa. It’s quite similar to reading and writing files, except that the file exists on a remote machine and the remote machine can decide exactly what it wants to do about the information you’re requesting or sending.

One of Java’s great strengths is painless networking. As much as possible, the underlying details of networking have been abstracted away and taken care of within the JVM and local machine installation of Java. The programming model you use is that of a file; in fact, you actually wrap the network connection (a “socket”) with stream objects, so you end up using the same method calls as you do with all other streams. In addition, Java’s built-in multithreading is exceptionally handy when dealing with another networking issue: handling multiple connections at once.

This chapter introduces Java’s networking support using easy-to-understand examples.

Identifying a machine

Of course, in order to tell one machine from another and to make sure that you are connected with the machine you want, there must be some way of uniquely identifying machines on a network. Early networks were satisfied to provide unique names for machines within the local network. However, Java works within the Internet, which requires a way to uniquely identify a machine from all the others in the world. This is accomplished with the IP (Internet Protocol) address that can exist in two forms:

  1. The familiar DNS (Domain Name Service) form. My domain name is bruceeckel.com, so suppose I have a computer called Opus in my domain. Its domain name would be Opus.bruceeckel.com. This is exactly the kind of name that you use when you send email to people, and is often incorporated into a World-Wide-Web address.
  2. Alternatively, you can use the “dotted quad” form, which is four numbers separated by dots, such as 123.255.28.120.

In both cases, the IP address is represented internally as a 32-bit number[59] (so each of the quad numbers cannot exceed 255), and you can get a special Java object to represent this number from either of the forms above by using the static InetAddress.getByName( ) method that’s in java.net. The result is an object of type InetAddress that you can use to build a “socket” as you will see later.

As a simple example of using InetAddress.getByName( ), consider what happens if you have a dial-up Internet service provider (ISP). Each time you dial up, you are assigned a temporary IP address. But while you’re connected, your IP address has the same validity as any other IP address on the Internet. If someone connects to your machine using your IP address then they can connect to a Web server or FTP server that you have running on your machine. Of course, they need to know your IP address, and since it’s assigned each time you dial up, how can you find out what it is?

The following program uses InetAddress.getByName( ) to produce your IP address. To use it, you must know the name of your computer. It has been tested only on Windows 95, but there you can go to “Settings,” “Control Panel,” “Network,” and then select the “Identification” tab. “Computer name” is the name to put on the command line.

//: WhoAmI.java
// Finds out your network address when you're 
// connected to the Internet.
package c15;
import java.net.*;

public class WhoAmI {
  public static void main(String[] args) 
      throws Exception {
    if(args.length != 1) {
      System.err.println(
        "Usage: WhoAmI MachineName");
      System.exit(1);
    }
    InetAddress a = 
      InetAddress.getByName(args[0]);
    System.out.println(a);
  }
} ///:~

In my case, the machine is called “Colossus” (from the movie of the same name, because I keep putting bigger disks on it). So, once I’ve connected to my ISP I run the program:

java WhoAmI Colossus

I get back a message like this (of course, the address is different each time):

Colossus/199.190.87.75

If I tell my friend this address, he can log onto my personal Web server by going to the URL http://199.190.87.75 (only as long as I continue to stay connected during that session). This can sometimes be a handy way to distribute information to someone else or to test out a Web site configuration before posting it to a “real” server.

Servers and clients

The whole point of a network is to allow two machines to connect and talk to each other. Once the two machines have found each other they can have a nice, two-way conversation. But how do they find each other? It’s like getting lost in an amusement park: one machine has to stay in one place and listen while the other machine says, “Hey, where are you?”

The machine that “stays in one place” is called the server, and the one that seeks is called the client. This distinction is important only while the client is trying to connect to the server. Once they’ve connected, it becomes a two-way communication process and it doesn’t matter anymore that one happened to take the role of server and the other happened to take the role of the client.

So the job of the server is to listen for a connection, and that’s performed by the special server object that you create. The job of the client is to try to make a connection to a server, and this is performed by the special client object you create. Once the connection is made, you’ll see that at both server and client ends, the connection is just magically turned into an IO stream object, and from then on you can treat the connection as if you were reading from and writing to a file. Thus, after the connection is made you will just use the familiar IO commands from Chapter 10. This is one of the nice features of Java networking.

Testing programs without a network

For many reasons, you might not have a client machine, a server machine, and a network available to test your programs. You might be performing exercises in a classroom situation, or you could be writing programs that aren’t yet stable enough to put onto the network. The creators of the Internet Protocol were aware of this issue, and they created a special address called localhost to be the “local loopback” IP address for testing without a network. The generic way to produce this address in Java is:

InetAddress addr = InetAddress.getByName(null);

If you hand getByName( ) a null, it defaults to using the localhost. The InetAddress is what you use to refer to the particular machine, and you must produce this before you can go any further. You can’t manipulate the contents of an InetAddress (but you can print them out, as you’ll see in the next example). The only way you can create an InetAddress is through one of that class’s static member methods getByName( ) (which is what you’ll usually use), getAllByName( ), or getLocalHost( ).

You can also produce the local loopback address by handing it the string localhost:

InetAddress.getByName("localhost");

or by using its dotted quad form to name the reserved IP number for the loopback:

InetAddress.getByName("127.0.0.1");

All three forms produce the same result.

Port: a unique place
within the machine

An IP address isn’t enough to identify a unique server, since many servers can exist on one machine. Each IP machine also contains ports, and when you’re setting up a client or a server you must choose a port where both client and server agree to connect; if you’re meeting someone, the IP address is the neighborhood and the port is the bar.

The port is not a physical location in a machine, but a software abstraction (mainly for bookkeeping purposes). The client program knows how to connect to the machine via its IP address, but how does it connect to a desired service (potentially one of many on that machine)? That’s where the port numbers come in as second level of addressing. The idea is that if you ask for a particular port, you’re requesting the service that’s associated with the port number. The time of day is a simple example of a service. Typically, each service is associated with a unique port number on a given server machine. It’s up to the client to know ahead of time which port number the desired service is running on.

The system services reserve the use of ports 1 through 1024, so you shouldn’t use those or any other port that you know to be in use. The first choice for examples in this book will be port 8080 (in memory of the venerable old 8-bit Intel 8080 chip in my first computer, a CP/M machine).

Sockets

The socket is the software abstraction used to represent the “terminals” of a connection between two machines. For a given connection, there’s a socket on each machine, and you can imagine a hypothetical “cable” running between the two machines with each end of the “cable” plugged into a socket. Of course, the physical hardware and cabling between machines is completely unknown. The whole point of the abstraction is that we don’t have to know more than is necessary.

In Java, you create a socket to make the connection to the other machine, then you get an InputStream and OutputStream (or, with the appropriate converters, Reader and Writer) from the socket in order to be able to treat the connection as an IO stream object. There are two stream-based socket classes: a ServerSocket that a server uses to “listen” for incoming connections and a Socket that a client uses in order to initiate a connection. Once a client makes a socket connection, the ServerSocket returns (via the accept( ) method) a corresponding server side Socket through which direct communications will take place. From then on, you have a true Socket to Socket connection and you treat both ends the same way because they are the same. At this point, you use the methods getInputStream( ) and getOutputStream( ) to produce the corresponding InputStream and OutputStream objects from each Socket. These must be wrapped inside buffers and formatting classes just like any other stream object described in Chapter 10.

The use of the term ServerSocket would seem to be another example of a confusing name scheme in the Java libraries. You might think ServerSocket would be better named “ServerConnector” or something without the word “Socket” in it. You might also think that ServerSocket and Socket should both be inherited from some common base class. Indeed, the two classes do have several methods in common but not enough to give them a common base class. Instead, ServerSocket’s job is to wait until some other machine connects to it, then to return an actual Socket. This is why ServerSocket seems to be a bit misnamed, since its job isn’t really to be a socket but instead to make a Socket object when someone else connects to it.

However, the ServerSocket does create a physical “server” or listening socket on the host machine. This socket listens for incoming connections and then returns an “established” socket (with the local and remote endpoints defined) via the accept( ) method. The confusing part is that both of these sockets (listening and established) are associated with the same server socket. The listening socket can accept only new connection requests and not data packets. So while ServerSocket doesn’t make much sense programmatically, it does “physically.”

When you create a ServerSocket, you give it only a port number. You don’t have to give it an IP address because it’s already on the machine it represents. When you create a Socket, however, you must give both the IP address and the port number where you’re trying to connect. (On the other hand, the Socket that comes back from ServerSocket.accept( ) already contains all this information.)

A simple server and client

This example makes the simplest use of servers and clients using sockets. All the server does is wait for a connection, then uses the Socket produced by that connection to create an InputStream and OutputStream. After that, everything it reads from the InputStream it echoes to the OutputStream until it receives the line END, at which time it closes the connection.

The client makes the connection to the server, then creates an OutputStream. Lines of text are sent through the OutputStream. The client also creates an InputStream to hear what the server is saying (which, in this case, is just the words echoed back).

Both the server and client use the same port number and the client uses the local loopback address to connect to the server on the same machine so you don’t have to test it over a network. (For some configurations, you might need to be connected to a network for the programs to work, even if you aren’t communicating over that network.)

Here is the server:

//: JabberServer.java
// Very simple server that just
// echoes whatever the client sends.
import java.io.*;
import java.net.*;

public class JabberServer {  
  // Choose a port outside of the range 1-1024:
  public static final int PORT = 8080;
  public static void main(String[] args) 
      throws IOException {
    ServerSocket s = new ServerSocket(PORT);
    System.out.println("Started: " + s);
    try {
      // Blocks until a connection occurs:
      Socket socket = s.accept();
      try {
        System.out.println(
          "Connection accepted: "+ socket);
        BufferedReader in = 
          new BufferedReader(
            new InputStreamReader(
              socket.getInputStream()));
        // Output is automatically flushed
        // by PrintWriter:
        PrintWriter out = 
          new PrintWriter(
            new BufferedWriter(
              new OutputStreamWriter(
                socket.getOutputStream())),true);
        while (true) {  
          String str = in.readLine();
          if (str.equals("END")) break;
          System.out.println("Echoing: " + str);
          out.println(str);
        }
      // Always close the two sockets...
      } finally {
        System.out.println("closing...");
        socket.close();
      }
    } finally {
      s.close();
    }
  } 
} ///:~

You can see that the ServerSocket just needs a port number, not an IP address (since it’s running on this machine!). When you call accept( ), the method blocks until some client tries to connect to it. That is, it’s there waiting for a connection but other processes can run (see Chapter 14). When a connection is made, accept( ) returns with a Socket object representing that connection.

The responsibility for cleaning up the sockets is crafted carefully here. If the ServerSocket constructor fails, the program just quits (notice we must assume that the constructor for ServerSocket doesn’t leave any open network sockets lying around if it fails). For this case, main( ) throws IOException so a try block is not necessary. If the ServerSocket constructor is successful then all other method calls must be guarded in a try-finally block to ensure that, no matter how the block is left, the ServerSocket is properly closed.

The same logic is used for the Socket returned by accept( ). If accept( ) fails, then we must assume that the Socket doesn’t exist or hold any resources, so it doesn’t need to be cleaned up. If it’s successful, however, the following statements must be in a try-finally block so that if they fail the Socket will still be cleaned up. Care is required here because sockets use important non-memory resources, so you must be diligent in order to clean them up (since there is no destructor in Java to do it for you).

Both the ServerSocket and the Socket produced by accept( ) are printed to System.out. This means that their toString( ) methods are automatically called. These produce:

ServerSocket[addr=0.0.0.0,PORT=0,localport=8080]
Socket[addr=127.0.0.1,PORT=1077,localport=8080]

Shortly, you’ll see how these fit together with what the client is doing.

The next part of the program looks just like opening files for reading and writing except that the InputStream and OutputStream are created from the Socket object. Both the InputStream and OutputStream objects are converted to Java 1.1 Reader and Writer objects using the “converter” classes InputStreamReader and OutputStreamWriter, respectively. You could also have used the Java 1.0 InputStream and OutputStream classes directly, but with output there’s a distinct advantage to using the Writer approach. This appears with PrintWriter, which has an overloaded constructor that takes a second argument, a boolean flag that indicates whether to automatically flush the output at the end of each println( ) (but not print( )) statement. Every time you write to out, its buffer must be flushed so the information goes out over the network. Flushing is important for this particular example because the client and server each wait for a line from the other party before proceeding. If flushing doesn’t occur, the information will not be put onto the network until the buffer is full, which causes lots of problems in this example.

When writing network programs you need to be careful about using automatic flushing. Every time you flush the buffer a packet must be created and sent. In this case, that’s exactly what we want, since if the packet containing the line isn’t sent then the handshaking back and forth between server and client will stop. Put another way, the end of a line is the end of a message. But in many cases messages aren’t delimited by lines so it’s much more efficient to not use auto flushing and instead let the built-in buffering decide when to build and send a packet. This way, larger packets can be sent and the process will be faster.

Note that, like virtually all streams you open, these are buffered. There’s an exercise at the end of the chapter to show you what happens if you don’t buffer the streams (things get slow).

The infinite while loop reads lines from the BufferedReader in and writes information to System.out and to the PrintWriter out. Note that these could be any streams, they just happen to be connected to the network.

When the client sends the line consisting of “END” the program breaks out of the loop and closes the Socket.

Here’s the client:

//: JabberClient.java
// Very simple client that just sends
// lines to the server and reads lines
// that the server sends.
import java.net.*;
import java.io.*;

public class JabberClient {
  public static void main(String[] args) 
      throws IOException {
    // Passing null to getByName() produces the
    // special "Local Loopback" IP address, for
    // testing on one machine w/o a network:
    InetAddress addr = 
      InetAddress.getByName(null);
    // Alternatively, you can use 
    // the address or name:
    // InetAddress addr = 
    //    InetAddress.getByName("127.0.0.1");
    // InetAddress addr = 
    //    InetAddress.getByName("localhost");
    System.out.println("addr = " + addr);
    Socket socket = 
      new Socket(addr, JabberServer.PORT);
    // Guard everything in a try-finally to make
    // sure that the socket is closed:
    try {
      System.out.println("socket = " + socket);
      BufferedReader in =
        new BufferedReader(
          new InputStreamReader(
            socket.getInputStream()));
      // Output is automatically flushed
      // by PrintWriter:
      PrintWriter out =
        new PrintWriter(
          new BufferedWriter(
            new OutputStreamWriter(
              socket.getOutputStream())),true);
      for(int i = 0; i < 10; i ++) {
        out.println("howdy " + i);
        String str = in.readLine();
        System.out.println(str);
      }
      out.println("END");
    } finally {
      System.out.println("closing...");
      socket.close();
    }
  }
} ///:~

In main( ) you can see all three ways to produce the InetAddress of the local loopback IP address: using null, localhost, or the explicit reserved address 127.0.0.1. Of course, if you want to connect to a machine across a network you substitute that machine’s IP address. When the InetAddress addr is printed (via the automatic call to its toString( ) method) the result is:

localhost/127.0.0.1

By handing getByName( ) a null, it defaulted to finding the localhost, and that produced the special address 127.0.0.1.

Note that the Socket called socket is created with both the InetAddress and the port number. To understand what it means when you print out one of these Socket objects, remember that an Internet connection is determined uniquely by these four pieces of data: clientHost, clientPortNumber, serverHost, and serverPortNumber. When the server comes up, it takes up its assigned port (8080) on the localhost (127.0.0.1). When the client comes up, it is allocated to the next available port on its machine, 1077 in this case, which also happens to be on the same machine (127.0.0.1) as the server. Now, in order for data to move between the client and server, each side has to know where to send it. Therefore, during the process of connecting to the “known” server, the client sends a “return address” so the server knows where to send its data. This is what you see in the example output for the server side:

Socket[addr=127.0.0.1,port=1077,localport=8080]

This means that the server just accepted a connection from 127.0.0.1 on port 1077 while listening on its local port (8080). On the client side:

Socket[addr=localhost/127.0.0.1,PORT=8080,localport=1077]

which means that the client made a connection to 127.0.0.1 on port 8080 using the local port 1077.

You’ll notice that every time you start up the client anew, the local port number is incremented. It starts at 1025 (one past the reserved block of ports) and keeps going up until you reboot the machine, at which point it starts at 1025 again. (On UNIX machines, once the upper limit of the socket range is reached, the numbers will wrap around to the lowest available number again.)

Once the Socket object has been created, the process of turning it into a BufferedReader and PrintWriter is the same as in the server (again, in both cases you start with a Socket). Here, the client initiates the conversation by sending the string “howdy” followed by a number. Note that the buffer must again be flushed (which happens automatically via the second argument to the PrintWriter constructor). If the buffer isn’t flushed, the whole conversation will hang because the initial “howdy” will never get sent (the buffer isn’t full enough to cause the send to happen automatically). Each line that is sent back from the server is written to System.out to verify that everything is working correctly. To terminate the conversation, the agreed-upon “END” is sent. If the client simply hangs up, then the server throws an exception.

You can see that the same care is taken here to ensure that the network resources represented by the Socket are properly cleaned up, using a try-finally block.

Sockets produce a “dedicated” connection that persists until it is explicitly disconnected. (The dedicated connection can still be disconnected un-explicitly if one side, or an intermediary link, of the connection crashes.) This means the two parties are locked in communication and the connection is constantly open. This seems like a logical approach to networking, but it puts an extra load on the network. Later in the chapter you’ll see a different approach to networking, in which the connections are only temporary.

Serving multiple clients

The JabberServer works, but it can handle only one client at a time. In a typical server, you’ll want to be able to deal with many clients at once. The answer is multithreading, and in languages that don’t directly support multithreading this means all sorts of complications. In Chapter 14 you saw that multithreading in Java is about as simple as possible, considering that multithreading is a rather complex topic. Because threading in Java is reasonably straightforward, making a server that handles multiple clients is relatively easy.

The basic scheme is to make a single ServerSocket in the server and call accept( ) to wait for a new connection. When accept( ) returns, you take the resulting Socket and use it to create a new thread whose job is to serve that particular client. Then you call accept( ) again to wait for a new client.

In the following server code, you can see that it looks similar to the JabberServer.java example except that all of the operations to serve a particular client have been moved inside a separate thread class:

//: MultiJabberServer.java
// A server that uses multithreading to handle 
// any number of clients.
import java.io.*;
import java.net.*;

class ServeOneJabber extends Thread {
  private Socket socket;
  private BufferedReader in;
  private PrintWriter out;
  public ServeOneJabber(Socket s) 
      throws IOException {
    socket = s;
    in = 
      new BufferedReader(
        new InputStreamReader(
          socket.getInputStream()));
    // Enable auto-flush:
    out = 
      new PrintWriter(
        new BufferedWriter(
          new OutputStreamWriter(
            socket.getOutputStream())), true);
    // If any of the above calls throw an 
    // exception, the caller is responsible for
    // closing the socket. Otherwise the thread
    // will close it.
    start(); // Calls run()
  }
  public void run() {
    try {
      while (true) {  
        String str = in.readLine();
        if (str.equals("END")) break;
        System.out.println("Echoing: " + str);
        out.println(str);
      }
      System.out.println("closing...");
    } catch (IOException e) {
    } finally {
      try {
        socket.close();
      } catch(IOException e) {}
    }
  }
}

public class MultiJabberServer {  
  static final int PORT = 8080;
  public static void main(String[] args)
      throws IOException {
    ServerSocket s = new ServerSocket(PORT);
    System.out.println("Server Started");
    try {
      while(true) {
        // Blocks until a connection occurs:
        Socket socket = s.accept();
        try {
          new ServeOneJabber(socket);
        } catch(IOException e) {
          // If it fails, close the socket,
          // otherwise the thread will close it:
          socket.close();
        }
      }
    } finally {
      s.close();
    }
  } 
} ///:~

The ServeOneJabber thread takes the Socket object that’s produced by accept( ) in main( ) every time a new client makes a connection. Then, as before, it creates a BufferedReader and auto-flushed PrintWriter object using the Socket. Finally, it calls the special Thread method start( ), which performs thread initialization and then calls run( ). This performs the same kind of action as in the previous example: reading something from the socket and then echoing it back until it reads the special “END” signal.

The responsibility for cleaning up the socket must again be carefully designed. In this case, the socket is created outside of the ServeOneJabber so the responsibility can be shared. If the ServeOneJabber constructor fails, it will just throw the exception to the caller, who will then clean up the thread. But if the constructor succeeds, then the ServeOneJabber object takes over responsibility for cleaning up the thread, in its run( ).

Notice the simplicity of the MultiJabberServer. As before, a ServerSocket is created and accept( ) is called to allow a new connection. But this time, the return value of accept( ) (a Socket) is passed to the constructor for ServeOneJabber, which creates a new thread to handle that connection. When the connection is terminated, the thread simply goes away.

If the creation of the ServerSocket fails, the exception is again thrown through main( ). But if it succeeds, the outer try-finally guarantees its cleanup. The inner try-catch guards only against the failure of the ServeOneJabber constructor; if the constructor succeeds, then the ServeOneJabber thread will close the associated socket.

To test that the server really does handle multiple clients, the following program creates many clients (using threads) that connect to the same server. Each thread has a limited lifetime, and when it goes away, that leaves space for the creation of a new thread. The maximum number of threads allowed is determined by the final int maxthreads. You’ll notice that this value is rather critical, since if you make it too high the threads seem to run out of resources and the program mysteriously fails.

//: MultiJabberClient.java
// Client that tests the MultiJabberServer
// by starting up multiple clients.
import java.net.*;
import java.io.*;

class JabberClientThread extends Thread {
  private Socket socket;
  private BufferedReader in;
  private PrintWriter out;
  private static int counter = 0;
  private int id = counter++;
  private static int threadcount = 0;
  public static int threadCount() { 
    return threadcount; 
  }
  public JabberClientThread(InetAddress addr) {
    System.out.println("Making client " + id);
    threadcount++;
    try {
      socket = 
        new Socket(addr, MultiJabberServer.PORT);
    } catch(IOException e) {
      // If the creation of the socket fails, 
      // nothing needs to be cleaned up.
    }
    try {    
      in = 
        new BufferedReader(
          new InputStreamReader(
            socket.getInputStream()));
      // Enable auto-flush:
      out = 
        new PrintWriter(
          new BufferedWriter(
            new OutputStreamWriter(
              socket.getOutputStream())), true);
      start();
    } catch(IOException e) {
      // The socket should be closed on any 
      // failures other than the socket 
      // constructor:
      try {
        socket.close();
      } catch(IOException e2) {}
    }
    // Otherwise the socket will be closed by
    // the run() method of the thread.
  }
  public void run() {
    try {
      for(int i = 0; i < 25; i++) {
        out.println("Client " + id + ": " + i);
        String str = in.readLine();
        System.out.println(str);
      }
      out.println("END");
    } catch(IOException e) {
    } finally {
      // Always close it:
      try {
        socket.close();
      } catch(IOException e) {}
      threadcount--; // Ending this thread
    }
  }
}

public class MultiJabberClient {
  static final int MAX_THREADS = 40;
  public static void main(String[] args) 
      throws IOException, InterruptedException {
    InetAddress addr = 
      InetAddress.getByName(null);
    while(true) {
      if(JabberClientThread.threadCount() 
         < MAX_THREADS)
        new JabberClientThread(addr);
      Thread.currentThread().sleep(100);
    }
  }
} ///:~

The JabberClientThread constructor takes an InetAddress and uses it to open a Socket. You’re probably starting to see the pattern: the Socket is always used to create some kind of Reader and/or Writer (or InputStream and/or OutputStream) object, which is the only way that the Socket can be used. (You can, of course, write a class or two to automate this process instead of doing all the typing if it becomes painful.) Again, start( ) performs thread initialization and calls run( ). Here, messages are sent to the server and information from the server is echoed to the screen. However, the thread has a limited lifetime and eventually completes. Note that the socket is cleaned up if the constructor fails after the socket is created but before the constructor completes. Otherwise the responsibility for calling close( ) for the socket is relegated to the run( ) method.

The threadcount keeps track of how many JabberClientThread objects currently exist. It is incremented as part of the constructor and decremented as run( ) exits (which means the thread is terminating). In MultiJabberClient.main( ), you can see that the number of threads is tested, and if there are too many, no more are created. Then the method sleeps. This way, some threads will eventually terminate and more can be created. You can experiment with MAX_THREADS to see where your particular system begins to have trouble with too many connections.

Datagrams

The examples you’ve seen so far use the Transmission Control Protocol (TCP, also known as stream-based sockets), which is designed for ultimate reliability and guarantees that the data will get there. It allows retransmission of lost data, it provides multiple paths through different routers in case one goes down, and bytes are delivered in the order they are sent. All this control and reliability comes at a cost: TCP has a high overhead.

There’s a second protocol, called User Datagram Protocol (UDP), which doesn’t guarantee that the packets will be delivered and doesn’t guarantee that they will arrive in the order they were sent. It’s called an “unreliable protocol” (TCP is a “reliable protocol”), which sounds bad, but because it’s much faster it can be useful. There are some applications, such as an audio signal, in which it isn’t so critical if a few packets are dropped here or there but speed is vital. Or consider a time-of-day server, where it really doesn’t matter if one of the messages is lost. Also, some applications might be able to fire off a UDP message to a server and can then assume, if there is no response in a reasonable period of time, that the message was lost.

The support for datagrams in Java has the same feel as its support for TCP sockets, but there are significant differences. With datagrams, you put a DatagramSocket on both the client and server, but there is no analogy to the ServerSocket that waits around for a connection. That’s because there is no “connection,” but instead a datagram just shows up. Another fundamental difference is that with TCP sockets, once you’ve made the connection you don’t need to worry about who’s talking to whom anymore; you just send the data back and forth through conventional streams. However, with datagrams, the datagram packet must know where it came from and where it’s supposed to go. That means you must know these things for each datagram packet that you load up and ship off.

A DatagramSocket sends and receives the packets, and the DatagramPacket contains the information. When you’re receiving a datagram, you need only provide a buffer in which the data will be placed; the information about the Internet address and port number where the information came from will be automatically initialized when the packet arrives through the DatagramSocket. So the constructor for a DatagramPacket to receive datagrams is:

DatagramPacket(buf, buf.length)

in which buf is an array of byte. Since buf is an array, you might wonder why the constructor couldn’t figure out the length of the array on its own. I wondered this, and can only guess that it’s a throwback to C-style programming, in which of course arrays can’t tell you how big they are.

You can reuse a receiving datagram; you don’t have to make a new one each time. Every time you reuse it, the data in the buffer is overwritten.

The maximum size of the buffer is restricted only by the allowable datagram packet size, which limits it to slightly less than 64Kbytes. However, in many applications you’ll want it to be much smaller, certainly when you’re sending data. Your chosen packet size depends on what you need for your particular application.

When you send a datagram, the DatagramPacket must contain not only the data, but also the Internet address and port where it will be sent. So the constructor for an outgoing DatagramPacket is:

DatagramPacket(buf, length, inetAddress, port)

This time, buf (which is a byte array) already contains the data that you want to send out. The length might be the length of buf, but it can also be shorter, indicating that you want to send only that many bytes. The other two arguments are the Internet address where the packet is going and the destination port within that machine.[60]

You might think that the two constructors create two different objects: one for receiving datagrams and one for sending them. Good OO design would suggest that these should be two different classes, rather than one class with different behavior depending on how you construct the object. This is probably true, but fortunately the use of DatagramPackets is simple enough that you’re not tripped up by the problem, as you can see in the following example. This example is similar to the MultiJabberServer and MultiJabberClient example for TCP sockets. Multiple clients will send datagrams to a server, which will echo them back to the same client that sent the message.

To simplify the creation of a DatagramPacket from a String and vice-versa, the example begins with a utility class, Dgram, to do the work for you:

//: Dgram.java
// A utility class to convert back and forth
// Between Strings and DataGramPackets.
import java.net.*;

public class Dgram {
  public static DatagramPacket toDatagram(
    String s, InetAddress destIA, int destPort) {
    // Deprecated in Java 1.1, but it works:
    byte[] buf = new byte[s.length() + 1];
    s.getBytes(0, s.length(), buf, 0);
    // The correct Java 1.1 approach, but it's
    // Broken (it truncates the String):
    // byte[] buf = s.getBytes();
    return new DatagramPacket(buf, buf.length, 
      destIA, destPort);
  }
  public static String toString(DatagramPacket p){
    // The Java 1.0 approach:
    // return new String(p.getData(), 
    //  0, 0, p.getLength());
    // The Java 1.1 approach:
    return 
      new String(p.getData(), 0, p.getLength());
  }
} ///:~

The first method of Dgram takes a String, an InetAddress, and a port number and builds a DatagramPacket by copying the contents of the String into a byte buffer and passing the buffer into the DatagramPacket constructor. Notice the “+1” in the buffer allocation – this was necessary to prevent truncation. The getBytes( ) method of String is a special operation that copies the chars of a String into a byte buffer. This method is now deprecated; Java 1.1 has a “better” way to do this but it’s commented out here because it truncates the String. So you’ll get a deprecation message when you compile it under Java 1.1, but the behavior will be correct. (This bug might be fixed by the time you read this.)

The Dgram.toString( ) method shows both the Java 1.0 approach and the Java 1.1 approach (which is different because there’s a new kind of String constructor).

Here is the server for the datagram demonstration:

//: ChatterServer.java
// A server that echoes datagrams
import java.net.*;
import java.io.*;
import java.util.*;

public class ChatterServer {
  static final int INPORT = 1711;
  private byte[] buf = new byte[1000];
  private DatagramPacket dp = 
    new DatagramPacket(buf, buf.length);
  // Can listen & send on the same socket:
  private DatagramSocket socket;

  public ChatterServer() {
    try {
      socket = new DatagramSocket(INPORT);
      System.out.println("Server started");
      while(true) {
        // Block until a datagram appears:
        socket.receive(dp);
        String rcvd = Dgram.toString(dp) +
          ", from address: " + dp.getAddress() +
          ", port: " + dp.getPort();
        System.out.println(rcvd);
        String echoString = 
          "Echoed: " + rcvd;
        // Extract the address and port from the
        // received datagram to find out where to
        // send it back:
        DatagramPacket echo = 
          Dgram.toDatagram(echoString,
            dp.getAddress(), dp.getPort());
        socket.send(echo);
      }
    } catch(SocketException e) {
      System.err.println("Can't open socket");
      System.exit(1);
    } catch(IOException e) {
      System.err.println("Communication error");
      e.printStackTrace();
    }
  }
  public static void main(String[] args) {
    new ChatterServer();
  }
} ///:~

The ChatterServer contains a single DatagramSocket for receiving messages, instead of creating one each time you’re ready to receive a new message. The single DatagramSocket can be used repeatedly. This DatagramSocket has a port number because this is the server and the client must have an exact address where it wants to send the datagram. It is given a port number but not an Internet address because it resides on “this” machine so it knows what its Internet address is (in this case, the default localhost). In the infinite while loop, the socket is told to receive( ), whereupon it blocks until a datagram shows up, and then sticks it into our designated receiver, the DatagramPacket dp. The packet is converted to a String along with information about the Internet address and socket where the packet came from. This information is displayed, and then an extra string is added to indicate that it is being echoed back from the server.

Now there’s a bit of a quandary. As you will see, there are potentially many different Internet addresses and port numbers that the messages might come from – that is, the clients can reside on any machine. (In this demonstration they all reside on the localhost, but the port number for each client is different.) To send a message back to the client that originated it, you need to know that client’s Internet address and port number. Fortunately, this information is conveniently packaged inside the DatagramPacket that sent the message, so all you have to do is pull it out using getAddress( ) and getPort( ), which are used to build the DatagramPacket echo that is sent back through the same socket that’s doing the receiving. In addition, when the socket sends the datagram, it automatically adds the Internet address and port information of this machine, so that when the client receives the message, it can use getAddress( ) and getPort( ) to find out where the datagram came from. In fact, the only time that getAddress( ) and getPort( ) don’t tell you where the datagram came from is if you create a datagram to send and you call getAddress( ) and getPort( ) before you send the datagram (in which case it tells the address and port of this machine, the one the datagram is being sent from). This is an essential part of datagrams: you don’t need to keep track of where a message came from because it’s always stored inside the datagram. In fact, the most reliable way to program is if you don’t try to keep track, but instead always extract the address and port from the datagram in question (as is done here).

To test this server, here’s a program that makes a number of clients, all of which fire datagram packets to the server and wait for the server to echo them back.

//: ChatterClient.java
// Tests the ChatterServer by starting multiple 
// clients, each of which sends datagrams.
import java.lang.Thread;
import java.net.*;
import java.io.*;

public class ChatterClient extends Thread {
  // Can listen & send on the same socket:
  private DatagramSocket s;
  private InetAddress hostAddress;
  private byte[] buf = new byte[1000];
  private DatagramPacket dp = 
    new DatagramPacket(buf, buf.length);
  private int id;

  public ChatterClient(int identifier) {
    id = identifier;
    try {
      // Auto-assign port number:
      s = new DatagramSocket();
      hostAddress = 
        InetAddress.getByName("localhost");
    } catch(UnknownHostException e) {
      System.err.println("Cannot find host");
      System.exit(1);
    } catch(SocketException e) {
      System.err.println("Can't open socket");
      e.printStackTrace();
      System.exit(1);
    } 
    System.out.println("ChatterClient starting");
  }
  public void run() {
    try {
      for(int i = 0; i < 25; i++) {
        String outMessage = "Client #" +
          id + ", message #" + i;
        // Make and send a datagram:
        s.send(Dgram.toDatagram(outMessage,
          hostAddress, 
          ChatterServer.INPORT));
        // Block until it echoes back:
        s.receive(dp);
        // Print out the echoed contents:
        String rcvd = "Client #" + id +
          ", rcvd from " + 
          dp.getAddress() + ", " + 
          dp.getPort() + ": " +
          Dgram.toString(dp);
        System.out.println(rcvd);
      }
    } catch(IOException e) {
      e.printStackTrace();
      System.exit(1);
    }
  }
  public static void main(String[] args) {
    for(int i = 0; i < 10; i++)
      new ChatterClient(i).start();
  }
} ///:~

ChatterClient is created as a Thread so that multiple clients can be made to bother the server. Here you can see that the receiving DatagramPacket looks just like the one used for ChatterServer. In the constructor, the DatagramSocket is created with no arguments since it doesn’t need to advertise itself as being at a particular port number. The Internet address used for this socket will be “this machine” (for the example, localhost) and the port number will be automatically assigned, as you will see from the output. This DatagramSocket, like the one for the server, will be used both for sending and receiving.

The hostAddress is the Internet address of the host machine you want to talk to. The one part of the program in which you must know an exact Internet address and port number is the part in which you make the outgoing DatagramPacket. As is always the case, the host must be at a known address and port number so that clients can originate conversations with the host.

Each thread is given a unique identification number (although the port number automatically assigned to the thread would also provide a unique identifier). In run( ), a message String is created that contains the thread’s identification number and the message number this thread is currently sending. This String is used to create a datagram that is sent to the host at its address; the port number is taken directly from a constant in ChatterServer. Once the message is sent, receive( ) blocks until the server replies with an echoing message. All of the information that’s shipped around with the message allows you to see that what comes back to this particular thread is derived from the message that originated from it. In this example, even though UDP is an “unreliable” protocol, you’ll see that all of the datagrams get where they’re supposed to. (This will be true for localhost and LAN situations, but you might begin to see some failures for non-local connections.)

When you run this program, you’ll see that each of the threads finishes, which means that each of the datagram packets sent to the server is turned around and echoed to the correct recipient; otherwise one or more threads would hang, blocking until their input shows up.

You might think that the only right way to, for example, transfer a file from one machine to another is through TCP sockets, since they’re “reliable.” However, because of the speed of datagrams they can actually be a better solution. You simply break the file up into packets and number each packet. The receiving machine takes the packets and reassembles them; a “header packet” tells the machine how many to expect and any other important information. If a packet is lost, the receiving machine sends a datagram back telling the sender to retransmit.

A Web application

Now let’s consider creating an application to run on the Web, which will show Java in all its glory. Part of this application will be a Java program running on the Web server, and the other part will be an applet that’s downloaded to the browser. The applet collects information from the user and sends it back to the application running on the Web server. The task of the program will be simple: the applet will ask for the email address of the user, and after verifying that this address is reasonably legitimate (it doesn’t contain spaces, and it does contain an ‘@’ symbol) the applet will send the email address to the Web server. The application running on the server will capture the data and check a data file in which all of the email addresses are kept. If that address is already in the file, it will send back a message to that effect, which is displayed by the applet. If the address isn’t in the file, it is placed in the list and the applet is informed that the address was added successfully.

Traditionally, the way to handle such a problem is to create an HTML page with a text field and a “submit” button. The user can type whatever he or she wants into the text field, and it will be submitted to the server without question. As it submits the data, the Web page also tells the server what to do with the data by mentioning the Common Gateway Interface (CGI) program that the server should run after receiving this data. This CGI program is typically written in either Perl or C (and sometimes C++, if the server supports it), and it must handle everything. First it looks at the data and decides whether it’s in the correct format. If not, the CGI program must create an HTML page to describe the problem; this page is handed to the server, which sends it back to the user. The user must then back up a page and try again. If the data is correct, the CGI program opens the data file and either adds the email address to the file or discovers that the address is already in the file. In both cases it must format an appropriate HTML page for the server to return to the user.

As Java programmers, this seems like an awkward way for us to solve the problem, and naturally, we’d like to do the whole thing in Java. First, we’ll use a Java applet to take care of data validation at the client site, without all that tedious Web traffic and page formatting. Then let’s skip the Perl CGI script in favor of a Java application running on the server. In fact, let’s skip the Web server altogether and simply make our own network connection from the applet to the Java application on the server!

As you’ll see, there are a number of issues that make this a more complicated problem than it seems. It would be ideal to write the applet using Java 1.1 but that’s hardly practical. At this writing, the number of users running Java 1.1-enabled browsers is small, and although such browsers are now commonly available, you’ll probably need to take into account that a significant number of users will be slow to upgrade. So to be on the safe side, the applet will be programmed using only Java 1.0 code. With this in mind, there will be no JAR files to combine .class files in the applet, so the applet should be designed to create as few .class files as possible to minimize download time.

Well, it turns out the Web server (the one available to me when I wrote the example) does have Java in it, but only Java 1.0! So the server application must also be written using Java 1.0.

The server application

Now consider the server application, which will be called NameCollector. What happens if more than one user at a time tries to submit their email addresses? If NameCollector uses TCP/IP sockets, then it must use the multithreading approach shown earlier to handle more than one client at a time. But all of these threads will try to write to a single file where all the email addresses will be kept. This would require a locking mechanism to make sure that more than one thread doesn’t access the file at once. A semaphore will do the trick, but perhaps there’s a simpler way.

If we use datagrams instead, multithreading is unnecessary. A single datagram socket will listen for incoming datagrams, and when one appears the program will process the message and send the reply as a datagram back to whomever sent the request. If the datagram gets lost, then the user will notice that no reply comes and can then re-submit the request.

When the server application receives a datagram and unpacks it, it must extract the email address and check the file to see if that address is there already (and if it isn’t, add it). And now we run into another problem. It turns out that Java 1.0 doesn’t quite have the horsepower to easily manipulate the file containing the email addresses (Java 1.1 does). However, the problem can be solved in C quite readily, and this will provide an excuse to show you the easiest way to connect a non-Java program to a Java program. A Runtime object for a program has a method called exec( ) that will start up a separate program on the machine and return a Process object. You can get an OutputStream that connects to standard input for this separate program and an InputStream that connects to standard output. All you need to do is write a program using any language that takes its input from standard input and writes the output to standard output. This is a convenient trick when you run into a problem that can’t be solved easily or quickly enough in Java (or when you have legacy code you don’t want to rewrite). You can also use Java’s native methods (see Appendix A) but those are much more involved.

The C program

The job of this non-Java application (written in C because Java wasn’t appropriate for CGI programming; if nothing else, the startup time is prohibitive) is to manage the list of email addresses. Standard input will accept an email address and the program will look up the name in the list to see if it’s already there. If not, it will add it and report success, but if the name is already there then it will report that. Don’t worry if you don’t completely understand what the following code means; it’s just one example of how you can write a program in another language and use it from Java. The particular programming language doesn’t really matter as long as it can read from standard input and write to standard output.

//: Listmgr.c
// Used by NameCollector.java to manage 
// the email list file on the server
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BSIZE 250

int alreadyInList(FILE* list, char* name) {
  char lbuf[BSIZE];
  // Go to the beginning of the list:
  fseek(list, 0, SEEK_SET);
  // Read each line in the list:
  while(fgets(lbuf, BSIZE, list)) {
    // Strip off the newline:
    char * newline = strchr(lbuf, '\n');
    if(newline != 0) 
      *newline = '\0';
    if(strcmp(lbuf, name) == 0)
      return 1;
  }
  return 0;
}

int main() {
  char buf[BSIZE];
  FILE* list = fopen("emlist.txt", "a+t");
  if(list == 0) {
    perror("could not open emlist.txt");
    exit(1);
  }
  while(1) {
    gets(buf); /* From stdin */
    if(alreadyInList(list, buf)) {
      printf("Already in list: %s", buf);
      fflush(stdout);
    }
    else {
      fseek(list, 0, SEEK_END);
      fprintf(list, "%s\n", buf);
      fflush(list);
      printf("%s added to list", buf);
      fflush(stdout);
    }
  }
} ///:~

This assumes that the C compiler accepts ‘//’ style comments. (Many do, and you can also compile this program with a C++ compiler.) If yours doesn’t, simply delete those comments.

The first function in the file checks to see whether the name you hand it as a second argument (a pointer to a char) is in the file. Here, the file is passed as a FILE pointer to an already-opened file (the file is opened inside main( )). The function fseek( ) moves around in the file; here it is used to move to the top of the file. fgets( ) reads a line from the file list into the buffer lbuf, not exceeding the buffer size BSIZE. This is inside a while loop so that each line in the file is read. Next, strchr( ) is used to locate the newline character so that it can be stripped off. Finally, strcmp( ) is used to compare the name you’ve passed into the function to the current line int the file. strcmp( ) returns zero if it finds a match. In this case the function exits and a one is returned to indicate that yes, the name was already in the list. (Note that the function returns as soon as it discovers the match, so it doesn’t waste time looking at the rest of the list.) If you get all the way through the list without a match, the function returns zero.

In main( ), the file is opened using fopen( ). The first argument is the file name and the second is the way to open the file; a+ means “Append, and open (or create if the file does not exist) for update at the end of the file.” The fopen( ) function returns a FILE pointer which, if it’s zero, means that the open was unsuccessful. This is dealt with by printing an error message with perror( ) and terminating the program with exit( ).

Assuming that the file was opened successfully, the program enters an infinite loop. The function call gets(buf) gets a line from standard input (which will be connected to the Java program, remember) and places it in the buffer buf. This is simply passed to the alreadyInList( ) function, and if it’s already in the list, printf( ) sends that message to standard output (where the Java program is listening). fflush( ) is a way to flush the output buffer.

If the name is not already in the list, fseek( ) is used to move to the end of the list and fprintf( ) “prints” the name to the end of the list. Then printf( ) is used to indicate that the name was added to the list (again flushing standard output) and the infinite loop goes back to waiting for a new name.

Remember that you usually cannot compile this program on your computer and load it onto the Web server machine, since that machine might use a different processor and operating system. For example, my Web server runs on an Intel processor but it uses Linux, so I must download the source code and compile using remote commands (via telnet) with the C compiler that comes with the Linux distribution.

The Java program

This program will first start the C program above and make the necessary connections to talk to it. Then it will create a datagram socket that will be used to listen for datagram packets from the applet.

//: NameCollector.java
// Extracts email names from datagrams and stores
// them inside a file, using Java 1.02.
import java.net.*;
import java.io.*;
import java.util.*;

public class NameCollector {
  final static int COLLECTOR_PORT = 8080;
  final static int BUFFER_SIZE = 1000;
  byte[] buf = new byte[BUFFER_SIZE];
  DatagramPacket dp = 
    new DatagramPacket(buf, buf.length);
  // Can listen & send on the same socket:
  DatagramSocket socket;
  Process listmgr;
  PrintStream nameList;
  DataInputStream addResult;
  public NameCollector() {
    try {
      listmgr =
        Runtime.getRuntime().exec("listmgr.exe");
      nameList = new PrintStream(
        new BufferedOutputStream(
          listmgr.getOutputStream()));
      addResult = new DataInputStream(
        new BufferedInputStream(
          listmgr.getInputStream()));

    } catch(IOException e) {
      System.err.println(
        "Cannot start listmgr.exe");
      System.exit(1);
    }
    try {
      socket =
        new DatagramSocket(COLLECTOR_PORT);
      System.out.println(
        "NameCollector Server started");
      while(true) {
        // Block until a datagram appears:
        socket.receive(dp);
        String rcvd = new String(dp.getData(),
            0, 0, dp.getLength());
        // Send to listmgr.exe standard input:
        nameList.println(rcvd.trim());
        nameList.flush();
        byte[] resultBuf = new byte[BUFFER_SIZE];
        int byteCount = 
          addResult.read(resultBuf);
        if(byteCount != -1) {
          String result = 
            new String(resultBuf, 0).trim();
          // Extract the address and port from 
          // the received datagram to find out 
          // where to send the reply:
          InetAddress senderAddress =
            dp.getAddress();
          int senderPort = dp.getPort();
          byte[] echoBuf = new byte[BUFFER_SIZE];
          result.getBytes(
            0, byteCount, echoBuf, 0);
          DatagramPacket echo =
            new DatagramPacket(
              echoBuf, echoBuf.length,
              senderAddress, senderPort);
          socket.send(echo);
        }
        else
          System.out.println(
            "Unexpected lack of result from " +
            "listmgr.exe");
      }
    } catch(SocketException e) {
      System.err.println("Can't open socket");
      System.exit(1);
    } catch(IOException e) {
      System.err.println("Communication error");
      e.printStackTrace();
    }
  }
  public static void main(String[] args) {
    new NameCollector();
  }
} ///:~

The first definitions in NameCollector should look familiar: the port is chosen, a datagram packet is created, and there’s a handle to a DatagramSocket. The next three definitions concern the connection to the C program: a Process object is what comes back when the C program is fired up by the Java program, and that Process object produces the InputStream and OutputStream objects representing, respectively, the standard output and standard input of the C program. These must of course be “wrapped” as is usual with Java IO, so we end up with a PrintStream and DataInputStream.

All the work for this program happens inside the constructor. To start up the C program, the current Runtime object is procured. This is used to call exec( ), which returns the Process object. You can see that there are simple calls to produce the streams from the Process object: getOutputStream( ) and getInputStream( ). From this point on, all you need to consider is sending data to the stream nameList and getting the results from addResult.

As before, a DatagramSocket is connected to a port. Inside the infinite while loop, the program calls receive( ), which blocks until a datagram shows up. When the datagram appears, its contents are extracted into the String rcvd. This is trimmed to remove white space at each end and sent to the C program in the line:

nameList.println(rcvd.trim());

This is only possible because Java’s exec( ) provides access to any executable that reads from standard input and writes to standard output. There are other ways to talk to non-Java code, which are discussed in Appendix A.

Capturing the result from the C program is slightly more complicated. You must call read( ) and provide a buffer where the results will be placed. The return value for read( ) is the number of bytes that came from the C program, and if this value is -1 it means that something is wrong. Otherwise, the resultBuf is turned into a String and the spaces are trimmed off. This string is then placed into a DatagramPacket as before and shipped back to the same address that sent the request in the first place. Note that the sender’s address is part of the DatagramPacket we received.

Remember that although the C program must be compiled on the Web server, the Java program can be compiled anywhere since the resulting byte codes will be the same regardless of the platform on which the program will be running.

The NameSender applet

As mentioned earlier, the applet must be written with Java 1.0 so that it will run on the largest number of browsers, so it’s best if the number of classes produced is minimized. Thus, instead of using the Dgram class developed earlier, all of the datagram manipulations will be placed in line. In addition, the applet needs a thread to listen for the reply from the server, and instead of making this a separate thread it’s integrated into the applet by implementing the Runnable interface. This isn’t as easy to read, but it produces a one-class (and one-server-hit) applet:

//: NameSender.java
// An applet that sends an email address
// as a datagram, using Java 1.02.
import java.awt.*;
import java.applet.*;
import java.net.*;
import java.io.*;

public class NameSender extends Applet 
    implements Runnable {
  private Thread pl = null;
  private Button send = new Button(
    "Add email address to mailing list");
  private TextField t = new TextField(
    "type your email address here", 40);
  private String str = new String();
  private Label 
    l = new Label(), l2 = new Label();
  private DatagramSocket s; 
  private InetAddress hostAddress;
  private byte[] buf = 
    new byte[NameCollector.BUFFER_SIZE];
  private DatagramPacket dp =
    new DatagramPacket(buf, buf.length);
  private int vcount = 0;
  public void init() {
    setLayout(new BorderLayout());
    Panel p = new Panel();
    p.setLayout(new GridLayout(2, 1));
    p.add(t);
    p.add(send);
    add("North", p);
    Panel labels = new Panel();
    labels.setLayout(new GridLayout(2, 1));
    labels.add(l);
    labels.add(l2);
    add("Center", labels);
    try {
      // Auto-assign port number:
      s = new DatagramSocket();
      hostAddress = InetAddress.getByName(
        getCodeBase().getHost());
    } catch(UnknownHostException e) {
      l.setText("Cannot find host");
    } catch(SocketException e) {
      l.setText("Can't open socket");
    } 
    l.setText("Ready to send your email address");
  }
  public boolean action (Event evt, Object arg) {
    if(evt.target.equals(send)) {
      if(pl != null) {
        // pl.stop(); Deprecated in Java 1.2
        Thread remove = pl;
        pl = null;
        remove.interrupt();
      }
      l2.setText("");
      // Check for errors in email name:
      str = t.getText().toLowerCase().trim();
      if(str.indexOf(' ') != -1) {
        l.setText("Spaces not allowed in name");
        return true;
      }
      if(str.indexOf(',') != -1) {
        l.setText("Commas not allowed in name");
        return true;
      }
      if(str.indexOf('@') == -1) {
        l.setText("Name must include '@'");
        l2.setText("");
        return true;
      }
      if(str.indexOf('@') == 0) {
        l.setText("Name must preceed '@'");
        l2.setText("");
        return true;
      }
      String end = 
        str.substring(str.indexOf('@'));
      if(end.indexOf('.') == -1) {
        l.setText("Portion after '@' must " +
          "have an extension, such as '.com'");
        l2.setText("");
        return true;
      }
      // Everything's OK, so send the name. Get a
      // fresh buffer, so it's zeroed. For some 
      // reason you must use a fixed size rather
      // than calculating the size dynamically:
      byte[] sbuf = 
        new byte[NameCollector.BUFFER_SIZE];
      str.getBytes(0, str.length(), sbuf, 0);
      DatagramPacket toSend =
        new DatagramPacket(
          sbuf, 100, hostAddress,
          NameCollector.COLLECTOR_PORT);
      try {
        s.send(toSend);
      } catch(Exception e) {
        l.setText("Couldn't send datagram");
        return true;
      }
      l.setText("Sent: " + str);
      send.setLabel("Re-send");
      pl = new Thread(this);
      pl.start();
      l2.setText(
        "Waiting for verification " + ++vcount);
    }
    else return super.action(evt, arg);
    return true;
  }
  // The thread portion of the applet watches for
  // the reply to come back from the server:
  public void run() {
    try {
      s.receive(dp);
    } catch(Exception e) {
      l2.setText("Couldn't receive datagram");
      return;
    }
    l2.setText(new String(dp.getData(),
      0, 0, dp.getLength()));
  }
} ///:~

The UI for the applet is quite simple. There’s a TextField in which you type your email address, and a Button to send the email address to the server. Two Labels are used to report status back to the user.

By now you can recognize the DatagramSocket, InetAddress, buffer, and DatagramPacket as trappings of the network connection. Lastly, you can see the run( ) method that implements the thread portion so the applet can listen for the reply sent back by the server.

The init( ) method sets up the GUI with the familiar layout tools, then creates the DatagramSocket that will be used both for sending and receiving datagrams.

The action( ) method (remember, we’re confined to Java 1.0 now, so we can’t use any slick inner listener classes) watches only to see if you press the “send” button. When the button is pressed, the first action is to check the Thread pl to see if it’s null. If it’s not null, there’s a live thread running. The first time the message is sent a thread is started up to watch for the reply. Thus, if a thread is running, it means this is not the first time the user has tried to send the message. The pl handle is set to null and the old listener is interrupted. (This is the preferred approach, since stop( ) is deprecated in Java 1.2 as explained in the previous chapter.)

Regardless of whether this is the first time the button was pressed, the text in l2 is erased.

The next group of statements checks the email name for errors. The String.indexOf( ) method is used to search for illegal characters, and if one is found it is reported to the user. Note that all of this happens without any network activity, so it’s fast and it doesn’t bog down the Internet.

Once the name is verified, it is packaged into a datagram and sent to the host address and port number in the same way that was described in the earlier datagram example. The first label is changed to show you that the send has occurred, and the button text is changed so that it reads “re-send.” At this point, the thread is started up and the second label informs you that the applet is waiting for a reply from the server.

The run( ) method for the thread uses the DatagramSocket that lives in NameSender to receive( ), which blocks until the datagram packet comes from the server. The resulting packet is placed into NameSender’s DatagramPacket dp. The data is retrieved from the packet and placed into the second label in NameSender. At this point, the thread terminates and becomes dead. If the reply doesn’t come back from the server in a reasonable amount of time, the user might become impatient and press the button again, thus terminating the current thread (and, after re-sending the data, starting a new one). Because a thread is used to listen for the reply, the user still has full use of the UI.

The Web page

Of course, the applet must go inside a Web page. Here is the complete Web page; you can see that it’s intended to be used to automatically collect names for my mailing list:

<HTML>
<HEAD>
<META CONTENT="text/html">
<TITLE>
Add Yourself to Bruce Eckel's Java Mailing List
</TITLE>
</HEAD>
<BODY LINK="#0000ff" VLINK="#800080" BGCOLOR="#ffffff">
<FONT SIZE=6><P>
Add Yourself to Bruce Eckel's Java Mailing List
</P></FONT>
The applet on this page will automatically add your email address to the mailing list, so you will receive update information about changes to the online version of "Thinking in Java," notification when the book is in print, information about upcoming Java seminars, and notification about the “Hands-on Java Seminar” Multimedia CD. Type in your email address and press the button to automatically add yourself to this mailing list. <HR>
<applet code=NameSender width=400 height=100>
</applet>
<HR>
If after several tries, you do not get verification it means that the Java application on the server is having problems. In this case, you can add yourself to the list by sending email to 
<A HREF="mailto:Bruce@EckelObjects.com">
Bruce@EckelObjects.com</A>
</BODY>
</HTML>

The applet tag is quite trivial, no different from the first one presented in Chapter 13.

Problems with this approach

This certainly seems like an elegant approach. There’s no CGI programming and so there are no delays while the server starts up a CGI program. The datagram approach seems to produce a nice quick response. In addition, when Java 1.1 is available everywhere, the server portion can be written entirely in Java. (Although it’s quite interesting to see how easy it is to connect to a non-Java program using standard input and output.)

There are problems, however. One problem is rather subtle: since the Java application is running constantly on the server and it spends most of its time blocked in the Datagram.receive( ) method, there might be some CPU hogging going on. At least, that’s the way it appeared on the server where I was experimenting. On the other hand, there wasn’t much else happening on that server, and starting the program using “nice” (a Unix program to prevent a process from hogging the CPU) or its equivalent could solve the problem if you have a more heavily-loaded server. In any event, it’s worth keeping your eye on an application like this – a blocked receive( ) could hog the CPU.

The second problem is a show stopper. It concerns firewalls. A firewall is a machine that sits between your network and the Internet. It monitors all traffic coming in from the Internet and going out to the Internet, and makes sure that traffic conforms to what it expects.

Firewalls are conservative little beasts. They demand strict conformance to all the rules, and if you’re not conforming they assume that you’re doing something sinful and shut you out (not quite so bad as the Spanish Inquisition, but close). For example, if you are on a network behind a firewall and you start connecting to the Internet using a Web browser, the firewall expects that all your transactions will connect to the server using the accepted http port, which is 80. Now along comes this Java applet NameSender, which is trying to send a datagram to port 8080, which is way outside the range of the “protected” ports 0-1024. The firewall naturally assumes the worst – that someone has a virus – and it doesn’t allow the transaction to happen.

As long as your customers have raw connections to the Internet (for example, using a typical Internet service provider) there’s no problem, but you might have some important customers dwelling behind firewalls, and they won’t be able to use your program.

This is rather disheartening after learning so much Java, because it would seem that you must give up Java on the server and learn how to write CGI scripts in C or Perl. But as it turns out, despair is not in order.

One scenario is part of Sun’s grand scheme. If everything goes as planned, Web servers will be equipped with servlet servers. These will take a request from the client (going through the firewall-accepted port 80) and instead of starting up a CGI program they will start up a Java program called a servlet. This is a little application that’s designed to run only on the server. A servlet server will automatically start up the servlet to handle the client request, which means you can write all your programs in Java (further enabling the “100 percent pure Java initiative”). It is admittedly an appealing idea: once you’re comfortable with Java, you don’t have to switch to a more primitive language to handle requests on the server.

Since it’s only for handling requests on the server, the servlet API has no GUI abilities. This fits quite well with NameCollector.java, which doesn’t have a GUI anyway.

At this writing, a low-cost servlet server was available from java.sun.com. In addition, Sun is encouraging other Web server manufacturers to add servlet capabilities to their servers.

Connecting Java to CGI

A Java program can send a CGI request to a server just like an HTML page can. As with HTML pages, this request can be either a GET or a POST. In addition, the Java program can intercept the output of the CGI program, so you don’t have to rely on the program to format a new page and force the user to back up from one page to another if something goes wrong. In fact, the appearance of the program can be the same as the previous version.

It also turns out that the code is simpler, and that CGI isn’t difficult to write after all. (An innocent statement that’s true of many things – after you understand them.) So in this section you’ll get a crash course in CGI programming. To solve the general problem, some CGI tools will be created in C++ that will allow you to easily write a CGI program to solve any problem. The benefit to this approach is portability – the example you are about to see will work on any system that supports CGI, and there’s no problem with firewalls.

This example also works out the basics of creating any connection with applets and CGI programs, so you can easily adapt it to your own projects.

Encoding data for CGI

In this version, the name and the email address will be collected and stored in the file in the form:

First Last <email@domain.com>;

This is a convenient form for many mailers. Since two fields are being collected, there are no shortcuts because CGI has a particular format for encoding the data in fields. You can see this for yourself if you make an ordinary HTML page and add the lines:

<Form method="GET" ACTION="/cgi-bin/Listmgr2.exe">
<P>Name: <INPUT TYPE = "text" NAME = "name" 
VALUE = "" size = "40"></p>
<P>Email Address: <INPUT TYPE = "text" 
NAME = "email" VALUE = "" size = "40"></p>
<p><input type = "submit" name = "submit" > </p>
</Form>

This creates two data entry fields called name and email, along with a submit button that collects the data and sends it to a CGI program. Listmgr2.exe is the name of the executable program that resides in the directory that’s typically called “cgi-bin” on your Web server.[61] (If the named program is not in the cgi-bin directory, you won’t see any results.) If you fill out this form and press the “submit” button, you will see in the URL address window of the browser something like:

http://www.myhome.com/cgi-bin/Listmgr2.exe?
name=First+Last&email=email@domain.com&submit=Submit

(Without the line break, of course). Here you see a little bit of the way that data is encoded to send to CGI. For one thing, spaces are not allowed (since spaces typically separate command-line arguments). Spaces are replaced by ‘+’ signs. In addition, each field contains the field name (which is determined by the HTML page) followed by an ‘=’ and the field data, and terminated by a ‘&’.

At this point, you might wonder about the ‘+’, ‘=,’ and ‘&’. What if those are used in the field, as in “John & Marsha Smith”? This is encoded to:

John+%26+Marsha+Smith

That is, the special character is turned into a ‘%’ followed by its ASCII value in hex.

Fortunately, Java has a tool to perform this encoding for you. It’s a static method of the class URLEncoder called encode( ). You can experiment with this method using the following program:

//: EncodeDemo.java
// Demonstration of URLEncoder.encode()
import java.net.*;

public class EncodeDemo {
  public static void main(String[] args) {
    String s = "";
    for(int i = 0; i < args.length; i++)
      s += args[i] + " ";
    s = URLEncoder.encode(s.trim());
    System.out.println(s);
  }
} ///:~

This takes the command-line arguments and combines them into a string of words separated by spaces (the final space is removed using String.trim( )). These are then encoded and printed.

To invoke a CGI program, all the applet needs to do is collect the data from its fields (or wherever it needs to collect the data from), URL-encode each piece of data, and then assemble it into a single string, placing the name of each field followed by an ‘=’, followed by the data, followed by an ‘&’. To form the entire CGI command, this string is placed after the URL of the CGI program and a ‘?’. That’s all it takes to invoke any CGI program, and as you’ll see you can easily do it within an applet.

The applet

The applet is actually considerably simpler than NameSender.java, partly because it’s so easy to send a GET request and also because no thread is required to wait for the reply. There are now two fields instead of one, but you’ll notice that much of the applet looks familiar, from NameSender.java.

//: NameSender2.java
// An applet that sends an email address
// via a CGI GET, using Java 1.02.
import java.awt.*;
import java.applet.*;
import java.net.*;
import java.io.*;

public class NameSender2 extends Applet {
  final String CGIProgram = "Listmgr2.exe";
  Button send = new Button(
    "Add email address to mailing list");
  TextField name = new TextField(
    "type your name here", 40),
    email = new TextField(
    "type your email address here", 40);
  String str = new String();
  Label l = new Label(), l2 = new Label();
  int vcount = 0;
  public void init() {
    setLayout(new BorderLayout());
    Panel p = new Panel();
    p.setLayout(new GridLayout(3, 1));
    p.add(name);
    p.add(email);
    p.add(send);
    add("North", p);
    Panel labels = new Panel();
    labels.setLayout(new GridLayout(2, 1));
    labels.add(l);
    labels.add(l2);
    add("Center", labels);
    l.setText("Ready to send email address");
  }
  public boolean action (Event evt, Object arg) {
    if(evt.target.equals(send)) {
      l2.setText("");
      // Check for errors in data:
      if(name.getText().trim()
         .indexOf(' ') == -1) {
        l.setText(
          "Please give first and last name");
        l2.setText("");
        return true;
      }
      str = email.getText().trim();
      if(str.indexOf(' ') != -1) {
        l.setText(
          "Spaces not allowed in email name");
        l2.setText("");
        return true;
      }
      if(str.indexOf(',') != -1) {
        l.setText(
          "Commas not allowed in email name");
        return true;
      }
      if(str.indexOf('@') == -1) {
        l.setText("Email name must include '@'");
        l2.setText("");
        return true;
      }
      if(str.indexOf('@') == 0) {
        l.setText(
          "Name must preceed '@' in email name");
        l2.setText("");
        return true;
      }
      String end = 
        str.substring(str.indexOf('@'));
      if(end.indexOf('.') == -1) {
        l.setText("Portion after '@' must " +
          "have an extension, such as '.com'");
        l2.setText("");
        return true;
      }
      // Build and encode the email data:
      String emailData = 
        "name=" + URLEncoder.encode(
          name.getText().trim()) +
        "&email=" + URLEncoder.encode(
          email.getText().trim().toLowerCase()) +
        "&submit=Submit";
      // Send the name using CGI's GET process:
      try {
        l.setText("Sending...");
        URL u = new URL(
          getDocumentBase(), "cgi-bin/" +
          CGIProgram + "?" + emailData);
        l.setText("Sent: " + email.getText());
        send.setLabel("Re-send");
        l2.setText(
          "Waiting for reply " + ++vcount);
        DataInputStream server =
          new DataInputStream(u.openStream());
        String line;
        while((line = server.readLine()) != null)
          l2.setText(line);
      } catch(MalformedURLException e) {
        l.setText("Bad URl");
      } catch(IOException e) {
        l.setText("IO Exception");
      } 
    }
    else return super.action(evt, arg);
    return true;
  }
} ///:~

The name of the CGI program (which you’ll see later) is Listmgr2.exe. Many Web servers are Unix machines (mine runs Linux) that don’t traditionally use the .exe extension for their executable programs, but you can call the program anything you want under Unix. By using the .exe extension the program can be tested without change under both Unix and Win32.

As before, the applet sets up its user interface (with two fields this time instead of one). The only significant difference occurs inside the action( ) method, which handles the button press. After the name has been checked, you see the lines:

      String emailData = 
        "name=" + URLEncoder.encode(
          name.getText().trim()) +
        "&email=" + URLEncoder.encode(
          email.getText().trim().toLowerCase()) +
        "&submit=Submit";
      // Send the name using CGI's GET process:
      try {
        l.setText("Sending...");
        URL u = new URL(
          getDocumentBase(), "cgi-bin/" +
          CGIProgram + "?" + emailData);
        l.setText("Sent: " + email.getText());
        send.setLabel("Re-send");
        l2.setText(
          "Waiting for reply " + ++vcount);
        DataInputStream server =
          new DataInputStream(u.openStream());
        String line;
        while((line = server.readLine()) != null)
          l2.setText(line);
        // ...

The name and email data are extracted from their respective text boxes, and the spaces are trimmed off both ends using trim( ). The email name is forced to lower case so all email addresses in the list can be accurately compared (to prevent accidental duplicates based on capitalization). The data from each field is URL-encoded, and then the GET string is assembled in the same way that an HTML page would do it. (This way you can use a Java applet in concert with any existing CGI program designed to work with regular HTML GET requests.)

At this point, some Java magic happens: if you want to connect to any URL, just create a URL object and hand the address to the constructor. The constructor makes the connection with the server (and, with Web servers, all the action happens in making the connection, via the string used as the URL). In this case, the URL points to the cgi-bin directory of the current Web site (the base address of the current Web site is produced with getDocumentBase( )). When the Web server sees “cgi-bin” in a URL, it expects that to be followed by the name of the program inside the cgi-bin directory that you want it to run. Following the program name is a question mark and the argument string that the CGI program will look for in the QUERY_STRING environment variable, as you’ll see.

Usually when you make any sort of request, you get back (you’re forced to accept in return) an HTML page. With Java URL objects, however, you can intercept anything that comes back from the CGI program by getting an InputStream from the URL object. This is performed with the URL openStream( ) method, which is in turn wrapped in a DataInputStream. Then you can read lines, and when readLine( ) returns null the CGI program has finished its output.

The CGI program you’re about to see returns only one line, a string indicating success or failure (and the details of the failure). This line is captured and placed into the second Label field so the user can see the results.

Displaying a Web page from within an applet

It’s also possible for the applet to display the result of the CGI program as a Web page, just as if it were running in normal HTML mode. You can do this with the following line:

getAppletContext().showDocument(u);

in which u is the URL object. Here’s a simple example that redirects you to another Web page. The page happens to be the output of a CGI program, but you can as easily go to an ordinary HTML page, so you could build on this applet to produce a password-protected gateway to a particular portion of your Web site:

//: ShowHTML.java
import java.awt.*;
import java.applet.*;
import java.net.*;
import java.io.*;

public class ShowHTML extends Applet {
  static final String CGIProgram = "MyCGIProgram";
  Button send = new Button("Go");
  Label l = new Label();
  public void init() {
    add(send);
    add(l);
  }
  public boolean action (Event evt, Object arg) {
    if(evt.target.equals(send)) {
      try {
        // This could be an HTML page instead of
        // a CGI program. Notice that this CGI 
        // program doesn't use arguments, but 
        // you can add them in the usual way.
        URL u = new URL(
          getDocumentBase(), 
          "cgi-bin/" + CGIProgram);
        // Display the output of the URL using
        // the Web browser, as an ordinary page:
        getAppletContext().showDocument(u);
      } catch(Exception e) {
        l.setText(e.toString());
      } 
    }
    else return super.action(evt, arg);
    return true;
  }
} ///:~

The beauty of the URL class is how much it shields you from. You can connect to Web servers without knowing much at all about what’s going on under the covers.

The CGI program in C++

At this point you could follow the previous example and write the CGI program for the server using ANSI C. One argument for doing this is that ANSI C can be found virtually everywhere. However, C++ has become quite ubiquitous, especially in the form of the GNU C++ Compiler[62] (g++) that can be downloaded free from the Internet for virtually any platform (and often comes pre-installed with operating systems such as Linux). As you will see, this means that you can get the benefit of object-oriented programming in a CGI program.

To avoid throwing too many new concepts at you all at once, this program will not be a “pure” C++ program; some code will be written in plain C even though C++ alternatives exist. This isn’t a significant issue because the biggest benefit in using C++ for this program is the ability to create classes. Since what we’re concerned with when parsing the CGI information is the field name-value pairs, one class (Pair) will be used to represent a single name-value pair and a second class (CGI_vector) will automatically parse the CGI string into Pair objects that it will hold (as a vector) so you can fetch each Pair out at your leisure.

This program is also interesting because it demonstrates some of the pluses and minuses of C++ in contrast with Java. You’ll see some similarities; for example the class keyword. Access control has identical keywords public and private, but they’re used differently: they control a block instead of a single method or field (that is, if you say private: each following definition is private until you say public:). Also, when you create a class, all the definitions automatically default to private.

One of the reasons for using C++ here is the convenience of the C++ Standard Template Library. Among other things, the STL contains a vector class. This is a C++ template, which means that it will be configured at compile time so it will hold objects of only a particular type (in this case, Pair objects). Unlike the Java Vector, which will accept anything, the C++ vector template will cause a compile-time error message if you try to put anything but a Pair object into the vector, and when you get something out of the vector it will automatically be a Pair object, without casting. Thus, the checking happens at compile time and produces a more robust program. In addition, the program can run faster since you don’t have to perform run-time casts. The vector also overloads the operator[] so you have a convenient syntax for extracting Pair objects. The vector template will be used in the creation of CGI_vector, which you’ll see is a fairly short definition considering how powerful it is.

On the down side, look at the complexity of the definition of Pair in the following code. Pair has more method definitions than you’re used to seeing in Java code, because the C++ programmer must know how to control copying with the copy-constructor and assignment with the overloaded operator=. As described in Chapter 12, occasionally you need to concern yourself with similar things in Java, but in C++ you must be aware of them almost constantly.

The project will start with a reusable portion, which consists of Pair and CGI_vector in a C++ header file. Technically, you shouldn’t cram this much into a header file, but for these examples it doesn’t hurt anything and it will also look more Java-like, so it will be easier for you to read:

//: CGITools.h
// Automatically extracts and decodes data
// from CGI GETs and POSTs. Tested with GNU C++ 
// (available for most server machines).
#include <string.h>
#include <vector> // STL vector
using namespace std;

// A class to hold a single name-value pair from
// a CGI query. CGI_vector holds Pair objects and
// returns them from its operator[].
class Pair {
  char* nm;
  char* val;
public:
  Pair() { nm = val = 0; }
  Pair(char* name, char* value) {
    // Creates new memory:
    nm = decodeURLString(name);
    val = decodeURLString(value);
  }
  const char* name() const { return nm; }
  const char* value() const { return val; }
  // Test for "emptiness"
  bool empty() const {
    return (nm == 0) || (val == 0);
  }
  // Automatic type conversion for boolean test:
  operator bool() const {
    return (nm != 0) && (val != 0);
  }
  // The following constructors & destructor are
  // necessary for bookkeeping in C++.
  // Copy-constructor:
  Pair(const Pair& p) {
    if(p.nm == 0 || p.val == 0) {
      nm = val = 0;
    } else {
      // Create storage & copy rhs values:
      nm = new char[strlen(p.nm) + 1];
      strcpy(nm, p.nm);
      val = new char[strlen(p.val) + 1];
      strcpy(val, p.val);
    }
  }
  // Assignment operator:
  Pair& operator=(const Pair& p) {
    // Clean up old lvalues:
    delete nm;
    delete val;
    if(p.nm == 0 || p.val == 0) {
      nm = val = 0;
    } else {
      // Create storage & copy rhs values:
      nm = new char[strlen(p.nm) + 1];
      strcpy(nm, p.nm);
      val = new char[strlen(p.val) + 1];
      strcpy(val, p.val);
    }
    return *this;
  } 
  ~Pair() { // Destructor
    delete nm; // 0 value OK
    delete val;
  }
  // If you use this method outide this class, 
  // you're responsible for calling 'delete' on
  // the pointer that's returned:
  static char* 
  decodeURLString(const char* URLstr) {
    int len = strlen(URLstr);
    char* result = new char[len + 1];
    memset(result, len + 1, 0);
    for(int i = 0, j = 0; i <= len; i++, j++) {
      if(URLstr[i] == '+')
        result[j] = ' ';
      else if(URLstr[i] == '%') {
        result[j] =
          translateHex(URLstr[i + 1]) * 16 +
          translateHex(URLstr[i + 2]);
        i += 2; // Move past hex code
      } else // An ordinary character
        result[j] = URLstr[i];
    }
    return result;
  }
  // Translate a single hex character; used by
  // decodeURLString():
  static char translateHex(char hex) {
    if(hex >= 'A')
      return (hex & 0xdf) - 'A' + 10;
    else
      return hex - '0';
  }
};

// Parses any CGI query and turns it
// into an STL vector of Pair objects:
class CGI_vector : public vector<Pair> {
  char* qry;
  const char* start; // Save starting position
  // Prevent assignment and copy-construction:
  void operator=(CGI_vector&);
  CGI_vector(CGI_vector&);
public:
  // const fields must be initialized in the C++
  // "Constructor initializer list":
  CGI_vector(char* query) :
      start(new char[strlen(query) + 1]) {
    qry = (char*)start; // Cast to non-const
    strcpy(qry, query);
    Pair p;
    while((p = nextPair()) != 0)
      push_back(p);
  }
  // Destructor:
  ~CGI_vector() { delete start; }
private:
  // Produces name-value pairs from the query 
  // string. Returns an empty Pair when there's 
  // no more query string left:
  Pair nextPair() {
    char* name = qry;
    if(name == 0 || *name == '\0')
      return Pair(); // End, return null Pair
    char* value = strchr(name, '=');
    if(value == 0)
      return Pair(); // Error, return null Pair
    // Null-terminate name, move value to start
    // of its set of characters:
    *value = '\0';
    value++;
    // Look for end of value, marked by '&':
    qry = strchr(value, '&');
    if(qry == 0) qry = ""; // Last pair found
    else {
      *qry = '\0'; // Terminate value string
      qry++; // Move to next pair
    }
    return Pair(name, value);
  }
}; ///:~

After the #include statements, you see a line that says:

using namespace std;

Namespaces in C++ solve one of the problems taken care of by the package scheme in Java: hiding library names. The std namespace refers to the Standard C++ library, and vector is in this library so the line is required.

The Pair class starts out looking pretty simple: it just holds two (private) character pointers, one for the name and one for the value. The default constructor simply sets these pointers to zero, since in C++ an object’s memory isn’t automatically zeroed. The second constructor calls the method decodeURLString( ) that produces a decoded string in newly-allocated heap memory. This memory must be managed and destroyed by the object, as you will see in the destructor. The name( ) and value( ) methods produce read-only pointers to the respective fields. The empty( ) method is a way for you to ask the Pair object whether either of its fields are empty; it returns a bool, which is C++’s built-in primitive Boolean data type. The operator bool( ) uses a special case of C++ operator overloading, which allows you to control automatic type conversion. If you have a Pair object called p and you use it in an expression in which a Boolean result is expected, such as if(p) { //..., then the compiler will recognize that it has a Pair and it needs a Boolean, so it will automatically call operator bool( ) to perform the necessary conversion.

The next three methods are part of the bookkeeping that’s necessary when you create a class in C++. In the so-called “canonical form” for a C++ class, you must define the necessary “ordinary” constructors as well as a copy-constructor and the assignment operator, operator= (and the destructor, to clean up the memory). You must define these because they can be quietly called by the compiler when you pass objects in and out of a function (this calls the copy-constructor) or when you assign objects (the assignment operator). Once you’ve gone through the trouble to understand how the copy-constructor and assignment operator work you can write robust classes in C++, but it is a bit of a learning experience.[63]

The copy-constructor Pair(const Pair&) is automatically called whenever you pass an object into or out of a function by value. That is, you aren’t passing the address of the object you’re making a copy of the whole object inside the function frame. This isn’t an option in Java since you pass only handles, thus there’s no copy-constructor in Java. (If you want to make a local duplicate, you clone( ) the object – see Chapter 12.) Likewise, if you assign a handle in Java, it’s simply copied. But assignment in C++ means that the entire object is copied. In the copy-constructor, you create new storage and copy the source data, but with the assignment operator you must release the old storage before allocating new storage. What you’re seeing is probably the worst-case complexity scenario for a C++ class, but it’s one of the reasons Java proponents can argue that Java is a lot simpler than C++. In Java you pass handles and there’s a garbage collector, so you don’t have to do this kind of thing.

This isn’t quite the whole story. The Pair class is using char* for nm and val, and the worst-case complexity occurs primarily around pointers. If you use the more modern Standard C++ string class instead of char*, things get much simpler (however, not all compilers have caught up enough to come with string). Then, the first part of Pair looks like this:

class Pair {
  string nm;
  string val;
public:
  Pair() { }
  Pair(char* name, char* value) {
    nm = decodeURLString(name);
    val = decodeURLString(value);
  }
  const char* name() const { return nm.c_str(); }
  const char* value() const { 
    return val.c_str(); 
  }
  // Test for "emptiness"
  bool empty() const {
    return (nm.length() == 0) 
      || (val.length() == 0);
  }
  // Automatic type conversion for boolean test:
  operator bool() const {
    return (nm.length() != 0) 
      && (val.length() != 0);
  }

(Also, for this case decodeURLString( ) returns a string instead of a char*.) You don’t need to define a copy-constructor, operator=, or destructor because the compiler does that for you, and does it correctly. But even if it sometimes works automatically, C++ programmers must still know the details of copy-construction and assignment.

The remainder of the Pair class consists of the two methods decodeURLString( ) and a helper method translateHex( ), which is used by decodeURLString( ). (Note that translateHex( ) does not guard against bad user input such as “%1H.”) After allocating adequate storage (which must be released by the destructor), decodeURLString( ) moves through and replaces each ‘+’ with a space and each hex code (beginning with a ‘%’) with the appropriate character.

CGI_vector parses and holds an entire CGI GET command. It is inherited from the STL vector, which is instantiated to hold Pairs. Inheritance in C++ is denoted by using a colon at the point you’d say extends in Java. In addition, inheritance defaults to private so you’ll almost always need to use the public keyword as was done here. You can also see that CGI_vector has a copy-constructor and an operator=, but they’re both declared as private. This is to prevent the compiler from synthesizing the two functions (which it will do if you don’t declare them yourself), but it also prevents the client programmer from passing a CGI_vector by value or from using assignment.

CGI_vector’s job is to take the QUERY_STRING and parse it into name-value pairs, which it will do with the aid of Pair. First it copies the string into locally-allocated memory and keeps track of the starting address with the constant pointer start. (This is later used in the destructor to release the memory.) Then it uses its method nextPair( ) to parse the string into raw name-value pairs, delimited by ‘=’ and &’ signs. These are handed by nextPair( ) to the Pair constructor so nextPair( ) can return the Pair object, which is then added to the vector with push_back( ). When nextPair( ) runs out of QUERY_STRING, it returns zero.

Now that the basic tools are defined, they can easily be used in a CGI program, like this:

//: Listmgr2.cpp
// CGI version of Listmgr.c in C++, which 
// extracts its input via the GET submission 
// from the associated applet. Also works as
// an ordinary CGI program with HTML forms.
#include <stdio.h>
#include "CGITools.h"
const char* dataFile = "list2.txt";
const char* notify = "Bruce@EckelObjects.com";
#undef DEBUG

// Similar code as before, except that it looks
// for the email name inside of '<>':
int inList(FILE* list, const char* emailName) {
  const int BSIZE = 255;
  char lbuf[BSIZE];
  char emname[BSIZE];
  // Put the email name in '<>' so there's no
  // possibility of a match within another name:
  sprintf(emname, "<%s>", emailName);
  // Go to the beginning of the list:
  fseek(list, 0, SEEK_SET);
  // Read each line in the list:
  while(fgets(lbuf, BSIZE, list)) {
    // Strip off the newline: 
    char * newline = strchr(lbuf, '\n');
    if(newline != 0) 
      *newline = '\0';
    if(strstr(lbuf, emname) != 0)
      return 1;
  }
  return 0;
}

void main() {
  // You MUST print this out, otherwise the 
  // server will not send the response:
  printf("Content-type: text/plain\n\n");
  FILE* list = fopen(dataFile, "a+t");
  if(list == 0) {
    printf("error: could not open database. ");
    printf("Notify %s", notify);
    return;
  }
  // For a CGI "GET," the server puts the data
  // in the environment variable QUERY_STRING:
  CGI_vector query(getenv("QUERY_STRING"));
  #if defined(DEBUG)
  // Test: dump all names and values
  for(int i = 0; i < query.size(); i++) {
    printf("query[%d].name() = [%s], ", 
      i, query[i].name());
    printf("query[%d].value() = [%s]\n", 
      i, query[i].value());
  }
  #endif(DEBUG)
  Pair name = query[0];
  Pair email = query[1];
  if(name.empty() || email.empty()) {
    printf("error: null name or email");
    return;
  } 
  if(inList(list, email.value())) {
    printf("Already in list: %s", email.value());
    return;
  }
  // It's not in the list, add it:
  fseek(list, 0, SEEK_END);
  fprintf(list, "%s <%s>;\n", 
    name.value(), email.value());
  fflush(list);
  fclose(list);
  printf("%s <%s> added to list\n", 
    name.value(), email.value());
} ///:~

The alreadyInList( ) function is almost identical to the previous version, except that it assumes all email names are inside ‘<>’.

When you use the GET approach (which is normally done in the HTML METHOD tag of the FORM directive, but which is controlled here by the way the data is sent), the Web server grabs everything after the ‘?’ and puts in into the environment variable QUERY_STRING. So to read that information you have to get the value of QUERY_STRING, which you do using the standard C library function getenv( ). In main( ), notice how simple the act of parsing the QUERY_STRING is: you just hand it to the constructor for the CGI_vector object called query and all the work is done for you. From then on you can pull out the names and values from query as if it were an array. (This is because the operator[] is overloaded in vector.) You can see how this works in the debug code, which is surrounded by the preprocessor directives #if defined(DEBUG) and #endif(DEBUG).

Now it’s important to understand something about CGI. A CGI program is handed its input in one of two ways: through QUERY_STRING during a GET (as in this case) or through standard input during a POST. But a CGI program sends its output through standard output, typically using printf( ) in a C program. Where does this output go? Back to the Web server, which decides what to do with it. The server makes this decision based on the content-type header, which means that if the content-type header isn’t the first thing it sees, it won’t know what to do with the data. Thus, it’s essential that you start the output of all CGI programs with the content-type header.

In this case, we want the server to feed all the information directly back to the client program (which is our applet, waiting for its reply). The information should be unchanged, so the content-type is text/plain. Once the server sees this, it will echo all strings right back to the client. So each of the strings you see, three for error conditions and one for a successful add, will end up back at the applet.

Adding the email name uses the same code. In the case of the CGI script, however, there isn’t an infinite loop – the program just responds and then terminates. Each time a CGI request comes in, the program is started in response to that request, and then it shuts down. Thus there is no possibility of CPU hogging, and the only performance issue concerns starting the program up and opening the file, which are dwarfed by the overhead of the Web server as it handles the CGI request.

One of the advantages of this design is that, now that Pair and CGI_vector are defined, most of the work is done for you so you can easily create your own CGI program simply by modifying main( ). Eventually, servlet servers will probably be ubiquitous, but in the meantime C++ is still handy for creating fast CGI programs.

What about POST?

Using a GET is fine for many applications. However, GET passes its data to the CGI program through an environment variable, and some Web servers can run out of environment space with long GET strings (you should start worrying at about 200 characters). CGI provides a solution for this: POST. With POST, the data is encoded and concatenated the same way as a GET, but POST uses standard input to pass the encoded query string to the CGI program. All you have to do is determine the length of the query string, and this length is stored in the environment variable CONTENT_LENGTH. Once you know the length, you can allocate storage and read the precise number of bytes from standard input.

The Pair and CGI_vector from CGITools.h can be used as is for a CGI program that handles POSTs. The following listing shows how simple it is to write such a CGI program. In this example, “pure” C++ will be used so the stdio.h library will be dropped in favor of iostreams. With iostreams, two predefined objects are available: cin, which connects to standard input, and cout, which connects to standard output. There are several ways to read from cin and write to cout, but the following program take the common approach of using ‘<<’ to send information to cout, and the use of a member function (in this case, read( )) to read from cin.

//: POSTtest.cpp
// CGI_vector works as easily with POST as it
// does with GET. Written in "pure" C++.
#include <iostream.h>
#include "CGITools.h"

void main() {
  cout << "Content-type: text/plain\n" << endl;
  // For a CGI "POST," the server puts the length
  // of the content string in the environment 
  // variable CONTENT_LENGTH:
  char* clen = getenv("CONTENT_LENGTH");
  if(clen == 0) {
    cout << "Zero CONTENT_LENGTH" << endl;
    return;
  }
  int len = atoi(clen);
  char* query_str = new char[len + 1];
  cin.read(query_str, len);
  query_str[len] = '\0';
  CGI_vector query(query_str);
  // Test: dump all names and values
  for(int i = 0; i < query.size(); i++)
    cout << "query[" << i << "].name() = [" <<
      query[i].name() << "], " <<
      "query[" << i << "].value() = [" <<
      query[i].value() << "]" << endl;
  delete query_str; // Release storage
} ///:~

The getenv( ) function returns a pointer to a character string representing the content length. If this pointer is zero, the CONTENT_LENGTH environment variable has not been set, so something is wrong. Otherwise, the character string must be converted to an integer using the ANSI C library function atoi( ). The length is used with new to allocate enough storage to hold the query string (plus its null terminator), and then read( ) is called for cin. The read( ) function takes a pointer to the destination buffer and the number of bytes to read. The query_str is then null-terminated to indicate the end of the character string.

At this point, the query string is no different from a GET query string, so it is handed to the constructor for CGI_vector. The different fields in the vector are then available just as in the previous example.

To test this program, you must compile it in the cgi-bin directory of your host Web server. Then you can perform a simple test by writing an HTML page like this:

<HTML>
<HEAD>
<META CONTENT="text/html">
<TITLE>A test of standard HTML POST</TITLE>
</HEAD>
Test, uses standard html POST
<Form method="POST" ACTION="/cgi-bin/POSTtest">
<P>Field1: <INPUT TYPE = "text" NAME = "Field1" 
VALUE = "" size = "40"></p>
<P>Field2: <INPUT TYPE = "text" NAME = "Field2" 
VALUE = "" size = "40"></p>
<P>Field3: <INPUT TYPE = "text" NAME = "Field3" 
VALUE = "" size = "40"></p>
<P>Field4: <INPUT TYPE = "text" NAME = "Field4" 
VALUE = "" size = "40"></p>
<P>Field5: <INPUT TYPE = "text" NAME = "Field5" 
VALUE = "" size = "40"></p>
<P>Field6: <INPUT TYPE = "text" NAME = "Field6" 
VALUE = "" size = "40"></p>
<p><input type = "submit" name = "submit" > </p>
</Form>
</HTML>

When you fill this out and submit it, you’ll get back a simple text page containing the parsed results, so you can see that the CGI program works correctly.

Of course, it’s a little more interesting to submit the data using an applet. Submitting POST data is a different process, however. After you invoke the CGI program in the usual way, you must make a direct connection to the server so you can feed it the query string. The server then turns around and feeds the query string to the CGI program via standard input.

To make a direct connection to the server, you must take the URL you’ve created and call openConnection( ) to produce a URLConnection. Then, because a URLConnection doesn’t usually allow you to send data to it, you must call the magic function setDoOutput(true) along with setDoInput(true) and setAllowUserInteraction(false).[64] Finally, you can call getOutputStream( ) to produce an OutputStream, which you wrap inside a DataOutputStream so you can talk to it conveniently. Here’s an applet that does just that, after collecting data from its various fields:

//: POSTtest.java
// An applet that sends its data via a CGI POST
import java.awt.*;
import java.applet.*;
import java.net.*;
import java.io.*;

public class POSTtest extends Applet {
  final static int SIZE = 10;
  Button submit = new Button("Submit");
  TextField[] t = new TextField[SIZE];
  String query = "";
  Label l = new Label();
  TextArea ta = new TextArea(15, 60);
  public void init() {
    Panel p = new Panel();
    p.setLayout(new GridLayout(t.length + 2, 2));
    for(int i = 0; i < t.length; i++) {
      p.add(new Label(
        "Field " + i + "  ", Label.RIGHT));
      p.add(t[i] = new TextField(30));
    }
    p.add(l);
    p.add(submit);
    add("North", p);
    add("South", ta);
  }
  public boolean action (Event evt, Object arg) {
    if(evt.target.equals(submit)) {
      query = "";
      ta.setText("");
      // Encode the query from the field data:
      for(int i = 0; i < t.length; i++)
         query += "Field" + i + "=" +
           URLEncoder.encode(
             t[i].getText().trim()) +
           "&";
      query += "submit=Submit";
      // Send the name using CGI's POST process:
      try {
        URL u = new URL(
          getDocumentBase(), "cgi-bin/POSTtest");
        URLConnection urlc = u.openConnection();
        urlc.setDoOutput(true);
        urlc.setDoInput(true);
        urlc.setAllowUserInteraction(false);
        DataOutputStream server = 
          new DataOutputStream(
            urlc.getOutputStream());
        // Send the data
        server.writeBytes(query);
        server.close();
        // Read and display the response. You
        // cannot use 
        // getAppletContext().showDocument(u);
        // to display the results as a Web page!
        DataInputStream in = 
          new DataInputStream(
            urlc.getInputStream());
        String s;
        while((s = in.readLine()) != null) {
          ta.appendText(s + "\n");
        }
        in.close();
      }
      catch (Exception e) {
        l.setText(e.toString());
      }
    }
    else return super.action(evt, arg);
    return true;
  }
} ///:~

Once the information is sent to the server, you can call getInputStream( ) and wrap the return value in a DataInputStream so that you can read the results. One thing you’ll notice is that the results are displayed as lines of text in a TextArea. Why not simply use getAppletContext().showDocument(u)? Well, this is one of those mysteries. The code above works fine, but if you try to use showDocument( ) instead, everything stops working – almost. That is, showDocument( ) does work, but what you get back from POSTtest is “Zero CONTENT_LENGTH.” So somehow, showDocument( ) prevents the POST query from being passed on to the CGI program. It’s difficult to know whether this is a bug that will be fixed, or some lack of understanding on my part (the books I looked at were equally abstruse). In any event, if you can stand to limit yourself to looking at the text that comes back from the CGI program, the above applet works fine.

Connecting to databases
with JDBC

It has been estimated that half of all software development involves client/server operations. A great promise of Java has been the ability to build platform-independent client/server database applications. In Java 1.1 this has come to fruition with Java DataBase Connectivity (JDBC).

One of the major problems with databases has been the feature wars between the database companies. There is a “standard” database language, Structured Query Language (SQL-92), but usually you must know which database vendor you’re working with despite the standard. JDBC is designed to be platform-independent, so you don’t need to worry about the database you’re using while you’re programming. However, it’s still possible to make vendor-specific calls from JDBC so you aren’t restricted from doing what you must.

JDBC, like many of the APIs in Java, is designed for simplicity. The method calls you make correspond to the logical operations you’d think of doing when gathering data from a database: connect to the database, create a statement and execute the query, and look at the result set.

To allow this platform independence, JDBC provides a driver manager that dynamically maintains all the driver objects that your database queries will need. So if you have three different kinds of vendor databases to connect to, you’ll need three different driver objects. The driver objects register themselves with the driver manager at the time of loading, and you can force the loading using Class.forName( ).

To open a database, you must create a “database URL” that specifies:

  1. That you’re using JDBC with “jdbc”
  2. The “subprotocol”: the name of the driver or the name of a database connectivity mechanism. Since the design of JDBC was inspired by ODBC, the first subprotocol available is the “jdbc-odbc bridge,” specified by “odbc”
  3. The database identifier. This varies with the database driver used, but it generally provides a logical name that is mapped by the database administration software to a physical directory where the database tables are located. For your database identifier to have any meaning, you must register the name using your database administration software. (The process of registration varies from platform to platform.)

All this information is combined into one string, the “database URL.” For example, to connect through the ODBC subprotocol to a database identified as “people,” the database URL could be:

String dbUrl = "jdbc:odbc:people";

If you’re connecting across a network, the database URL will also contain the information identifying the remote machine.

When you’re ready to connect to the database, you call the static method DriverManager.getConnection( ), passing it the database URL, the user name, and a password to get into the database. You get back a Connection object that you can then use to query and manipulate the database.

The following example opens a database of contact information and looks for a person’s last name as given on the command line. It selects only the names of people that have email addresses, then prints out all the ones that match the given last name:

//: Lookup.java
// Looks up email addresses in a 
// local database using JDBC
import java.sql.*;

public class Lookup {
  public static void main(String[] args) {
    String dbUrl = "jdbc:odbc:people";
    String user = "";
    String password = "";
    try {
      // Load the driver (registers itself)
      Class.forName(
        "sun.jdbc.odbc.JdbcOdbcDriver");
      Connection c = DriverManager.getConnection(
        dbUrl, user, password);
      Statement s = c.createStatement();
      // SQL code:
      ResultSet r = 
        s.executeQuery(
          "SELECT FIRST, LAST, EMAIL " +
          "FROM people.csv people " +
          "WHERE " +
          "(LAST='" + args[0] + "') " +
          " AND (EMAIL Is Not Null) " +
          "ORDER BY FIRST");
      while(r.next()) {
        // Capitalization doesn't matter:
        System.out.println(
          r.getString("Last") + ", " 
          + r.getString("fIRST")
          + ": " + r.getString("EMAIL") );
      }
      s.close(); // Also closes ResultSet
    } catch(Exception e) {
      e.printStackTrace();
    }
  }
} ///:~

You can see the creation of the database URL as previously described. In this example, there is no password protection on the database so the user name and password are empty strings.

Once the connection is made with DriverManager.getConnection( ), you can use the resulting Connection object to create a Statement object using the createStatement( ) method. With the resulting Statement, you can call executeQuery( ), passing in a string containing an SQL-92 standard SQL statement. (You’ll see shortly how you can generate this statement automatically, so you don’t have to know much about SQL.)

The executeQuery( ) method returns a ResultSet object, which is quite a bit like an iterator: the next( ) method moves the iterator to the next record in the statement, or returns false if the end of the result set has been reached. You’ll always get a ResultSet object back from executeQuery( ) even if a query results in an empty set (that is, an exception is not thrown). Note that you must call next( ) once before trying to read any record data. If the result set is empty, this first call to next( ) will return false. For each record in the result set, you can select the fields using (among other approaches) the field name as a string. Also note that the capitalization of the field name is ignored – it doesn’t matter with an SQL database. You determine the type you’ll get back by calling getInt( ), getString( ), getFloat( ), etc. At this point, you’ve got your database data in Java native format and can do whatever you want with it using ordinary Java code.

Getting the example to work

With JDBC, understanding the code is relatively simple. The confusing part is making it work on your particular system. The reason this is confusing is that it requires you to figure out how to get your JDBC driver to load properly, and how to set up a database using your database administration software.

Of course, this process can vary radically from machine to machine, but the process I used to make it work under 32-bit Windows might give you clues to help you attack your own situation.

Step 1: Find the JDBC Driver

The program above contains the statement:

Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");

This implies a directory structure, which is deceiving. With this particular installation of JDK 1.1, there was no file called JdbcOdbcDriver.class, so if you looked at this example and went searching for it you’d be frustrated. Other published examples use a pseudo name, such as “myDriver.ClassName,” which is less than helpful. In fact, the load statement above for the jdbc-odbc driver (the only one that actually comes with JDK 1.1) appears in only a few places in the online documentation (in particular, a page labeled “JDBC-ODBC Bridge Driver”). If the load statement above doesn’t work, then the name might have been changed as part of a Java version change, so you should hunt through the documentation again.

If the load statement is wrong, you’ll get an exception at this point. To test whether your driver load statement is working correctly, comment out the code after the statement and up to the catch clause; if the program throws no exceptions it means that the driver is loading properly.

Step 2: Configure the database

Again, this is specific to 32-bit Windows; you might need to do some research to figure it out for your own platform.

First, open the control panel. You might find two icons that say “ODBC.” You must use the one that says “32bit ODBC,” since the other one is for backwards compatibility with 16-bit ODBC software and will produce no results for JDBC. When you open the “32bit ODBC” icon, you’ll see a tabbed dialog with a number of tabs, including “User DSN,” “System DSN,” “File DSN,” etc., in which “DSN” means “Data Source Name.” It turns out that for the JDBC-ODBC bridge, the only place where it’s important to set up your database is “System DSN,” but you’ll also want to test your configuration and create queries, and for that you’ll also need to set up your database in “File DSN.” This will allow the Microsoft Query tool (that comes with Microsoft Office) to find the database. Note that other query tools are also available from other vendors.

The most interesting database is one that you’re already using. Standard ODBC supports a number of different file formats including such venerable workhorses as DBase. However, it also includes the simple “comma-separated ASCII” format, which virtually every data tool has the ability to write. In my case, I just took my “people” database that I’ve been maintaining for years using various contact-management tools and exported it as a comma-separated ASCII file (these typically have an extension of .csv). In the “File DSN” section I chose “Add,” chose the text driver to handle my comma-separated ASCII file, and then un-checked “use current directory” to allow me to specify the directory where I exported the data file.

You’ll notice when you do this that you don’t actually specify a file, only a directory. That’s because a database is typically represented as a collection of files under a single directory (although it could be represented in other forms as well). Each file usually contains a single table, and the SQL statements can produce results that are culled from multiple tables in the database (this is called a join). A database that contains only a single table (like this one) is usually called a flat-file database. Most problems that go beyond the simple storage and retrieval of data generally require multiple tables that must be related by joins to produce the desired results, and these are called relational databases.

Step 3: Test the configuration

To test the configuration you’ll need a way to discover whether the database is visible from a program that queries it. Of course, you can simply run the JDBC program example above up to and including the statement:

Connection c = DriverManager.getConnection(
  dbUrl, user, password);

If an exception is thrown, your configuration was incorrect.

However, it’s useful to get a query-generation tool involved at this point. I used Microsoft Query that came with Microsoft Office, but you might prefer something else. The query tool must know where the database is, and Microsoft Query required that I go to the ODBC Administrator’s “File DSN” tab and add a new entry there, again specifying the text driver and the directory where my database lives. You can name the entry anything you want, but it’s helpful to use the same name you used in “System DSN.”

Once you’ve done this, you will see that your database is available when you create a new query using your query tool.

Step 4: Generate your SQL query

The query that I created using Microsoft Query not only showed me that my database was there and in good order, but it also automatically created the SQL code that I needed to insert into my Java program. I wanted a query that would search for records that had the last name that was typed on the command line when starting the Java program. So as a starting point, I searched for a specific last name, ‘Eckel’. I also wanted to display only those names that had email addresses associated with them. The steps I took to create this query were:

  1. Start a new query and use the Query Wizard. Select the “people” database. (This is the equivalent of opening the database connection using the appropriate database URL.)
  2. Select the “people” table within the database. From within the table, choose the columns FIRST, LAST, and EMAIL.
  3. Under “Filter Data,” choose LAST and select “equals” with an argument of Eckel. Click the “And” radio button.
  4. Choose EMAIL and select “Is not Null.”
  5. Under “Sort By,” choose FIRST.

The result of this query will show you whether you’re getting what you want.

Now you can press the SQL button and without any research on your part, up will pop the correct SQL code, ready for you to cut and paste. For this query, it looked like this:

SELECT people.FIRST, people.LAST, people.EMAIL
FROM people.csv people
WHERE (people.LAST='Eckel') AND 
(people.EMAIL Is Not Null)
ORDER BY people.FIRST

With more complicated queries it’s easy to get things wrong, but with a query tool you can interactively test your queries and automatically generate the correct code. It’s hard to argue the case for doing this by hand.

Step 5: Modify and paste in your query

You’ll notice that the code above looks different from what’s used in the program. That’s because the query tool uses full qualification for all of the names, even when there’s only one table involved. (When more than one table is involved, the qualification prevents collisions between columns from different tables that have the same names.) Since this query involves only one table, you can optionally remove the “people” qualifier from most of the names, like this:

SELECT FIRST, LAST, EMAIL
FROM people.csv people
WHERE (LAST='Eckel') AND 
(EMAIL Is Not Null)
ORDER BY FIRST

In addition, you don’t want this program to be hard coded to look for only one name. Instead, it should hunt for the name given as the command-line argument. Making these changes and turning the SQL statement into a dynamically-created String produces:

"SELECT FIRST, LAST, EMAIL " +
"FROM people.csv people " +
"WHERE " +
"(LAST='" + args[0] + "') " +
" AND (EMAIL Is Not Null) " +
"ORDER BY FIRST");

SQL has another way to insert names into a query called stored procedures, which is used for speed. But for much of your database experimentation and for your first cut, building your own query strings in Java is fine.

You can see from this example that by using the tools currently available – in particular the query-building tool – database programming with SQL and JDBC can be quite straightforward.

A GUI version of the lookup program

It’s more useful to leave the lookup program running all the time and simply switch to it and type in a name whenever you want to look someone up. The following program creates the lookup program as an application/applet, and it also adds name completion so the data will show up without forcing you to type the entire last name:

//: VLookup.java
// GUI version of Lookup.java
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
import java.sql.*;

public class VLookup extends Applet {
  String dbUrl = "jdbc:odbc:people";
  String user = "";
  String password = "";
  Statement s;
  TextField searchFor = new TextField(20);
  Label completion = 
    new Label("                        ");
  TextArea results = new TextArea(40, 20);
  public void init() {
    searchFor.addTextListener(new SearchForL());
    Panel p = new Panel();
    p.add(new Label("Last name to search for:"));
    p.add(searchFor);
    p.add(completion);
    setLayout(new BorderLayout());
    add(p, BorderLayout.NORTH);
    add(results, BorderLayout.CENTER);
    try {
      // Load the driver (registers itself)
      Class.forName(
        "sun.jdbc.odbc.JdbcOdbcDriver");
      Connection c = DriverManager.getConnection(
        dbUrl, user, password);
      s = c.createStatement();
    } catch(Exception e) {
      results.setText(e.getMessage());
    }
  }
  class SearchForL implements TextListener {
    public void textValueChanged(TextEvent te) {
      ResultSet r;
      if(searchFor.getText().length() == 0) {
        completion.setText("");
        results.setText("");
        return;
      }
      try {
        // Name completion:
        r = s.executeQuery(
          "SELECT LAST FROM people.csv people " +
          "WHERE (LAST Like '" +
          searchFor.getText()  + 
          "%') ORDER BY LAST");
        if(r.next()) 
          completion.setText(
            r.getString("last"));
        r = s.executeQuery(
          "SELECT FIRST, LAST, EMAIL " +
          "FROM people.csv people " +
          "WHERE (LAST='" + 
          completion.getText() +
          "') AND (EMAIL Is Not Null) " +
          "ORDER BY FIRST");
      } catch(Exception e) {
        results.setText(
          searchFor.getText() + "\n");
        results.append(e.getMessage());
        return; 
      }
      results.setText("");
      try {
        while(r.next()) {
          results.append(
            r.getString("Last") + ", " 
            + r.getString("fIRST") + 
            ": " + r.getString("EMAIL") + "\n");
        }
      } catch(Exception e) {
        results.setText(e.getMessage());
      }
    }
  }
  public static void main(String[] args) {
    VLookup applet = new VLookup();
    Frame aFrame = new Frame("Email lookup");
    aFrame.addWindowListener(
      new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
          System.exit(0);
        }
      });
    aFrame.add(applet, BorderLayout.CENTER);
    aFrame.setSize(500,200);
    applet.init();
    applet.start();
    aFrame.setVisible(true);
  }
} ///:~

Much of the database logic is the same, but you can see that a TextListener is added to listen to the TextField, so that whenever you type a new character it first tries to do a name completion by looking up the last name in the database and using the first one that shows up. (It places it in the completion Label, and uses that as the lookup text.) This way, as soon as you’ve typed enough characters for the program to uniquely find the name you’re looking for, you can stop.

Why the JDBC API
seems so complex

When you browse the online documentation for JDBC it can seem daunting. In particular, in the DatabaseMetaData interface – which is just huge, contrary to most of the interfaces you see in Java – there are methods such as dataDefinitionCausesTransactionCommit( ), getMaxColumnNameLength( ), getMaxStatementLength( ), storesMixedCaseQuotedIdentifiers( ), supportsANSI92IntermediateSQL( ), supportsLimitedOuterJoins( ), and so on. What’s this all about?

As mentioned earlier, databases have seemed from their inception to be in a constant state of turmoil, primarily because the demand for database applications, and thus database tools, is so great. Only recently has there been any convergence on the common language of SQL (and there are plenty of other database languages in common use). But even with an SQL “standard” there are so many variations on that theme that JDBC must provide the large DatabaseMetaData interface so that your code can discover the capabilities of the particular “standard” SQL database that it’s currently connected to. In short, you can write simple, transportable SQL, but if you want to optimize speed your coding will multiply tremendously as you investigate the capabilities of a particular vendor’s database.

This, of course, is not Java’s fault. The discrepancies between database products are just something that JDBC tries to help compensate for. But bear in mind that your life will be easier if you can either write generic queries and not worry too much about performance, or, if you must tune for performance, know the platform you’re writing for so you don’t need to write all that investigation code.

There is more JDBC information available in the electronic documents that come as part of the Java 1.1 distribution from Sun. In addition, you can find more in the book JDBC Database Access with Java (Hamilton, Cattel, and Fisher, Addison-Wesley 1997). Other JDBC books are appearing regularly.

Remote methods

Traditional approaches to executing code on other machines across a network have been confusing as well as tedious and error-prone to implement. The nicest way to think about this problem is that some object happens to live on another machine, and you can send a message to that object and get a result as if the object lived on your local machine. This simplification is exactly what Java 1.1 Remote Method Invocation (RMI) allows you to do. This section walks you through the steps necessary to create your own RMI objects.

Remote interfaces

RMI makes heavy use of interfaces. When you want to create a remote object, you mask the underlying implementation by passing around an interface. Thus, when the client gets a handle to a remote object, what they really get is an interface handle, which happens to connect to some local stub code that talks across the network. But you don’t think about this, you just send messages via your interface handle.

When you create a remote interface, you must follow these guidelines:

  1. The remote interface must be public (it cannot have “package access,” that is, it cannot be “friendly”). Otherwise, a client will get an error when attempting to load a remote object that implements the remote interface.
  2. The remote interface must extend the interface java.rmi.Remote.
  3. Each method in the remote interface must declare java.rmi.RemoteException in its throws clause in addition to any application-specific exceptions.
  4. A remote object passed as an argument or return value (either directly or embedded within a local object) must be declared as the remote interface, not the implementation class.

Here’s a simple remote interface that represents an accurate time service:

//: PerfectTimeI.java
// The PerfectTime remote interface
package c15.ptime;
import java.rmi.*;

interface PerfectTimeI extends Remote {
  long getPerfectTime() throws RemoteException;
} ///:~

It looks like any other interface except that it extends Remote and all of its methods throw RemoteException. Remember that an interface and all of its methods are automatically public.

Implementing the remote interface

The server must contain a class that extends UnicastRemoteObject and implements the remote interface. This class can also have additional methods, but only the methods in the remote interface will be available to the client, of course, since the client will get only a handle to the interface, not the class that implements it.

You must explicitly define the constructor for the remote object even if you’re only defining a default constructor that calls the base-class constructor. You must write it out since it must throw RemoteException.

Here’s the implementation of the remote interface PerfectTimeI:

//: PerfectTime.java
// The implementation of the PerfectTime 
// remote object
package c15.ptime;
import java.rmi.*;
import java.rmi.server.*;
import java.rmi.registry.*;
import java.net.*;

public class PerfectTime 
    extends UnicastRemoteObject
    implements PerfectTimeI {
  // Implementation of the interface:
  public long getPerfectTime() 
      throws RemoteException {
    return System.currentTimeMillis();
  }
  // Must implement constructor to throw
  // RemoteException:
  public PerfectTime() throws RemoteException {
    // super(); // Called automatically
  }
  // Registration for RMI serving:
  public static void main(String[] args) {
    System.setSecurityManager(
      new RMISecurityManager());
    try {
      PerfectTime pt = new PerfectTime();
      Naming.bind(
        "//colossus:2005/PerfectTime", pt);
      System.out.println("Ready to do time");
    } catch(Exception e) {
      e.printStackTrace();
    }
  }
} ///:~

Here, main( ) handles all the details of setting up the server. When you’re serving RMI objects, at some point in your program you must:

  1. Create and install a security manager that supports RMI. The only one available for RMI as part of the Java distribution is RMISecurityManager.
  2. Create one or more instances of a remote object. Here, you can see the creation of the PerfectTime object.
  3. Register at least one of the remote objects with the RMI remote object registry for bootstrapping purposes. One remote object can have methods that produce handles to other remote objects. This allows you to set it up so the client must go to the registry only once, to get the first remote object.

Setting up the registry

Here, you see a call to the static method Naming.bind( ). However, this call requires that the registry be running as a separate process on the computer. The name of the registry server is rmiregistry, and under 32-bit Windows you say:

start rmiregistry

to start it in the background. On Unix, it is:

rmiregistry &

Like many network programs, the rmiregistry is located at the IP address of whatever machine started it up, but it must also be listening at a port. If you invoke the rmiregistry as above, with no argument, the registry’s port will default to 1099. If you want it to be at some other port, you add an argument on the command line to specify the port. For this example, the port will be located at 2005, so the rmiregistry should be started like this under 32-bit Windows:

start rmiregistry 2005

or for Unix:

rmiregistry 2005 &

The information about the port must also be given to the bind( ) command, as well as the IP address of the machine where the registry is located. But this brings up what can be a frustrating problem if you’re expecting to test RMI programs locally the way the network programs have been tested so far in this chapter. In the JDK 1.1.1 release, there are a couple of problems:[65]

  1. localhost does not work with RMI. Thus, to experiment with RMI on a single machine, you must provide the name of the machine. To find out the name of your machine under 32-bit Windows, go to the control panel and select “Network.” Select the “Identification” tab, and you’ll see your computer name. In my case, I called my computer “Colossus” (for all the hard disks I’ve had to put on to hold all the different development systems). It appears that capitalization is ignored.
  2. RMI will not work unless your computer has an active TCP/IP connection, even if all your components are just talking to each other on the local machine. This means that you must connect to your Internet service provider before trying to run the program or you’ll get some obscure exception messages.

Will all this in mind, the bind( ) command becomes:

Naming.bind("//colossus:2005/PerfectTime", pt);

If you are using the default port 1099, you don’t need to specify a port, so you could say:

Naming.bind("//colossus/PerfectTime", pt);

In a future release of the JDK (after 1.1) when the localhost bug is fixed, you will be able to perform local testing by leaving off the IP address and using only the identifier:

Naming.bind("PerfectTime", pt);

The name for the service is arbitrary; it happens to be PerfectTime here, just like the name of the class, but you could call it anything you want. The important thing is that it’s a unique name in the registry that the client knows to look for to procure the remote object. If the name is already in the registry, you’ll get an AlreadyBoundException. To prevent this, you can always use rebind( ) instead of bind( ), since rebind( ) either adds a new entry or replaces the one that’s already there.

Even though main( ) exits, your object has been created and registered so it’s kept alive by the registry, waiting for a client to come along and request it. As long as the rmiregistry is running and you don’t call Naming.unbind( ) on your name, the object will be there. For this reason, when you’re developing your code you need to shut down the rmiregistry and restart it when you compile a new version of your remote object.

You aren’t forced to start up rmiregistry as an external process. If you know that your application is the only one that’s going to use the registry, you can start it up inside your program with the line:

LocateRegistry.createRegistry(2005);

Like before, 2005 is the port number we happen to be using in this example. This is the equivalent of running rmiregistry 2005 from a command line, but it can often be more convenient when you’re developing RMI code since it eliminates the extra steps of starting and stopping the registry. Once you’ve executed this code, you can bind( ) using Naming as before.

Creating stubs and skeletons

If you compile and run PerfectTime.java, it won’t work even if you have the rmiregistry running correctly. That’s because the framework for RMI isn’t all there yet. You must first create the stubs and skeletons that provide the network connection operations and allow you to pretend that the remote object is just another local object on your machine.

What’s going on behind the scenes is complex. Any objects that you pass into or return from a remote object must implement Serializable (if you want to pass remote references instead of the entire objects, the object arguments can implement Remote), so you can imagine that the stubs and skeletons are automatically performing serialization and deserialization as they “marshal” all of the arguments across the network and return the result. Fortunately, you don’t have to know any of this, but you do have to create the stubs and skeletons. This is a simple process: you invoke the rmic tool on your compiled code, and it creates the necessary files. So the only requirement is that another step be added to your compilation process.

The rmic tool is particular about packages and classpaths, however. PerfectTime.java is in the package c15.Ptime, and even if you invoke rmic in the same directory in which PerfectTime.class is located, rmic won’t find the file, since it searches the classpath. So you must specify the location off the class path, like so:

rmic c15.PTime.PerfectTime

You don’t have to be in the directory containing PerfectTime.class when you execute this command, but the results will be placed in the current directory.

When rmic runs successfully, you’ll have two new classes in the directory:

PerfectTime_Stub.class
PerfectTime_Skel.class

corresponding to the stub and skeleton. Now you’re ready to get the server and client to talk to each other.

Using the remote object

The whole point of RMI is to make the use of remote objects simple. The only extra thing that you must do in your client program is to look up and fetch the remote interface from the server. From then on, it’s just regular Java programming: sending messages to objects. Here’s the program that uses PerfectTime:

//: DisplayPerfectTime.java
// Uses remote object PerfectTime
package c15.ptime;
import java.rmi.*;
import java.rmi.registry.*;

public class DisplayPerfectTime {
  public static void main(String[] args) {
    System.setSecurityManager(
      new RMISecurityManager());
    try {
      PerfectTimeI t = 
        (PerfectTimeI)Naming.lookup(
          "//colossus:2005/PerfectTime");
      for(int i = 0; i < 10; i++)
        System.out.println("Perfect time = " +
          t.getPerfectTime());
    } catch(Exception e) {
      e.printStackTrace();
    }
  }
} ///:~

The ID string is the same as the one used to register the object with Naming, and the first part represents the URL and port number. Since you’re using a URL, you can also specify a machine on the Internet.

What comes back from Naming.lookup( ) must be cast to the remote interface, not to the class. If you use the class instead, you’ll get an exception.

You can see in the method call

t.getPerfectTime( )

that once you have a handle to the remote object, programming with it is indistinguishable from programming with a local object (with one difference: remote methods throw RemoteException).

Alternatives to RMI

RMI is just one way to create objects that can be distributed across a network. It has the advantage of being a “pure Java” solution, but if you have a lot of code written in some other language, it might not meet your needs. The two most compelling alternatives are Microsoft’s DCOM (which, according to Microsoft’s plan, will eventually be hosted on platforms other than Windows) and CORBA, which is supported in Java 1.1 and was designed from the start to be cross-platform. You can get an introduction to distributed objects in Java (albeit with a clear bias towards CORBA) in Client/Server Programming with Java and CORBA by Orfali & Harkey (John Wiley & Sons, 1997). A more serious treatment of CORBA is given by Java Programming with CORBA by Andreas Vogel
and Keith Duddy (John Wiley & Sons, 1997).

Summary

There’s actually a lot more to networking than can be covered in this introductory treatment. Java networking also provides fairly extensive support for URLs, including protocol handlers for different types of content that can be discovered at an Internet site.

In addition, an up-and-coming technology is the Servlet Server, which is an Internet server that uses Java to handle requests instead of the slow and rather awkward CGI (Common Gateway Interface) protocol. This means that to provide services on the server side you’ll be able to write in Java instead of using some other language that you might not know as well. You’ll also get the portability benefits of Java so you won’t have to worry about the particular platform the server is hosted on.

These and other features are fully and carefully described in Java Network Programming by Elliotte Rusty Harold (O’Reilly, 1997).

Exercises

  1. Compile and run the JabberServer and JabberClient programs in this chapter. Now edit the files to remove all of the buffering for the input and output, then compile and run them again to observe the results.
  2. Create a server that asks for a password, then opens a file and sends the file over the network connection. Create a client that connects to this server, gives the appropriate password, then captures and saves the file. Test the pair of programs on your machine using the localhost (the local loopback IP address 127.0.0.1 produced by calling InetAddress.getByName(null)).
  3. Modify the server in Exercise 2 so that it uses multithreading to handle multiple clients.
  4. Modify JabberClient so that output flushing doesn’t occur and observe the effect.
  5. Build on ShowHTML.java to produce an applet that is a password-protected gateway to a particular portion of your Web site.
  6. (More challenging) Create a client/server pair of programs that use datagrams to transmit a file from one machine to the other. (See the description at the end of the datagram section of this chapter.)
  7. (More challenging) Take the VLookup.java program and modify it so that when you click on the resulting name it automatically takes that name and copies it to the clipboard (so you can simply paste it into your email). You’ll need to look back at the IO stream chapter to remember how to use the Java 1.1 clipboard.

[59] This means a maximum of just over four billion numbers, which is rapidly running out. The new standard for IP addresses will use a 128-bit number, which should produce enough unique IP addresses for the foreseeable future.

[60] TCP and UDP ports are considered unique. That is, you can simultaneously run a TCP and UDP server on port 8080 without interference.

[61] You can test this under Windows32 using the Microsoft Personal Web Server that comes with Microsoft Office 97 and some of their other products. This is a nice way to experiment since you can perform local tests (and it's also fast). If you're on a different platform or if you don't have Office 97, you might be able to find a freeware Web server for testing by searching the Internet.

[62] GNU stands for “Gnu’s Not Unix.” The project, created by the Free Software Foundation, was originally intended to replace the Unix operating system with a free version of that OS. Linux appears to have replaced this initiative, but the GNU tools have played an integral part in the development of Linux, which comes packaged with many GNU components.

[63] My book Thinking in C++ (Prentice-Hall, 1995) devotes an entire chapter to this subject. Refer to this if you need further information on the subject.

[64] I can’t say I really understand what’s going on here, but I managed to get it working by studying Java Network Programming by Elliotte Rusty Harold (O’Reilly 1997). He alludes to a number of confusing bugs in the Java networking libraries, so this is an area in which you can’t just write code and have it work right away. Be warned.

[65] Many brain cells died in agony to discover this information.

[ Previous Chapter ] [ Short TOC ] [ Table of Contents ] [ Index ] [ Next Chapter ]
Last Update:02/04/2000