[ Team LiB ] Previous Section Next Section

5.5 A Generic Client

The Connect class of Example 5-4 was a useful first example of the Socket class, but it is too simple to use with most network protocols. Example 5-5 defines a class, GenericClient, that can serve as a client for a variety of text-based services. When you run this program, it connects to the host and port you have specified on the command line. From that point on, it simply sends the text you type to the server and then outputs the text the server sends in response to the console.

You can use GenericClient to download files from a web server by sending HTTP GET commands, for example. (We'll see what that protocol looks like in Example 5-6.) For big files, however, the server's output scrolls by too quickly for this to be useful. GenericClient is more useful for text-based interactive protocols. The Post Office Protocol (POP) is such a protocol. You can use GenericClient to preview any email you have waiting for you at your ISP (or elsewhere). An interaction, using GenericClient, with a POP server might look as follows. The lines in bold are those typed by the user:

oxymoron% java je3.net.GenericClient mail.isp.net 110
Connected to mail.isp.net/208.99.99.251:110
+OK QUALCOMM Pop server derived from UCB (version 2.1.4-R3) at mail.isp.net
starting.
USER david
+OK Password required for david.
PASS notrealpassword
+OK david has 3 message(s) (2861 octets).
RETR 3
+OK 363 octets
Received: from obsidian.oreilly.com (obsidian.oreilly.com [207.144.66.251])
        by mail.isp.net (8.8.5/8.8.5) with SMTP id RAA11654
        for david@isp.net; Wed, 21 Jun 2999 17:01:50 -0400 (EDT)
Date: Wed, 25 Jun 1997 17:01:50 -0400 (EDT)
Message-Id: <199706252101.RAA11654@mail.isp.net>
From: "Brett McLaughlin" <bmc@oreilly.com>
To: david@isp.net
Subject: schedule!

Aren't you done with that book yet?
.
DELE 3
+OK Message 3 has been deleted.
QUIT
+OK Pop server at mail.isp.net signing off.
Connection closed by server.
oxymoron%

The GenericClient class uses two threads. The main thread reads input from the console and sends it to the server. This thread spends most of its time blocked while waiting for input from the user. The second thread is implemented by an anonymous inner class. This second thread reads data from the server and prints it out to the console. It spends most of its time blocked while waiting to read from the server. Two threads are required because there are two read methods that must both block at the same time! The java.net package does not support nonblocking streams, and it is common for networking code (particularly server code) to require two or more threads. (This is the reason I placed the Chapter 4 chapter of this book between the chapter on I/O and this chapter on networking.) In Java 1.4, the java.nio package and its subpackages allow multiple nonblocking channels to be "multiplexed" in a single thread, which greatly enhances the scalability of Java servers. We'll learn about this in Chapter 6.

Example 5-5. GenericClient.java
package je3.net;
import java.io.*;
import java.net.*;

/**
 * This program connects to a server at a specified host and port.
 * It reads text from the console and sends it to the server.
 * It reads text from the server and sends it to the console.
 **/
public class GenericClient {
    public static void main(String[  ] args) throws IOException {
        try {
            // Check the number of arguments
            if (args.length != 2) 
                throw new IllegalArgumentException("Wrong number of args");
            
            // Parse the host and port specifications
            String host = args[0];
            int port = Integer.parseInt(args[1]);
            
            // Connect to the specified host and port
            Socket s = new Socket(host, port);
            
            // Set up streams for reading from and writing to the server.
            // The from_server stream is final for use in the inner class below
            final Reader from_server=new InputStreamReader(s.getInputStream( ));
            PrintWriter to_server = new PrintWriter(s.getOutputStream( ));
            
            // Set up streams for reading from and writing to the console
            // The to_user stream is final for use in the anonymous class below
            BufferedReader from_user = 
                new BufferedReader(new InputStreamReader(System.in));
            // Pass true for auto-flush on println( )
            final PrintWriter to_user = new PrintWriter(System.out, true);
            
            // Tell the user that we've connected
            to_user.println("Connected to " + s.getInetAddress( ) +
                            ":" + s.getPort( ));
            
            // Create a thread that gets output from the server and displays 
            // it to the user.  We use a separate thread for this so that we
            // can receive asynchronous output
            Thread t = new Thread( ) {
                public void run( ) {
                    char[  ] buffer = new char[1024];
                    int chars_read;
                    try { 
                        // Read characters from the server until the
                        // stream closes, and write them to the console
                        while((chars_read = from_server.read(buffer)) != -1) {
                            to_user.write(buffer, 0, chars_read);
                            to_user.flush( );
                        }
                    }
                    catch (IOException e) { to_user.println(e); }

                    // When the server closes the connection, the loop above
                    // will end.  Tell the user what happened, and call
                    // System.exit( ), causing the main thread to exit along
                    // with this one.
                    to_user.println("Connection closed by server.");
                    System.exit(0);
                }
            };
            
            // Now start the server-to-user thread
            t.start( );
            
            // In parallel, read the user's input and pass it on to the server.
            String line;
            while((line = from_user.readLine( )) != null) {
                to_server.print(line + "\r\n");
                to_server.flush( );
            }
            
            // If the user types a Ctrl-D (Unix) or Ctrl-Z (Windows) to end
            // their input, we'll get an EOF, and the loop above will exit.
            // When this happens, we stop the server-to-user thread and close
            // the socket.

            s.close( );
            to_user.println("Connection closed by client.");
            System.exit(0);
        }
        // If anything goes wrong, print an error message
        catch (Exception e) { 
            System.err.println(e);
            System.err.println("Usage: java GenericClient <hostname> <port>");
        }
    }
}
    [ Team LiB ] Previous Section Next Section